1609 lines
64 KiB
JavaScript
1609 lines
64 KiB
JavaScript
import { g as resolveStateDir } from "./paths-BDd7_JUB.js";
|
|
import { a as logDebug, o as logError } from "./exec-CTo4hK94.js";
|
|
import { t as rawDataToString } from "./ws-Cxo2p9fH.js";
|
|
import { h as GATEWAY_CLIENT_NAMES, m as GATEWAY_CLIENT_MODES, p as GATEWAY_CLIENT_IDS } from "./message-channel-BD3kuDks.js";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import fs from "node:fs";
|
|
import crypto, { randomUUID } from "node:crypto";
|
|
import AjvPkg from "ajv";
|
|
import { WebSocket as WebSocket$1 } from "ws";
|
|
import { Type } from "@sinclair/typebox";
|
|
|
|
//#region src/infra/device-identity.ts
|
|
const DEFAULT_DIR = path.join(os.homedir(), ".openclaw", "identity");
|
|
const DEFAULT_FILE = path.join(DEFAULT_DIR, "device.json");
|
|
function ensureDir(filePath) {
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
}
|
|
const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
function base64UrlEncode(buf) {
|
|
return buf.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/g, "");
|
|
}
|
|
function base64UrlDecode(input) {
|
|
const normalized = input.replaceAll("-", "+").replaceAll("_", "/");
|
|
const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
|
|
return Buffer.from(padded, "base64");
|
|
}
|
|
function derivePublicKeyRaw(publicKeyPem) {
|
|
const spki = crypto.createPublicKey(publicKeyPem).export({
|
|
type: "spki",
|
|
format: "der"
|
|
});
|
|
if (spki.length === ED25519_SPKI_PREFIX.length + 32 && spki.subarray(0, ED25519_SPKI_PREFIX.length).equals(ED25519_SPKI_PREFIX)) return spki.subarray(ED25519_SPKI_PREFIX.length);
|
|
return spki;
|
|
}
|
|
function fingerprintPublicKey(publicKeyPem) {
|
|
const raw = derivePublicKeyRaw(publicKeyPem);
|
|
return crypto.createHash("sha256").update(raw).digest("hex");
|
|
}
|
|
function generateIdentity() {
|
|
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
|
const publicKeyPem = publicKey.export({
|
|
type: "spki",
|
|
format: "pem"
|
|
}).toString();
|
|
const privateKeyPem = privateKey.export({
|
|
type: "pkcs8",
|
|
format: "pem"
|
|
}).toString();
|
|
return {
|
|
deviceId: fingerprintPublicKey(publicKeyPem),
|
|
publicKeyPem,
|
|
privateKeyPem
|
|
};
|
|
}
|
|
function loadOrCreateDeviceIdentity(filePath = DEFAULT_FILE) {
|
|
try {
|
|
if (fs.existsSync(filePath)) {
|
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
const parsed = JSON.parse(raw);
|
|
if (parsed?.version === 1 && typeof parsed.deviceId === "string" && typeof parsed.publicKeyPem === "string" && typeof parsed.privateKeyPem === "string") {
|
|
const derivedId = fingerprintPublicKey(parsed.publicKeyPem);
|
|
if (derivedId && derivedId !== parsed.deviceId) {
|
|
const updated = {
|
|
...parsed,
|
|
deviceId: derivedId
|
|
};
|
|
fs.writeFileSync(filePath, `${JSON.stringify(updated, null, 2)}\n`, { mode: 384 });
|
|
try {
|
|
fs.chmodSync(filePath, 384);
|
|
} catch {}
|
|
return {
|
|
deviceId: derivedId,
|
|
publicKeyPem: parsed.publicKeyPem,
|
|
privateKeyPem: parsed.privateKeyPem
|
|
};
|
|
}
|
|
return {
|
|
deviceId: parsed.deviceId,
|
|
publicKeyPem: parsed.publicKeyPem,
|
|
privateKeyPem: parsed.privateKeyPem
|
|
};
|
|
}
|
|
}
|
|
} catch {}
|
|
const identity = generateIdentity();
|
|
ensureDir(filePath);
|
|
const stored = {
|
|
version: 1,
|
|
deviceId: identity.deviceId,
|
|
publicKeyPem: identity.publicKeyPem,
|
|
privateKeyPem: identity.privateKeyPem,
|
|
createdAtMs: Date.now()
|
|
};
|
|
fs.writeFileSync(filePath, `${JSON.stringify(stored, null, 2)}\n`, { mode: 384 });
|
|
try {
|
|
fs.chmodSync(filePath, 384);
|
|
} catch {}
|
|
return identity;
|
|
}
|
|
function signDevicePayload(privateKeyPem, payload) {
|
|
const key = crypto.createPrivateKey(privateKeyPem);
|
|
return base64UrlEncode(crypto.sign(null, Buffer.from(payload, "utf8"), key));
|
|
}
|
|
function normalizeDevicePublicKeyBase64Url(publicKey) {
|
|
try {
|
|
if (publicKey.includes("BEGIN")) return base64UrlEncode(derivePublicKeyRaw(publicKey));
|
|
return base64UrlEncode(base64UrlDecode(publicKey));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
function deriveDeviceIdFromPublicKey(publicKey) {
|
|
try {
|
|
const raw = publicKey.includes("BEGIN") ? derivePublicKeyRaw(publicKey) : base64UrlDecode(publicKey);
|
|
return crypto.createHash("sha256").update(raw).digest("hex");
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
function publicKeyRawBase64UrlFromPem(publicKeyPem) {
|
|
return base64UrlEncode(derivePublicKeyRaw(publicKeyPem));
|
|
}
|
|
function verifyDeviceSignature(publicKey, payload, signatureBase64Url) {
|
|
try {
|
|
const key = publicKey.includes("BEGIN") ? crypto.createPublicKey(publicKey) : crypto.createPublicKey({
|
|
key: Buffer.concat([ED25519_SPKI_PREFIX, base64UrlDecode(publicKey)]),
|
|
type: "spki",
|
|
format: "der"
|
|
});
|
|
const sig = (() => {
|
|
try {
|
|
return base64UrlDecode(signatureBase64Url);
|
|
} catch {
|
|
return Buffer.from(signatureBase64Url, "base64");
|
|
}
|
|
})();
|
|
return crypto.verify(null, Buffer.from(payload, "utf8"), key, sig);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/infra/tls/fingerprint.ts
|
|
function normalizeFingerprint(input) {
|
|
return input.trim().replace(/^sha-?256\s*:?\s*/i, "").replace(/[^a-fA-F0-9]/g, "").toLowerCase();
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/infra/device-auth-store.ts
|
|
const DEVICE_AUTH_FILE = "device-auth.json";
|
|
function resolveDeviceAuthPath(env = process.env) {
|
|
return path.join(resolveStateDir(env), "identity", DEVICE_AUTH_FILE);
|
|
}
|
|
function normalizeRole(role) {
|
|
return role.trim();
|
|
}
|
|
function normalizeScopes(scopes) {
|
|
if (!Array.isArray(scopes)) return [];
|
|
const out = /* @__PURE__ */ new Set();
|
|
for (const scope of scopes) {
|
|
const trimmed = scope.trim();
|
|
if (trimmed) out.add(trimmed);
|
|
}
|
|
return [...out].toSorted();
|
|
}
|
|
function readStore(filePath) {
|
|
try {
|
|
if (!fs.existsSync(filePath)) return null;
|
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
const parsed = JSON.parse(raw);
|
|
if (parsed?.version !== 1 || typeof parsed.deviceId !== "string") return null;
|
|
if (!parsed.tokens || typeof parsed.tokens !== "object") return null;
|
|
return parsed;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
function writeStore(filePath, store) {
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}\n`, { mode: 384 });
|
|
try {
|
|
fs.chmodSync(filePath, 384);
|
|
} catch {}
|
|
}
|
|
function loadDeviceAuthToken(params) {
|
|
const store = readStore(resolveDeviceAuthPath(params.env));
|
|
if (!store) return null;
|
|
if (store.deviceId !== params.deviceId) return null;
|
|
const role = normalizeRole(params.role);
|
|
const entry = store.tokens[role];
|
|
if (!entry || typeof entry.token !== "string") return null;
|
|
return entry;
|
|
}
|
|
function storeDeviceAuthToken(params) {
|
|
const filePath = resolveDeviceAuthPath(params.env);
|
|
const existing = readStore(filePath);
|
|
const role = normalizeRole(params.role);
|
|
const next = {
|
|
version: 1,
|
|
deviceId: params.deviceId,
|
|
tokens: existing && existing.deviceId === params.deviceId && existing.tokens ? { ...existing.tokens } : {}
|
|
};
|
|
const entry = {
|
|
token: params.token,
|
|
role,
|
|
scopes: normalizeScopes(params.scopes),
|
|
updatedAtMs: Date.now()
|
|
};
|
|
next.tokens[role] = entry;
|
|
writeStore(filePath, next);
|
|
return entry;
|
|
}
|
|
function clearDeviceAuthToken(params) {
|
|
const filePath = resolveDeviceAuthPath(params.env);
|
|
const store = readStore(filePath);
|
|
if (!store || store.deviceId !== params.deviceId) return;
|
|
const role = normalizeRole(params.role);
|
|
if (!store.tokens[role]) return;
|
|
const next = {
|
|
version: 1,
|
|
deviceId: store.deviceId,
|
|
tokens: { ...store.tokens }
|
|
};
|
|
delete next.tokens[role];
|
|
writeStore(filePath, next);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/gateway/device-auth.ts
|
|
function buildDeviceAuthPayload(params) {
|
|
const version = params.version ?? (params.nonce ? "v2" : "v1");
|
|
const scopes = params.scopes.join(",");
|
|
const token = params.token ?? "";
|
|
const base = [
|
|
version,
|
|
params.deviceId,
|
|
params.clientId,
|
|
params.clientMode,
|
|
params.role,
|
|
scopes,
|
|
String(params.signedAtMs),
|
|
token
|
|
];
|
|
if (version === "v2") base.push(params.nonce ?? "");
|
|
return base.join("|");
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/sessions/session-label.ts
|
|
const SESSION_LABEL_MAX_LENGTH = 64;
|
|
function parseSessionLabel(raw) {
|
|
if (typeof raw !== "string") return {
|
|
ok: false,
|
|
error: "invalid label: must be a string"
|
|
};
|
|
const trimmed = raw.trim();
|
|
if (!trimmed) return {
|
|
ok: false,
|
|
error: "invalid label: empty"
|
|
};
|
|
if (trimmed.length > SESSION_LABEL_MAX_LENGTH) return {
|
|
ok: false,
|
|
error: `invalid label: too long (max ${SESSION_LABEL_MAX_LENGTH})`
|
|
};
|
|
return {
|
|
ok: true,
|
|
label: trimmed
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/primitives.ts
|
|
const NonEmptyString = Type.String({ minLength: 1 });
|
|
const SessionLabelString = Type.String({
|
|
minLength: 1,
|
|
maxLength: SESSION_LABEL_MAX_LENGTH
|
|
});
|
|
const GatewayClientIdSchema = Type.Union(Object.values(GATEWAY_CLIENT_IDS).map((value) => Type.Literal(value)));
|
|
const GatewayClientModeSchema = Type.Union(Object.values(GATEWAY_CLIENT_MODES).map((value) => Type.Literal(value)));
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/agent.ts
|
|
const AgentEventSchema = Type.Object({
|
|
runId: NonEmptyString,
|
|
seq: Type.Integer({ minimum: 0 }),
|
|
stream: NonEmptyString,
|
|
ts: Type.Integer({ minimum: 0 }),
|
|
data: Type.Record(Type.String(), Type.Unknown())
|
|
}, { additionalProperties: false });
|
|
const SendParamsSchema = Type.Object({
|
|
to: NonEmptyString,
|
|
message: NonEmptyString,
|
|
mediaUrl: Type.Optional(Type.String()),
|
|
mediaUrls: Type.Optional(Type.Array(Type.String())),
|
|
gifPlayback: Type.Optional(Type.Boolean()),
|
|
channel: Type.Optional(Type.String()),
|
|
accountId: Type.Optional(Type.String()),
|
|
sessionKey: Type.Optional(Type.String()),
|
|
idempotencyKey: NonEmptyString
|
|
}, { additionalProperties: false });
|
|
const PollParamsSchema = Type.Object({
|
|
to: NonEmptyString,
|
|
question: NonEmptyString,
|
|
options: Type.Array(NonEmptyString, {
|
|
minItems: 2,
|
|
maxItems: 12
|
|
}),
|
|
maxSelections: Type.Optional(Type.Integer({
|
|
minimum: 1,
|
|
maximum: 12
|
|
})),
|
|
durationHours: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
channel: Type.Optional(Type.String()),
|
|
accountId: Type.Optional(Type.String()),
|
|
idempotencyKey: NonEmptyString
|
|
}, { additionalProperties: false });
|
|
const AgentParamsSchema = Type.Object({
|
|
message: NonEmptyString,
|
|
agentId: Type.Optional(NonEmptyString),
|
|
to: Type.Optional(Type.String()),
|
|
replyTo: Type.Optional(Type.String()),
|
|
sessionId: Type.Optional(Type.String()),
|
|
sessionKey: Type.Optional(Type.String()),
|
|
thinking: Type.Optional(Type.String()),
|
|
deliver: Type.Optional(Type.Boolean()),
|
|
attachments: Type.Optional(Type.Array(Type.Unknown())),
|
|
channel: Type.Optional(Type.String()),
|
|
replyChannel: Type.Optional(Type.String()),
|
|
accountId: Type.Optional(Type.String()),
|
|
replyAccountId: Type.Optional(Type.String()),
|
|
threadId: Type.Optional(Type.String()),
|
|
groupId: Type.Optional(Type.String()),
|
|
groupChannel: Type.Optional(Type.String()),
|
|
groupSpace: Type.Optional(Type.String()),
|
|
timeout: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
lane: Type.Optional(Type.String()),
|
|
extraSystemPrompt: Type.Optional(Type.String()),
|
|
idempotencyKey: NonEmptyString,
|
|
label: Type.Optional(SessionLabelString),
|
|
spawnedBy: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const AgentIdentityParamsSchema = Type.Object({
|
|
agentId: Type.Optional(NonEmptyString),
|
|
sessionKey: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const AgentIdentityResultSchema = Type.Object({
|
|
agentId: NonEmptyString,
|
|
name: Type.Optional(NonEmptyString),
|
|
avatar: Type.Optional(NonEmptyString),
|
|
emoji: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false });
|
|
const AgentWaitParamsSchema = Type.Object({
|
|
runId: NonEmptyString,
|
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
}, { additionalProperties: false });
|
|
const WakeParamsSchema = Type.Object({
|
|
mode: Type.Union([Type.Literal("now"), Type.Literal("next-heartbeat")]),
|
|
text: NonEmptyString
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/agents-models-skills.ts
|
|
const ModelChoiceSchema = Type.Object({
|
|
id: NonEmptyString,
|
|
name: NonEmptyString,
|
|
provider: NonEmptyString,
|
|
contextWindow: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
reasoning: Type.Optional(Type.Boolean())
|
|
}, { additionalProperties: false });
|
|
const AgentSummarySchema = Type.Object({
|
|
id: NonEmptyString,
|
|
name: Type.Optional(NonEmptyString),
|
|
identity: Type.Optional(Type.Object({
|
|
name: Type.Optional(NonEmptyString),
|
|
theme: Type.Optional(NonEmptyString),
|
|
emoji: Type.Optional(NonEmptyString),
|
|
avatar: Type.Optional(NonEmptyString),
|
|
avatarUrl: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false }))
|
|
}, { additionalProperties: false });
|
|
const AgentsListParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
const AgentsListResultSchema = Type.Object({
|
|
defaultId: NonEmptyString,
|
|
mainKey: NonEmptyString,
|
|
scope: Type.Union([Type.Literal("per-sender"), Type.Literal("global")]),
|
|
agents: Type.Array(AgentSummarySchema)
|
|
}, { additionalProperties: false });
|
|
const AgentsFileEntrySchema = Type.Object({
|
|
name: NonEmptyString,
|
|
path: NonEmptyString,
|
|
missing: Type.Boolean(),
|
|
size: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
updatedAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
content: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const AgentsFilesListParamsSchema = Type.Object({ agentId: NonEmptyString }, { additionalProperties: false });
|
|
const AgentsFilesListResultSchema = Type.Object({
|
|
agentId: NonEmptyString,
|
|
workspace: NonEmptyString,
|
|
files: Type.Array(AgentsFileEntrySchema)
|
|
}, { additionalProperties: false });
|
|
const AgentsFilesGetParamsSchema = Type.Object({
|
|
agentId: NonEmptyString,
|
|
name: NonEmptyString
|
|
}, { additionalProperties: false });
|
|
const AgentsFilesGetResultSchema = Type.Object({
|
|
agentId: NonEmptyString,
|
|
workspace: NonEmptyString,
|
|
file: AgentsFileEntrySchema
|
|
}, { additionalProperties: false });
|
|
const AgentsFilesSetParamsSchema = Type.Object({
|
|
agentId: NonEmptyString,
|
|
name: NonEmptyString,
|
|
content: Type.String()
|
|
}, { additionalProperties: false });
|
|
const AgentsFilesSetResultSchema = Type.Object({
|
|
ok: Type.Literal(true),
|
|
agentId: NonEmptyString,
|
|
workspace: NonEmptyString,
|
|
file: AgentsFileEntrySchema
|
|
}, { additionalProperties: false });
|
|
const ModelsListParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
const ModelsListResultSchema = Type.Object({ models: Type.Array(ModelChoiceSchema) }, { additionalProperties: false });
|
|
const SkillsStatusParamsSchema = Type.Object({ agentId: Type.Optional(NonEmptyString) }, { additionalProperties: false });
|
|
const SkillsBinsParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
const SkillsBinsResultSchema = Type.Object({ bins: Type.Array(NonEmptyString) }, { additionalProperties: false });
|
|
const SkillsInstallParamsSchema = Type.Object({
|
|
name: NonEmptyString,
|
|
installId: NonEmptyString,
|
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 1e3 }))
|
|
}, { additionalProperties: false });
|
|
const SkillsUpdateParamsSchema = Type.Object({
|
|
skillKey: NonEmptyString,
|
|
enabled: Type.Optional(Type.Boolean()),
|
|
apiKey: Type.Optional(Type.String()),
|
|
env: Type.Optional(Type.Record(NonEmptyString, Type.String()))
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/channels.ts
|
|
const TalkModeParamsSchema = Type.Object({
|
|
enabled: Type.Boolean(),
|
|
phase: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const ChannelsStatusParamsSchema = Type.Object({
|
|
probe: Type.Optional(Type.Boolean()),
|
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
}, { additionalProperties: false });
|
|
const ChannelAccountSnapshotSchema = Type.Object({
|
|
accountId: NonEmptyString,
|
|
name: Type.Optional(Type.String()),
|
|
enabled: Type.Optional(Type.Boolean()),
|
|
configured: Type.Optional(Type.Boolean()),
|
|
linked: Type.Optional(Type.Boolean()),
|
|
running: Type.Optional(Type.Boolean()),
|
|
connected: Type.Optional(Type.Boolean()),
|
|
reconnectAttempts: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
lastConnectedAt: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
lastError: Type.Optional(Type.String()),
|
|
lastStartAt: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
lastStopAt: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
lastInboundAt: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
lastOutboundAt: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
lastProbeAt: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
mode: Type.Optional(Type.String()),
|
|
dmPolicy: Type.Optional(Type.String()),
|
|
allowFrom: Type.Optional(Type.Array(Type.String())),
|
|
tokenSource: Type.Optional(Type.String()),
|
|
botTokenSource: Type.Optional(Type.String()),
|
|
appTokenSource: Type.Optional(Type.String()),
|
|
baseUrl: Type.Optional(Type.String()),
|
|
allowUnmentionedGroups: Type.Optional(Type.Boolean()),
|
|
cliPath: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
dbPath: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
port: Type.Optional(Type.Union([Type.Integer({ minimum: 0 }), Type.Null()])),
|
|
probe: Type.Optional(Type.Unknown()),
|
|
audit: Type.Optional(Type.Unknown()),
|
|
application: Type.Optional(Type.Unknown())
|
|
}, { additionalProperties: true });
|
|
const ChannelUiMetaSchema = Type.Object({
|
|
id: NonEmptyString,
|
|
label: NonEmptyString,
|
|
detailLabel: NonEmptyString,
|
|
systemImage: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const ChannelsStatusResultSchema = Type.Object({
|
|
ts: Type.Integer({ minimum: 0 }),
|
|
channelOrder: Type.Array(NonEmptyString),
|
|
channelLabels: Type.Record(NonEmptyString, NonEmptyString),
|
|
channelDetailLabels: Type.Optional(Type.Record(NonEmptyString, NonEmptyString)),
|
|
channelSystemImages: Type.Optional(Type.Record(NonEmptyString, NonEmptyString)),
|
|
channelMeta: Type.Optional(Type.Array(ChannelUiMetaSchema)),
|
|
channels: Type.Record(NonEmptyString, Type.Unknown()),
|
|
channelAccounts: Type.Record(NonEmptyString, Type.Array(ChannelAccountSnapshotSchema)),
|
|
channelDefaultAccountId: Type.Record(NonEmptyString, NonEmptyString)
|
|
}, { additionalProperties: false });
|
|
const ChannelsLogoutParamsSchema = Type.Object({
|
|
channel: NonEmptyString,
|
|
accountId: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const WebLoginStartParamsSchema = Type.Object({
|
|
force: Type.Optional(Type.Boolean()),
|
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
verbose: Type.Optional(Type.Boolean()),
|
|
accountId: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const WebLoginWaitParamsSchema = Type.Object({
|
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
accountId: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/config.ts
|
|
const ConfigGetParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
const ConfigSetParamsSchema = Type.Object({
|
|
raw: NonEmptyString,
|
|
baseHash: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false });
|
|
const ConfigApplyParamsSchema = Type.Object({
|
|
raw: NonEmptyString,
|
|
baseHash: Type.Optional(NonEmptyString),
|
|
sessionKey: Type.Optional(Type.String()),
|
|
note: Type.Optional(Type.String()),
|
|
restartDelayMs: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
}, { additionalProperties: false });
|
|
const ConfigPatchParamsSchema = Type.Object({
|
|
raw: NonEmptyString,
|
|
baseHash: Type.Optional(NonEmptyString),
|
|
sessionKey: Type.Optional(Type.String()),
|
|
note: Type.Optional(Type.String()),
|
|
restartDelayMs: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
}, { additionalProperties: false });
|
|
const ConfigSchemaParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
const UpdateRunParamsSchema = Type.Object({
|
|
sessionKey: Type.Optional(Type.String()),
|
|
note: Type.Optional(Type.String()),
|
|
restartDelayMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 }))
|
|
}, { additionalProperties: false });
|
|
const ConfigUiHintSchema = Type.Object({
|
|
label: Type.Optional(Type.String()),
|
|
help: Type.Optional(Type.String()),
|
|
group: Type.Optional(Type.String()),
|
|
order: Type.Optional(Type.Integer()),
|
|
advanced: Type.Optional(Type.Boolean()),
|
|
sensitive: Type.Optional(Type.Boolean()),
|
|
placeholder: Type.Optional(Type.String()),
|
|
itemTemplate: Type.Optional(Type.Unknown())
|
|
}, { additionalProperties: false });
|
|
const ConfigSchemaResponseSchema = Type.Object({
|
|
schema: Type.Unknown(),
|
|
uiHints: Type.Record(Type.String(), ConfigUiHintSchema),
|
|
version: NonEmptyString,
|
|
generatedAt: NonEmptyString
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/cron.ts
|
|
const CronScheduleSchema = Type.Union([
|
|
Type.Object({
|
|
kind: Type.Literal("at"),
|
|
at: NonEmptyString
|
|
}, { additionalProperties: false }),
|
|
Type.Object({
|
|
kind: Type.Literal("every"),
|
|
everyMs: Type.Integer({ minimum: 1 }),
|
|
anchorMs: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
}, { additionalProperties: false }),
|
|
Type.Object({
|
|
kind: Type.Literal("cron"),
|
|
expr: NonEmptyString,
|
|
tz: Type.Optional(Type.String())
|
|
}, { additionalProperties: false })
|
|
]);
|
|
const CronPayloadSchema = Type.Union([Type.Object({
|
|
kind: Type.Literal("systemEvent"),
|
|
text: NonEmptyString
|
|
}, { additionalProperties: false }), Type.Object({
|
|
kind: Type.Literal("agentTurn"),
|
|
message: NonEmptyString,
|
|
model: Type.Optional(Type.String()),
|
|
thinking: Type.Optional(Type.String()),
|
|
timeoutSeconds: Type.Optional(Type.Integer({ minimum: 1 }))
|
|
}, { additionalProperties: false })]);
|
|
const CronPayloadPatchSchema = Type.Union([Type.Object({
|
|
kind: Type.Literal("systemEvent"),
|
|
text: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false }), Type.Object({
|
|
kind: Type.Literal("agentTurn"),
|
|
message: Type.Optional(NonEmptyString),
|
|
model: Type.Optional(Type.String()),
|
|
thinking: Type.Optional(Type.String()),
|
|
timeoutSeconds: Type.Optional(Type.Integer({ minimum: 1 }))
|
|
}, { additionalProperties: false })]);
|
|
const CronDeliverySchema = Type.Object({
|
|
mode: Type.Union([Type.Literal("none"), Type.Literal("announce")]),
|
|
channel: Type.Optional(Type.Union([Type.Literal("last"), NonEmptyString])),
|
|
to: Type.Optional(Type.String()),
|
|
bestEffort: Type.Optional(Type.Boolean())
|
|
}, { additionalProperties: false });
|
|
const CronDeliveryPatchSchema = Type.Object({
|
|
mode: Type.Optional(Type.Union([Type.Literal("none"), Type.Literal("announce")])),
|
|
channel: Type.Optional(Type.Union([Type.Literal("last"), NonEmptyString])),
|
|
to: Type.Optional(Type.String()),
|
|
bestEffort: Type.Optional(Type.Boolean())
|
|
}, { additionalProperties: false });
|
|
const CronJobStateSchema = Type.Object({
|
|
nextRunAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
runningAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
lastRunAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
lastStatus: Type.Optional(Type.Union([
|
|
Type.Literal("ok"),
|
|
Type.Literal("error"),
|
|
Type.Literal("skipped")
|
|
])),
|
|
lastError: Type.Optional(Type.String()),
|
|
lastDurationMs: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
}, { additionalProperties: false });
|
|
const CronJobSchema = Type.Object({
|
|
id: NonEmptyString,
|
|
agentId: Type.Optional(NonEmptyString),
|
|
name: NonEmptyString,
|
|
description: Type.Optional(Type.String()),
|
|
enabled: Type.Boolean(),
|
|
deleteAfterRun: Type.Optional(Type.Boolean()),
|
|
createdAtMs: Type.Integer({ minimum: 0 }),
|
|
updatedAtMs: Type.Integer({ minimum: 0 }),
|
|
schedule: CronScheduleSchema,
|
|
sessionTarget: Type.Union([Type.Literal("main"), Type.Literal("isolated")]),
|
|
wakeMode: Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]),
|
|
payload: CronPayloadSchema,
|
|
delivery: Type.Optional(CronDeliverySchema),
|
|
state: CronJobStateSchema
|
|
}, { additionalProperties: false });
|
|
const CronListParamsSchema = Type.Object({ includeDisabled: Type.Optional(Type.Boolean()) }, { additionalProperties: false });
|
|
const CronStatusParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
const CronAddParamsSchema = Type.Object({
|
|
name: NonEmptyString,
|
|
agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
description: Type.Optional(Type.String()),
|
|
enabled: Type.Optional(Type.Boolean()),
|
|
deleteAfterRun: Type.Optional(Type.Boolean()),
|
|
schedule: CronScheduleSchema,
|
|
sessionTarget: Type.Union([Type.Literal("main"), Type.Literal("isolated")]),
|
|
wakeMode: Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]),
|
|
payload: CronPayloadSchema,
|
|
delivery: Type.Optional(CronDeliverySchema)
|
|
}, { additionalProperties: false });
|
|
const CronJobPatchSchema = Type.Object({
|
|
name: Type.Optional(NonEmptyString),
|
|
agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
description: Type.Optional(Type.String()),
|
|
enabled: Type.Optional(Type.Boolean()),
|
|
deleteAfterRun: Type.Optional(Type.Boolean()),
|
|
schedule: Type.Optional(CronScheduleSchema),
|
|
sessionTarget: Type.Optional(Type.Union([Type.Literal("main"), Type.Literal("isolated")])),
|
|
wakeMode: Type.Optional(Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")])),
|
|
payload: Type.Optional(CronPayloadPatchSchema),
|
|
delivery: Type.Optional(CronDeliveryPatchSchema),
|
|
state: Type.Optional(Type.Partial(CronJobStateSchema))
|
|
}, { additionalProperties: false });
|
|
const CronUpdateParamsSchema = Type.Union([Type.Object({
|
|
id: NonEmptyString,
|
|
patch: CronJobPatchSchema
|
|
}, { additionalProperties: false }), Type.Object({
|
|
jobId: NonEmptyString,
|
|
patch: CronJobPatchSchema
|
|
}, { additionalProperties: false })]);
|
|
const CronRemoveParamsSchema = Type.Union([Type.Object({ id: NonEmptyString }, { additionalProperties: false }), Type.Object({ jobId: NonEmptyString }, { additionalProperties: false })]);
|
|
const CronRunParamsSchema = Type.Union([Type.Object({
|
|
id: NonEmptyString,
|
|
mode: Type.Optional(Type.Union([Type.Literal("due"), Type.Literal("force")]))
|
|
}, { additionalProperties: false }), Type.Object({
|
|
jobId: NonEmptyString,
|
|
mode: Type.Optional(Type.Union([Type.Literal("due"), Type.Literal("force")]))
|
|
}, { additionalProperties: false })]);
|
|
const CronRunsParamsSchema = Type.Union([Type.Object({
|
|
id: NonEmptyString,
|
|
limit: Type.Optional(Type.Integer({
|
|
minimum: 1,
|
|
maximum: 5e3
|
|
}))
|
|
}, { additionalProperties: false }), Type.Object({
|
|
jobId: NonEmptyString,
|
|
limit: Type.Optional(Type.Integer({
|
|
minimum: 1,
|
|
maximum: 5e3
|
|
}))
|
|
}, { additionalProperties: false })]);
|
|
const CronRunLogEntrySchema = Type.Object({
|
|
ts: Type.Integer({ minimum: 0 }),
|
|
jobId: NonEmptyString,
|
|
action: Type.Literal("finished"),
|
|
status: Type.Optional(Type.Union([
|
|
Type.Literal("ok"),
|
|
Type.Literal("error"),
|
|
Type.Literal("skipped")
|
|
])),
|
|
error: Type.Optional(Type.String()),
|
|
summary: Type.Optional(Type.String()),
|
|
runAtMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
durationMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
nextRunAtMs: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/error-codes.ts
|
|
const ErrorCodes = {
|
|
NOT_LINKED: "NOT_LINKED",
|
|
NOT_PAIRED: "NOT_PAIRED",
|
|
AGENT_TIMEOUT: "AGENT_TIMEOUT",
|
|
INVALID_REQUEST: "INVALID_REQUEST",
|
|
UNAVAILABLE: "UNAVAILABLE"
|
|
};
|
|
function errorShape(code, message, opts) {
|
|
return {
|
|
code,
|
|
message,
|
|
...opts
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/exec-approvals.ts
|
|
const ExecApprovalsAllowlistEntrySchema = Type.Object({
|
|
id: Type.Optional(NonEmptyString),
|
|
pattern: Type.String(),
|
|
lastUsedAt: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
lastUsedCommand: Type.Optional(Type.String()),
|
|
lastResolvedPath: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const ExecApprovalsDefaultsSchema = Type.Object({
|
|
security: Type.Optional(Type.String()),
|
|
ask: Type.Optional(Type.String()),
|
|
askFallback: Type.Optional(Type.String()),
|
|
autoAllowSkills: Type.Optional(Type.Boolean())
|
|
}, { additionalProperties: false });
|
|
const ExecApprovalsAgentSchema = Type.Object({
|
|
security: Type.Optional(Type.String()),
|
|
ask: Type.Optional(Type.String()),
|
|
askFallback: Type.Optional(Type.String()),
|
|
autoAllowSkills: Type.Optional(Type.Boolean()),
|
|
allowlist: Type.Optional(Type.Array(ExecApprovalsAllowlistEntrySchema))
|
|
}, { additionalProperties: false });
|
|
const ExecApprovalsFileSchema = Type.Object({
|
|
version: Type.Literal(1),
|
|
socket: Type.Optional(Type.Object({
|
|
path: Type.Optional(Type.String()),
|
|
token: Type.Optional(Type.String())
|
|
}, { additionalProperties: false })),
|
|
defaults: Type.Optional(ExecApprovalsDefaultsSchema),
|
|
agents: Type.Optional(Type.Record(Type.String(), ExecApprovalsAgentSchema))
|
|
}, { additionalProperties: false });
|
|
const ExecApprovalsSnapshotSchema = Type.Object({
|
|
path: NonEmptyString,
|
|
exists: Type.Boolean(),
|
|
hash: NonEmptyString,
|
|
file: ExecApprovalsFileSchema
|
|
}, { additionalProperties: false });
|
|
const ExecApprovalsGetParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
const ExecApprovalsSetParamsSchema = Type.Object({
|
|
file: ExecApprovalsFileSchema,
|
|
baseHash: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false });
|
|
const ExecApprovalsNodeGetParamsSchema = Type.Object({ nodeId: NonEmptyString }, { additionalProperties: false });
|
|
const ExecApprovalsNodeSetParamsSchema = Type.Object({
|
|
nodeId: NonEmptyString,
|
|
file: ExecApprovalsFileSchema,
|
|
baseHash: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false });
|
|
const ExecApprovalRequestParamsSchema = Type.Object({
|
|
id: Type.Optional(NonEmptyString),
|
|
command: NonEmptyString,
|
|
cwd: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
host: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
security: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
ask: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
agentId: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
resolvedPath: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
sessionKey: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 }))
|
|
}, { additionalProperties: false });
|
|
const ExecApprovalResolveParamsSchema = Type.Object({
|
|
id: NonEmptyString,
|
|
decision: NonEmptyString
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/devices.ts
|
|
const DevicePairListParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
const DevicePairApproveParamsSchema = Type.Object({ requestId: NonEmptyString }, { additionalProperties: false });
|
|
const DevicePairRejectParamsSchema = Type.Object({ requestId: NonEmptyString }, { additionalProperties: false });
|
|
const DeviceTokenRotateParamsSchema = Type.Object({
|
|
deviceId: NonEmptyString,
|
|
role: NonEmptyString,
|
|
scopes: Type.Optional(Type.Array(NonEmptyString))
|
|
}, { additionalProperties: false });
|
|
const DeviceTokenRevokeParamsSchema = Type.Object({
|
|
deviceId: NonEmptyString,
|
|
role: NonEmptyString
|
|
}, { additionalProperties: false });
|
|
const DevicePairRequestedEventSchema = Type.Object({
|
|
requestId: NonEmptyString,
|
|
deviceId: NonEmptyString,
|
|
publicKey: NonEmptyString,
|
|
displayName: Type.Optional(NonEmptyString),
|
|
platform: Type.Optional(NonEmptyString),
|
|
clientId: Type.Optional(NonEmptyString),
|
|
clientMode: Type.Optional(NonEmptyString),
|
|
role: Type.Optional(NonEmptyString),
|
|
roles: Type.Optional(Type.Array(NonEmptyString)),
|
|
scopes: Type.Optional(Type.Array(NonEmptyString)),
|
|
remoteIp: Type.Optional(NonEmptyString),
|
|
silent: Type.Optional(Type.Boolean()),
|
|
isRepair: Type.Optional(Type.Boolean()),
|
|
ts: Type.Integer({ minimum: 0 })
|
|
}, { additionalProperties: false });
|
|
const DevicePairResolvedEventSchema = Type.Object({
|
|
requestId: NonEmptyString,
|
|
deviceId: NonEmptyString,
|
|
decision: NonEmptyString,
|
|
ts: Type.Integer({ minimum: 0 })
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/snapshot.ts
|
|
const PresenceEntrySchema = Type.Object({
|
|
host: Type.Optional(NonEmptyString),
|
|
ip: Type.Optional(NonEmptyString),
|
|
version: Type.Optional(NonEmptyString),
|
|
platform: Type.Optional(NonEmptyString),
|
|
deviceFamily: Type.Optional(NonEmptyString),
|
|
modelIdentifier: Type.Optional(NonEmptyString),
|
|
mode: Type.Optional(NonEmptyString),
|
|
lastInputSeconds: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
reason: Type.Optional(NonEmptyString),
|
|
tags: Type.Optional(Type.Array(NonEmptyString)),
|
|
text: Type.Optional(Type.String()),
|
|
ts: Type.Integer({ minimum: 0 }),
|
|
deviceId: Type.Optional(NonEmptyString),
|
|
roles: Type.Optional(Type.Array(NonEmptyString)),
|
|
scopes: Type.Optional(Type.Array(NonEmptyString)),
|
|
instanceId: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false });
|
|
const HealthSnapshotSchema = Type.Any();
|
|
const SessionDefaultsSchema = Type.Object({
|
|
defaultAgentId: NonEmptyString,
|
|
mainKey: NonEmptyString,
|
|
mainSessionKey: NonEmptyString,
|
|
scope: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false });
|
|
const StateVersionSchema = Type.Object({
|
|
presence: Type.Integer({ minimum: 0 }),
|
|
health: Type.Integer({ minimum: 0 })
|
|
}, { additionalProperties: false });
|
|
const SnapshotSchema = Type.Object({
|
|
presence: Type.Array(PresenceEntrySchema),
|
|
health: HealthSnapshotSchema,
|
|
stateVersion: StateVersionSchema,
|
|
uptimeMs: Type.Integer({ minimum: 0 }),
|
|
configPath: Type.Optional(NonEmptyString),
|
|
stateDir: Type.Optional(NonEmptyString),
|
|
sessionDefaults: Type.Optional(SessionDefaultsSchema)
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/frames.ts
|
|
const TickEventSchema = Type.Object({ ts: Type.Integer({ minimum: 0 }) }, { additionalProperties: false });
|
|
const ShutdownEventSchema = Type.Object({
|
|
reason: NonEmptyString,
|
|
restartExpectedMs: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
}, { additionalProperties: false });
|
|
const ConnectParamsSchema = Type.Object({
|
|
minProtocol: Type.Integer({ minimum: 1 }),
|
|
maxProtocol: Type.Integer({ minimum: 1 }),
|
|
client: Type.Object({
|
|
id: GatewayClientIdSchema,
|
|
displayName: Type.Optional(NonEmptyString),
|
|
version: NonEmptyString,
|
|
platform: NonEmptyString,
|
|
deviceFamily: Type.Optional(NonEmptyString),
|
|
modelIdentifier: Type.Optional(NonEmptyString),
|
|
mode: GatewayClientModeSchema,
|
|
instanceId: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false }),
|
|
caps: Type.Optional(Type.Array(NonEmptyString, { default: [] })),
|
|
commands: Type.Optional(Type.Array(NonEmptyString)),
|
|
permissions: Type.Optional(Type.Record(NonEmptyString, Type.Boolean())),
|
|
pathEnv: Type.Optional(Type.String()),
|
|
role: Type.Optional(NonEmptyString),
|
|
scopes: Type.Optional(Type.Array(NonEmptyString)),
|
|
device: Type.Optional(Type.Object({
|
|
id: NonEmptyString,
|
|
publicKey: NonEmptyString,
|
|
signature: NonEmptyString,
|
|
signedAt: Type.Integer({ minimum: 0 }),
|
|
nonce: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false })),
|
|
auth: Type.Optional(Type.Object({
|
|
token: Type.Optional(Type.String()),
|
|
password: Type.Optional(Type.String())
|
|
}, { additionalProperties: false })),
|
|
locale: Type.Optional(Type.String()),
|
|
userAgent: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const HelloOkSchema = Type.Object({
|
|
type: Type.Literal("hello-ok"),
|
|
protocol: Type.Integer({ minimum: 1 }),
|
|
server: Type.Object({
|
|
version: NonEmptyString,
|
|
commit: Type.Optional(NonEmptyString),
|
|
host: Type.Optional(NonEmptyString),
|
|
connId: NonEmptyString
|
|
}, { additionalProperties: false }),
|
|
features: Type.Object({
|
|
methods: Type.Array(NonEmptyString),
|
|
events: Type.Array(NonEmptyString)
|
|
}, { additionalProperties: false }),
|
|
snapshot: SnapshotSchema,
|
|
canvasHostUrl: Type.Optional(NonEmptyString),
|
|
auth: Type.Optional(Type.Object({
|
|
deviceToken: NonEmptyString,
|
|
role: NonEmptyString,
|
|
scopes: Type.Array(NonEmptyString),
|
|
issuedAtMs: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
}, { additionalProperties: false })),
|
|
policy: Type.Object({
|
|
maxPayload: Type.Integer({ minimum: 1 }),
|
|
maxBufferedBytes: Type.Integer({ minimum: 1 }),
|
|
tickIntervalMs: Type.Integer({ minimum: 1 })
|
|
}, { additionalProperties: false })
|
|
}, { additionalProperties: false });
|
|
const ErrorShapeSchema = Type.Object({
|
|
code: NonEmptyString,
|
|
message: NonEmptyString,
|
|
details: Type.Optional(Type.Unknown()),
|
|
retryable: Type.Optional(Type.Boolean()),
|
|
retryAfterMs: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
}, { additionalProperties: false });
|
|
const RequestFrameSchema = Type.Object({
|
|
type: Type.Literal("req"),
|
|
id: NonEmptyString,
|
|
method: NonEmptyString,
|
|
params: Type.Optional(Type.Unknown())
|
|
}, { additionalProperties: false });
|
|
const ResponseFrameSchema = Type.Object({
|
|
type: Type.Literal("res"),
|
|
id: NonEmptyString,
|
|
ok: Type.Boolean(),
|
|
payload: Type.Optional(Type.Unknown()),
|
|
error: Type.Optional(ErrorShapeSchema)
|
|
}, { additionalProperties: false });
|
|
const EventFrameSchema = Type.Object({
|
|
type: Type.Literal("event"),
|
|
event: NonEmptyString,
|
|
payload: Type.Optional(Type.Unknown()),
|
|
seq: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
stateVersion: Type.Optional(StateVersionSchema)
|
|
}, { additionalProperties: false });
|
|
const GatewayFrameSchema = Type.Union([
|
|
RequestFrameSchema,
|
|
ResponseFrameSchema,
|
|
EventFrameSchema
|
|
], { discriminator: "type" });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/logs-chat.ts
|
|
const LogsTailParamsSchema = Type.Object({
|
|
cursor: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
limit: Type.Optional(Type.Integer({
|
|
minimum: 1,
|
|
maximum: 5e3
|
|
})),
|
|
maxBytes: Type.Optional(Type.Integer({
|
|
minimum: 1,
|
|
maximum: 1e6
|
|
}))
|
|
}, { additionalProperties: false });
|
|
const LogsTailResultSchema = Type.Object({
|
|
file: NonEmptyString,
|
|
cursor: Type.Integer({ minimum: 0 }),
|
|
size: Type.Integer({ minimum: 0 }),
|
|
lines: Type.Array(Type.String()),
|
|
truncated: Type.Optional(Type.Boolean()),
|
|
reset: Type.Optional(Type.Boolean())
|
|
}, { additionalProperties: false });
|
|
const ChatHistoryParamsSchema = Type.Object({
|
|
sessionKey: NonEmptyString,
|
|
limit: Type.Optional(Type.Integer({
|
|
minimum: 1,
|
|
maximum: 1e3
|
|
}))
|
|
}, { additionalProperties: false });
|
|
const ChatSendParamsSchema = Type.Object({
|
|
sessionKey: NonEmptyString,
|
|
message: Type.String(),
|
|
thinking: Type.Optional(Type.String()),
|
|
deliver: Type.Optional(Type.Boolean()),
|
|
attachments: Type.Optional(Type.Array(Type.Unknown())),
|
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
idempotencyKey: NonEmptyString
|
|
}, { additionalProperties: false });
|
|
const ChatAbortParamsSchema = Type.Object({
|
|
sessionKey: NonEmptyString,
|
|
runId: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false });
|
|
const ChatInjectParamsSchema = Type.Object({
|
|
sessionKey: NonEmptyString,
|
|
message: NonEmptyString,
|
|
label: Type.Optional(Type.String({ maxLength: 100 }))
|
|
}, { additionalProperties: false });
|
|
const ChatEventSchema = Type.Object({
|
|
runId: NonEmptyString,
|
|
sessionKey: NonEmptyString,
|
|
seq: Type.Integer({ minimum: 0 }),
|
|
state: Type.Union([
|
|
Type.Literal("delta"),
|
|
Type.Literal("final"),
|
|
Type.Literal("aborted"),
|
|
Type.Literal("error")
|
|
]),
|
|
message: Type.Optional(Type.Unknown()),
|
|
errorMessage: Type.Optional(Type.String()),
|
|
usage: Type.Optional(Type.Unknown()),
|
|
stopReason: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/nodes.ts
|
|
const NodePairRequestParamsSchema = Type.Object({
|
|
nodeId: NonEmptyString,
|
|
displayName: Type.Optional(NonEmptyString),
|
|
platform: Type.Optional(NonEmptyString),
|
|
version: Type.Optional(NonEmptyString),
|
|
coreVersion: Type.Optional(NonEmptyString),
|
|
uiVersion: Type.Optional(NonEmptyString),
|
|
deviceFamily: Type.Optional(NonEmptyString),
|
|
modelIdentifier: Type.Optional(NonEmptyString),
|
|
caps: Type.Optional(Type.Array(NonEmptyString)),
|
|
commands: Type.Optional(Type.Array(NonEmptyString)),
|
|
remoteIp: Type.Optional(NonEmptyString),
|
|
silent: Type.Optional(Type.Boolean())
|
|
}, { additionalProperties: false });
|
|
const NodePairListParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
const NodePairApproveParamsSchema = Type.Object({ requestId: NonEmptyString }, { additionalProperties: false });
|
|
const NodePairRejectParamsSchema = Type.Object({ requestId: NonEmptyString }, { additionalProperties: false });
|
|
const NodePairVerifyParamsSchema = Type.Object({
|
|
nodeId: NonEmptyString,
|
|
token: NonEmptyString
|
|
}, { additionalProperties: false });
|
|
const NodeRenameParamsSchema = Type.Object({
|
|
nodeId: NonEmptyString,
|
|
displayName: NonEmptyString
|
|
}, { additionalProperties: false });
|
|
const NodeListParamsSchema = Type.Object({}, { additionalProperties: false });
|
|
const NodeDescribeParamsSchema = Type.Object({ nodeId: NonEmptyString }, { additionalProperties: false });
|
|
const NodeInvokeParamsSchema = Type.Object({
|
|
nodeId: NonEmptyString,
|
|
command: NonEmptyString,
|
|
params: Type.Optional(Type.Unknown()),
|
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
idempotencyKey: NonEmptyString
|
|
}, { additionalProperties: false });
|
|
const NodeInvokeResultParamsSchema = Type.Object({
|
|
id: NonEmptyString,
|
|
nodeId: NonEmptyString,
|
|
ok: Type.Boolean(),
|
|
payload: Type.Optional(Type.Unknown()),
|
|
payloadJSON: Type.Optional(Type.String()),
|
|
error: Type.Optional(Type.Object({
|
|
code: Type.Optional(NonEmptyString),
|
|
message: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false }))
|
|
}, { additionalProperties: false });
|
|
const NodeEventParamsSchema = Type.Object({
|
|
event: NonEmptyString,
|
|
payload: Type.Optional(Type.Unknown()),
|
|
payloadJSON: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const NodeInvokeRequestEventSchema = Type.Object({
|
|
id: NonEmptyString,
|
|
nodeId: NonEmptyString,
|
|
command: NonEmptyString,
|
|
paramsJSON: Type.Optional(Type.String()),
|
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
idempotencyKey: Type.Optional(NonEmptyString)
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/sessions.ts
|
|
const SessionsListParamsSchema = Type.Object({
|
|
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
activeMinutes: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
includeGlobal: Type.Optional(Type.Boolean()),
|
|
includeUnknown: Type.Optional(Type.Boolean()),
|
|
includeDerivedTitles: Type.Optional(Type.Boolean()),
|
|
includeLastMessage: Type.Optional(Type.Boolean()),
|
|
label: Type.Optional(SessionLabelString),
|
|
spawnedBy: Type.Optional(NonEmptyString),
|
|
agentId: Type.Optional(NonEmptyString),
|
|
search: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const SessionsPreviewParamsSchema = Type.Object({
|
|
keys: Type.Array(NonEmptyString, { minItems: 1 }),
|
|
limit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
maxChars: Type.Optional(Type.Integer({ minimum: 20 }))
|
|
}, { additionalProperties: false });
|
|
const SessionsResolveParamsSchema = Type.Object({
|
|
key: Type.Optional(NonEmptyString),
|
|
sessionId: Type.Optional(NonEmptyString),
|
|
label: Type.Optional(SessionLabelString),
|
|
agentId: Type.Optional(NonEmptyString),
|
|
spawnedBy: Type.Optional(NonEmptyString),
|
|
includeGlobal: Type.Optional(Type.Boolean()),
|
|
includeUnknown: Type.Optional(Type.Boolean())
|
|
}, { additionalProperties: false });
|
|
const SessionsPatchParamsSchema = Type.Object({
|
|
key: NonEmptyString,
|
|
label: Type.Optional(Type.Union([SessionLabelString, Type.Null()])),
|
|
thinkingLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
verboseLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
reasoningLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
responseUsage: Type.Optional(Type.Union([
|
|
Type.Literal("off"),
|
|
Type.Literal("tokens"),
|
|
Type.Literal("full"),
|
|
Type.Literal("on"),
|
|
Type.Null()
|
|
])),
|
|
elevatedLevel: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
execHost: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
execSecurity: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
execAsk: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
execNode: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
model: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
spawnedBy: Type.Optional(Type.Union([NonEmptyString, Type.Null()])),
|
|
sendPolicy: Type.Optional(Type.Union([
|
|
Type.Literal("allow"),
|
|
Type.Literal("deny"),
|
|
Type.Null()
|
|
])),
|
|
groupActivation: Type.Optional(Type.Union([
|
|
Type.Literal("mention"),
|
|
Type.Literal("always"),
|
|
Type.Null()
|
|
]))
|
|
}, { additionalProperties: false });
|
|
const SessionsResetParamsSchema = Type.Object({ key: NonEmptyString }, { additionalProperties: false });
|
|
const SessionsDeleteParamsSchema = Type.Object({
|
|
key: NonEmptyString,
|
|
deleteTranscript: Type.Optional(Type.Boolean())
|
|
}, { additionalProperties: false });
|
|
const SessionsCompactParamsSchema = Type.Object({
|
|
key: NonEmptyString,
|
|
maxLines: Type.Optional(Type.Integer({ minimum: 1 }))
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/wizard.ts
|
|
const WizardStartParamsSchema = Type.Object({
|
|
mode: Type.Optional(Type.Union([Type.Literal("local"), Type.Literal("remote")])),
|
|
workspace: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const WizardAnswerSchema = Type.Object({
|
|
stepId: NonEmptyString,
|
|
value: Type.Optional(Type.Unknown())
|
|
}, { additionalProperties: false });
|
|
const WizardNextParamsSchema = Type.Object({
|
|
sessionId: NonEmptyString,
|
|
answer: Type.Optional(WizardAnswerSchema)
|
|
}, { additionalProperties: false });
|
|
const WizardCancelParamsSchema = Type.Object({ sessionId: NonEmptyString }, { additionalProperties: false });
|
|
const WizardStatusParamsSchema = Type.Object({ sessionId: NonEmptyString }, { additionalProperties: false });
|
|
const WizardStepOptionSchema = Type.Object({
|
|
value: Type.Unknown(),
|
|
label: NonEmptyString,
|
|
hint: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const WizardStepSchema = Type.Object({
|
|
id: NonEmptyString,
|
|
type: Type.Union([
|
|
Type.Literal("note"),
|
|
Type.Literal("select"),
|
|
Type.Literal("text"),
|
|
Type.Literal("confirm"),
|
|
Type.Literal("multiselect"),
|
|
Type.Literal("progress"),
|
|
Type.Literal("action")
|
|
]),
|
|
title: Type.Optional(Type.String()),
|
|
message: Type.Optional(Type.String()),
|
|
options: Type.Optional(Type.Array(WizardStepOptionSchema)),
|
|
initialValue: Type.Optional(Type.Unknown()),
|
|
placeholder: Type.Optional(Type.String()),
|
|
sensitive: Type.Optional(Type.Boolean()),
|
|
executor: Type.Optional(Type.Union([Type.Literal("gateway"), Type.Literal("client")]))
|
|
}, { additionalProperties: false });
|
|
const WizardNextResultSchema = Type.Object({
|
|
done: Type.Boolean(),
|
|
step: Type.Optional(WizardStepSchema),
|
|
status: Type.Optional(Type.Union([
|
|
Type.Literal("running"),
|
|
Type.Literal("done"),
|
|
Type.Literal("cancelled"),
|
|
Type.Literal("error")
|
|
])),
|
|
error: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const WizardStartResultSchema = Type.Object({
|
|
sessionId: NonEmptyString,
|
|
done: Type.Boolean(),
|
|
step: Type.Optional(WizardStepSchema),
|
|
status: Type.Optional(Type.Union([
|
|
Type.Literal("running"),
|
|
Type.Literal("done"),
|
|
Type.Literal("cancelled"),
|
|
Type.Literal("error")
|
|
])),
|
|
error: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
const WizardStatusResultSchema = Type.Object({
|
|
status: Type.Union([
|
|
Type.Literal("running"),
|
|
Type.Literal("done"),
|
|
Type.Literal("cancelled"),
|
|
Type.Literal("error")
|
|
]),
|
|
error: Type.Optional(Type.String())
|
|
}, { additionalProperties: false });
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/schema/protocol-schemas.ts
|
|
const PROTOCOL_VERSION = 3;
|
|
|
|
//#endregion
|
|
//#region src/gateway/protocol/index.ts
|
|
const ajv = new AjvPkg({
|
|
allErrors: true,
|
|
strict: false,
|
|
removeAdditional: false
|
|
});
|
|
const validateConnectParams = ajv.compile(ConnectParamsSchema);
|
|
const validateRequestFrame = ajv.compile(RequestFrameSchema);
|
|
const validateResponseFrame = ajv.compile(ResponseFrameSchema);
|
|
const validateEventFrame = ajv.compile(EventFrameSchema);
|
|
const validateSendParams = ajv.compile(SendParamsSchema);
|
|
const validatePollParams = ajv.compile(PollParamsSchema);
|
|
const validateAgentParams = ajv.compile(AgentParamsSchema);
|
|
const validateAgentIdentityParams = ajv.compile(AgentIdentityParamsSchema);
|
|
const validateAgentWaitParams = ajv.compile(AgentWaitParamsSchema);
|
|
const validateWakeParams = ajv.compile(WakeParamsSchema);
|
|
const validateAgentsListParams = ajv.compile(AgentsListParamsSchema);
|
|
const validateAgentsFilesListParams = ajv.compile(AgentsFilesListParamsSchema);
|
|
const validateAgentsFilesGetParams = ajv.compile(AgentsFilesGetParamsSchema);
|
|
const validateAgentsFilesSetParams = ajv.compile(AgentsFilesSetParamsSchema);
|
|
const validateNodePairRequestParams = ajv.compile(NodePairRequestParamsSchema);
|
|
const validateNodePairListParams = ajv.compile(NodePairListParamsSchema);
|
|
const validateNodePairApproveParams = ajv.compile(NodePairApproveParamsSchema);
|
|
const validateNodePairRejectParams = ajv.compile(NodePairRejectParamsSchema);
|
|
const validateNodePairVerifyParams = ajv.compile(NodePairVerifyParamsSchema);
|
|
const validateNodeRenameParams = ajv.compile(NodeRenameParamsSchema);
|
|
const validateNodeListParams = ajv.compile(NodeListParamsSchema);
|
|
const validateNodeDescribeParams = ajv.compile(NodeDescribeParamsSchema);
|
|
const validateNodeInvokeParams = ajv.compile(NodeInvokeParamsSchema);
|
|
const validateNodeInvokeResultParams = ajv.compile(NodeInvokeResultParamsSchema);
|
|
const validateNodeEventParams = ajv.compile(NodeEventParamsSchema);
|
|
const validateSessionsListParams = ajv.compile(SessionsListParamsSchema);
|
|
const validateSessionsPreviewParams = ajv.compile(SessionsPreviewParamsSchema);
|
|
const validateSessionsResolveParams = ajv.compile(SessionsResolveParamsSchema);
|
|
const validateSessionsPatchParams = ajv.compile(SessionsPatchParamsSchema);
|
|
const validateSessionsResetParams = ajv.compile(SessionsResetParamsSchema);
|
|
const validateSessionsDeleteParams = ajv.compile(SessionsDeleteParamsSchema);
|
|
const validateSessionsCompactParams = ajv.compile(SessionsCompactParamsSchema);
|
|
const validateConfigGetParams = ajv.compile(ConfigGetParamsSchema);
|
|
const validateConfigSetParams = ajv.compile(ConfigSetParamsSchema);
|
|
const validateConfigApplyParams = ajv.compile(ConfigApplyParamsSchema);
|
|
const validateConfigPatchParams = ajv.compile(ConfigPatchParamsSchema);
|
|
const validateConfigSchemaParams = ajv.compile(ConfigSchemaParamsSchema);
|
|
const validateWizardStartParams = ajv.compile(WizardStartParamsSchema);
|
|
const validateWizardNextParams = ajv.compile(WizardNextParamsSchema);
|
|
const validateWizardCancelParams = ajv.compile(WizardCancelParamsSchema);
|
|
const validateWizardStatusParams = ajv.compile(WizardStatusParamsSchema);
|
|
const validateTalkModeParams = ajv.compile(TalkModeParamsSchema);
|
|
const validateChannelsStatusParams = ajv.compile(ChannelsStatusParamsSchema);
|
|
const validateChannelsLogoutParams = ajv.compile(ChannelsLogoutParamsSchema);
|
|
const validateModelsListParams = ajv.compile(ModelsListParamsSchema);
|
|
const validateSkillsStatusParams = ajv.compile(SkillsStatusParamsSchema);
|
|
const validateSkillsBinsParams = ajv.compile(SkillsBinsParamsSchema);
|
|
const validateSkillsInstallParams = ajv.compile(SkillsInstallParamsSchema);
|
|
const validateSkillsUpdateParams = ajv.compile(SkillsUpdateParamsSchema);
|
|
const validateCronListParams = ajv.compile(CronListParamsSchema);
|
|
const validateCronStatusParams = ajv.compile(CronStatusParamsSchema);
|
|
const validateCronAddParams = ajv.compile(CronAddParamsSchema);
|
|
const validateCronUpdateParams = ajv.compile(CronUpdateParamsSchema);
|
|
const validateCronRemoveParams = ajv.compile(CronRemoveParamsSchema);
|
|
const validateCronRunParams = ajv.compile(CronRunParamsSchema);
|
|
const validateCronRunsParams = ajv.compile(CronRunsParamsSchema);
|
|
const validateDevicePairListParams = ajv.compile(DevicePairListParamsSchema);
|
|
const validateDevicePairApproveParams = ajv.compile(DevicePairApproveParamsSchema);
|
|
const validateDevicePairRejectParams = ajv.compile(DevicePairRejectParamsSchema);
|
|
const validateDeviceTokenRotateParams = ajv.compile(DeviceTokenRotateParamsSchema);
|
|
const validateDeviceTokenRevokeParams = ajv.compile(DeviceTokenRevokeParamsSchema);
|
|
const validateExecApprovalsGetParams = ajv.compile(ExecApprovalsGetParamsSchema);
|
|
const validateExecApprovalsSetParams = ajv.compile(ExecApprovalsSetParamsSchema);
|
|
const validateExecApprovalRequestParams = ajv.compile(ExecApprovalRequestParamsSchema);
|
|
const validateExecApprovalResolveParams = ajv.compile(ExecApprovalResolveParamsSchema);
|
|
const validateExecApprovalsNodeGetParams = ajv.compile(ExecApprovalsNodeGetParamsSchema);
|
|
const validateExecApprovalsNodeSetParams = ajv.compile(ExecApprovalsNodeSetParamsSchema);
|
|
const validateLogsTailParams = ajv.compile(LogsTailParamsSchema);
|
|
const validateChatHistoryParams = ajv.compile(ChatHistoryParamsSchema);
|
|
const validateChatSendParams = ajv.compile(ChatSendParamsSchema);
|
|
const validateChatAbortParams = ajv.compile(ChatAbortParamsSchema);
|
|
const validateChatInjectParams = ajv.compile(ChatInjectParamsSchema);
|
|
const validateChatEvent = ajv.compile(ChatEventSchema);
|
|
const validateUpdateRunParams = ajv.compile(UpdateRunParamsSchema);
|
|
const validateWebLoginStartParams = ajv.compile(WebLoginStartParamsSchema);
|
|
const validateWebLoginWaitParams = ajv.compile(WebLoginWaitParamsSchema);
|
|
function formatValidationErrors(errors) {
|
|
if (!errors?.length) return "unknown validation error";
|
|
const parts = [];
|
|
for (const err of errors) {
|
|
const keyword = typeof err?.keyword === "string" ? err.keyword : "";
|
|
const instancePath = typeof err?.instancePath === "string" ? err.instancePath : "";
|
|
if (keyword === "additionalProperties") {
|
|
const additionalProperty = (err?.params)?.additionalProperty;
|
|
if (typeof additionalProperty === "string" && additionalProperty.trim()) {
|
|
const where = instancePath ? `at ${instancePath}` : "at root";
|
|
parts.push(`${where}: unexpected property '${additionalProperty}'`);
|
|
continue;
|
|
}
|
|
}
|
|
const message = typeof err?.message === "string" && err.message.trim() ? err.message : "validation error";
|
|
const where = instancePath ? `at ${instancePath}: ` : "";
|
|
parts.push(`${where}${message}`);
|
|
}
|
|
const unique = Array.from(new Set(parts.filter((part) => part.trim())));
|
|
if (!unique.length) return ajv.errorsText(errors, { separator: "; " }) || "unknown validation error";
|
|
return unique.join("; ");
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/gateway/client.ts
|
|
var GatewayClient = class {
|
|
constructor(opts) {
|
|
this.ws = null;
|
|
this.pending = /* @__PURE__ */ new Map();
|
|
this.backoffMs = 1e3;
|
|
this.closed = false;
|
|
this.lastSeq = null;
|
|
this.connectNonce = null;
|
|
this.connectSent = false;
|
|
this.connectTimer = null;
|
|
this.lastTick = null;
|
|
this.tickIntervalMs = 3e4;
|
|
this.tickTimer = null;
|
|
this.opts = {
|
|
...opts,
|
|
deviceIdentity: opts.deviceIdentity ?? loadOrCreateDeviceIdentity()
|
|
};
|
|
}
|
|
start() {
|
|
if (this.closed) return;
|
|
const url = this.opts.url ?? "ws://127.0.0.1:18789";
|
|
if (this.opts.tlsFingerprint && !url.startsWith("wss://")) {
|
|
this.opts.onConnectError?.(/* @__PURE__ */ new Error("gateway tls fingerprint requires wss:// gateway url"));
|
|
return;
|
|
}
|
|
const wsOptions = { maxPayload: 25 * 1024 * 1024 };
|
|
if (url.startsWith("wss://") && this.opts.tlsFingerprint) {
|
|
wsOptions.rejectUnauthorized = false;
|
|
wsOptions.checkServerIdentity = ((_host, cert) => {
|
|
const fingerprintValue = typeof cert === "object" && cert && "fingerprint256" in cert ? cert.fingerprint256 ?? "" : "";
|
|
const fingerprint = normalizeFingerprint(typeof fingerprintValue === "string" ? fingerprintValue : "");
|
|
const expected = normalizeFingerprint(this.opts.tlsFingerprint ?? "");
|
|
if (!expected) return /* @__PURE__ */ new Error("gateway tls fingerprint missing");
|
|
if (!fingerprint) return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
|
|
if (fingerprint !== expected) return /* @__PURE__ */ new Error("gateway tls fingerprint mismatch");
|
|
});
|
|
}
|
|
this.ws = new WebSocket$1(url, wsOptions);
|
|
this.ws.on("open", () => {
|
|
if (url.startsWith("wss://") && this.opts.tlsFingerprint) {
|
|
const tlsError = this.validateTlsFingerprint();
|
|
if (tlsError) {
|
|
this.opts.onConnectError?.(tlsError);
|
|
this.ws?.close(1008, tlsError.message);
|
|
return;
|
|
}
|
|
}
|
|
this.queueConnect();
|
|
});
|
|
this.ws.on("message", (data) => this.handleMessage(rawDataToString(data)));
|
|
this.ws.on("close", (code, reason) => {
|
|
const reasonText = rawDataToString(reason);
|
|
this.ws = null;
|
|
this.flushPendingErrors(/* @__PURE__ */ new Error(`gateway closed (${code}): ${reasonText}`));
|
|
this.scheduleReconnect();
|
|
this.opts.onClose?.(code, reasonText);
|
|
});
|
|
this.ws.on("error", (err) => {
|
|
logDebug(`gateway client error: ${String(err)}`);
|
|
if (!this.connectSent) this.opts.onConnectError?.(err instanceof Error ? err : new Error(String(err)));
|
|
});
|
|
}
|
|
stop() {
|
|
this.closed = true;
|
|
if (this.tickTimer) {
|
|
clearInterval(this.tickTimer);
|
|
this.tickTimer = null;
|
|
}
|
|
this.ws?.close();
|
|
this.ws = null;
|
|
this.flushPendingErrors(/* @__PURE__ */ new Error("gateway client stopped"));
|
|
}
|
|
sendConnect() {
|
|
if (this.connectSent) return;
|
|
this.connectSent = true;
|
|
if (this.connectTimer) {
|
|
clearTimeout(this.connectTimer);
|
|
this.connectTimer = null;
|
|
}
|
|
const role = this.opts.role ?? "operator";
|
|
const storedToken = this.opts.deviceIdentity ? loadDeviceAuthToken({
|
|
deviceId: this.opts.deviceIdentity.deviceId,
|
|
role
|
|
})?.token : null;
|
|
const authToken = storedToken ?? this.opts.token ?? void 0;
|
|
const canFallbackToShared = Boolean(storedToken && this.opts.token);
|
|
const auth = authToken || this.opts.password ? {
|
|
token: authToken,
|
|
password: this.opts.password
|
|
} : void 0;
|
|
const signedAtMs = Date.now();
|
|
const nonce = this.connectNonce ?? void 0;
|
|
const scopes = this.opts.scopes ?? ["operator.admin"];
|
|
const device = (() => {
|
|
if (!this.opts.deviceIdentity) return;
|
|
const payload = buildDeviceAuthPayload({
|
|
deviceId: this.opts.deviceIdentity.deviceId,
|
|
clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
|
|
clientMode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND,
|
|
role,
|
|
scopes,
|
|
signedAtMs,
|
|
token: authToken ?? null,
|
|
nonce
|
|
});
|
|
const signature = signDevicePayload(this.opts.deviceIdentity.privateKeyPem, payload);
|
|
return {
|
|
id: this.opts.deviceIdentity.deviceId,
|
|
publicKey: publicKeyRawBase64UrlFromPem(this.opts.deviceIdentity.publicKeyPem),
|
|
signature,
|
|
signedAt: signedAtMs,
|
|
nonce
|
|
};
|
|
})();
|
|
const params = {
|
|
minProtocol: this.opts.minProtocol ?? PROTOCOL_VERSION,
|
|
maxProtocol: this.opts.maxProtocol ?? PROTOCOL_VERSION,
|
|
client: {
|
|
id: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
|
|
displayName: this.opts.clientDisplayName,
|
|
version: this.opts.clientVersion ?? "dev",
|
|
platform: this.opts.platform ?? process.platform,
|
|
mode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND,
|
|
instanceId: this.opts.instanceId
|
|
},
|
|
caps: Array.isArray(this.opts.caps) ? this.opts.caps : [],
|
|
commands: Array.isArray(this.opts.commands) ? this.opts.commands : void 0,
|
|
permissions: this.opts.permissions && typeof this.opts.permissions === "object" ? this.opts.permissions : void 0,
|
|
pathEnv: this.opts.pathEnv,
|
|
auth,
|
|
role,
|
|
scopes,
|
|
device
|
|
};
|
|
this.request("connect", params).then((helloOk) => {
|
|
const authInfo = helloOk?.auth;
|
|
if (authInfo?.deviceToken && this.opts.deviceIdentity) storeDeviceAuthToken({
|
|
deviceId: this.opts.deviceIdentity.deviceId,
|
|
role: authInfo.role ?? role,
|
|
token: authInfo.deviceToken,
|
|
scopes: authInfo.scopes ?? []
|
|
});
|
|
this.backoffMs = 1e3;
|
|
this.tickIntervalMs = typeof helloOk.policy?.tickIntervalMs === "number" ? helloOk.policy.tickIntervalMs : 3e4;
|
|
this.lastTick = Date.now();
|
|
this.startTickWatch();
|
|
this.opts.onHelloOk?.(helloOk);
|
|
}).catch((err) => {
|
|
if (canFallbackToShared && this.opts.deviceIdentity) clearDeviceAuthToken({
|
|
deviceId: this.opts.deviceIdentity.deviceId,
|
|
role
|
|
});
|
|
this.opts.onConnectError?.(err instanceof Error ? err : new Error(String(err)));
|
|
const msg = `gateway connect failed: ${String(err)}`;
|
|
if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE) logDebug(msg);
|
|
else logError(msg);
|
|
this.ws?.close(1008, "connect failed");
|
|
});
|
|
}
|
|
handleMessage(raw) {
|
|
try {
|
|
const parsed = JSON.parse(raw);
|
|
if (validateEventFrame(parsed)) {
|
|
const evt = parsed;
|
|
if (evt.event === "connect.challenge") {
|
|
const payload = evt.payload;
|
|
const nonce = payload && typeof payload.nonce === "string" ? payload.nonce : null;
|
|
if (nonce) {
|
|
this.connectNonce = nonce;
|
|
this.sendConnect();
|
|
}
|
|
return;
|
|
}
|
|
const seq = typeof evt.seq === "number" ? evt.seq : null;
|
|
if (seq !== null) {
|
|
if (this.lastSeq !== null && seq > this.lastSeq + 1) this.opts.onGap?.({
|
|
expected: this.lastSeq + 1,
|
|
received: seq
|
|
});
|
|
this.lastSeq = seq;
|
|
}
|
|
if (evt.event === "tick") this.lastTick = Date.now();
|
|
this.opts.onEvent?.(evt);
|
|
return;
|
|
}
|
|
if (validateResponseFrame(parsed)) {
|
|
const pending = this.pending.get(parsed.id);
|
|
if (!pending) return;
|
|
const status = parsed.payload?.status;
|
|
if (pending.expectFinal && status === "accepted") return;
|
|
this.pending.delete(parsed.id);
|
|
if (parsed.ok) pending.resolve(parsed.payload);
|
|
else pending.reject(new Error(parsed.error?.message ?? "unknown error"));
|
|
}
|
|
} catch (err) {
|
|
logDebug(`gateway client parse error: ${String(err)}`);
|
|
}
|
|
}
|
|
queueConnect() {
|
|
this.connectNonce = null;
|
|
this.connectSent = false;
|
|
if (this.connectTimer) clearTimeout(this.connectTimer);
|
|
this.connectTimer = setTimeout(() => {
|
|
this.sendConnect();
|
|
}, 750);
|
|
}
|
|
scheduleReconnect() {
|
|
if (this.closed) return;
|
|
if (this.tickTimer) {
|
|
clearInterval(this.tickTimer);
|
|
this.tickTimer = null;
|
|
}
|
|
const delay = this.backoffMs;
|
|
this.backoffMs = Math.min(this.backoffMs * 2, 3e4);
|
|
setTimeout(() => this.start(), delay).unref();
|
|
}
|
|
flushPendingErrors(err) {
|
|
for (const [, p] of this.pending) p.reject(err);
|
|
this.pending.clear();
|
|
}
|
|
startTickWatch() {
|
|
if (this.tickTimer) clearInterval(this.tickTimer);
|
|
const interval = Math.max(this.tickIntervalMs, 1e3);
|
|
this.tickTimer = setInterval(() => {
|
|
if (this.closed) return;
|
|
if (!this.lastTick) return;
|
|
if (Date.now() - this.lastTick > this.tickIntervalMs * 2) this.ws?.close(4e3, "tick timeout");
|
|
}, interval);
|
|
}
|
|
validateTlsFingerprint() {
|
|
if (!this.opts.tlsFingerprint || !this.ws) return null;
|
|
const expected = normalizeFingerprint(this.opts.tlsFingerprint);
|
|
if (!expected) return /* @__PURE__ */ new Error("gateway tls fingerprint missing");
|
|
const socket = this.ws._socket;
|
|
if (!socket || typeof socket.getPeerCertificate !== "function") return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
|
|
const fingerprint = normalizeFingerprint(socket.getPeerCertificate()?.fingerprint256 ?? "");
|
|
if (!fingerprint) return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
|
|
if (fingerprint !== expected) return /* @__PURE__ */ new Error("gateway tls fingerprint mismatch");
|
|
return null;
|
|
}
|
|
async request(method, params, opts) {
|
|
if (!this.ws || this.ws.readyState !== WebSocket$1.OPEN) throw new Error("gateway not connected");
|
|
const id = randomUUID();
|
|
const frame = {
|
|
type: "req",
|
|
id,
|
|
method,
|
|
params
|
|
};
|
|
if (!validateRequestFrame(frame)) throw new Error(`invalid request frame: ${JSON.stringify(validateRequestFrame.errors, null, 2)}`);
|
|
const expectFinal = opts?.expectFinal === true;
|
|
const p = new Promise((resolve, reject) => {
|
|
this.pending.set(id, {
|
|
resolve: (value) => resolve(value),
|
|
reject,
|
|
expectFinal
|
|
});
|
|
});
|
|
this.ws.send(JSON.stringify(frame));
|
|
return p;
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
export { validatePollParams as $, validateDevicePairListParams as A, loadOrCreateDeviceIdentity as At, validateLogsTailParams as B, validateCronListParams as C, ErrorCodes as Ct, validateCronStatusParams as D, buildDeviceAuthPayload as Dt, validateCronRunsParams as E, parseSessionLabel as Et, validateExecApprovalResolveParams as F, validateNodeInvokeResultParams as G, validateNodeDescribeParams as H, validateExecApprovalsGetParams as I, validateNodePairListParams as J, validateNodeListParams as K, validateExecApprovalsNodeGetParams as L, validateDeviceTokenRevokeParams as M, verifyDeviceSignature as Mt, validateDeviceTokenRotateParams as N, validateCronUpdateParams as O, normalizeFingerprint as Ot, validateExecApprovalRequestParams as P, validateNodeRenameParams as Q, validateExecApprovalsNodeSetParams as R, validateCronAddParams as S, PROTOCOL_VERSION as St, validateCronRunParams as T, SESSION_LABEL_MAX_LENGTH as Tt, validateNodeEventParams as U, validateModelsListParams as V, validateNodeInvokeParams as W, validateNodePairRequestParams as X, validateNodePairRejectParams as Y, validateNodePairVerifyParams as Z, validateConfigGetParams as _, validateWebLoginWaitParams as _t, validateAgentWaitParams as a, validateSessionsPatchParams as at, validateConfigSetParams as b, validateWizardStartParams as bt, validateAgentsFilesSetParams as c, validateSessionsResolveParams as ct, validateChannelsStatusParams as d, validateSkillsStatusParams as dt, validateRequestFrame as et, validateChatAbortParams as f, validateSkillsUpdateParams as ft, validateConfigApplyParams as g, validateWebLoginStartParams as gt, validateChatSendParams as h, validateWakeParams as ht, validateAgentParams as i, validateSessionsListParams as it, validateDevicePairRejectParams as j, normalizeDevicePublicKeyBase64Url as jt, validateDevicePairApproveParams as k, deriveDeviceIdFromPublicKey as kt, validateAgentsListParams as l, validateSkillsBinsParams as lt, validateChatInjectParams as m, validateUpdateRunParams as mt, formatValidationErrors as n, validateSessionsCompactParams as nt, validateAgentsFilesGetParams as o, validateSessionsPreviewParams as ot, validateChatHistoryParams as p, validateTalkModeParams as pt, validateNodePairApproveParams as q, validateAgentIdentityParams as r, validateSessionsDeleteParams as rt, validateAgentsFilesListParams as s, validateSessionsResetParams as st, GatewayClient as t, validateSendParams as tt, validateChannelsLogoutParams as u, validateSkillsInstallParams as ut, validateConfigPatchParams as v, validateWizardCancelParams as vt, validateCronRemoveParams as w, errorShape as wt, validateConnectParams as x, validateWizardStatusParams as xt, validateConfigSchemaParams as y, validateWizardNextParams as yt, validateExecApprovalsSetParams as z }; |