UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,385 lines (1,367 loc) 87.5 kB
import { g as resolveStateDir } from "./paths-B4BZAPZh.js"; import { a as logError, i as logDebug } from "./exec-DYqRzFbo.js"; import { h as GATEWAY_CLIENT_NAMES, m as GATEWAY_CLIENT_MODES, p as GATEWAY_CLIENT_IDS } from "./message-channel-Bena1Tzd.js"; import { i as isSecureWebSocketUrl, t as rawDataToString } from "./ws-CV6gEox3.js"; import { t as INPUT_PROVENANCE_KIND_VALUES } from "./input-provenance-DkoU-lPU.js"; import { c as writeJsonAtomic, i as resolvePairingPaths, n as verifyPairingToken, o as createAsyncLock, r as pruneExpiredPending, s as readJsonFile, t as generatePairingToken } from "./pairing-token-B-_eiWlR.js"; import fs from "node:fs"; import path from "node:path"; 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 function resolveDefaultIdentityPath() { return path.join(resolveStateDir(), "identity", "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 = resolveDefaultIdentityPath()) { 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/shared/device-auth.ts function normalizeDeviceAuthRole(role) { return role.trim(); } function normalizeDeviceAuthScopes(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(); } //#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 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 = normalizeDeviceAuthRole(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 = normalizeDeviceAuthRole(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: normalizeDeviceAuthScopes(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 = normalizeDeviceAuthRole(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/shared/operator-scope-compat.ts const OPERATOR_ROLE = "operator"; const OPERATOR_ADMIN_SCOPE = "operator.admin"; const OPERATOR_READ_SCOPE = "operator.read"; const OPERATOR_WRITE_SCOPE = "operator.write"; function normalizeScopeList(scopes) { const out = /* @__PURE__ */ new Set(); for (const scope of scopes) { const trimmed = scope.trim(); if (trimmed) out.add(trimmed); } return [...out]; } function operatorScopeSatisfied(requestedScope, granted) { if (requestedScope === OPERATOR_READ_SCOPE) return granted.has(OPERATOR_READ_SCOPE) || granted.has(OPERATOR_WRITE_SCOPE) || granted.has(OPERATOR_ADMIN_SCOPE); return granted.has(requestedScope); } function roleScopesAllow(params) { const requested = normalizeScopeList(params.requestedScopes); if (requested.length === 0) return true; const allowed = normalizeScopeList(params.allowedScopes); if (allowed.length === 0) return false; const allowedSet = new Set(allowed); if (params.role.trim() !== OPERATOR_ROLE) return requested.every((scope) => allowedSet.has(scope)); return requested.every((scope) => operatorScopeSatisfied(scope, allowedSet)); } //#endregion //#region src/infra/device-pairing.ts const PENDING_TTL_MS = 300 * 1e3; const withLock = createAsyncLock(); async function loadState(baseDir) { const { pendingPath, pairedPath } = resolvePairingPaths(baseDir, "devices"); const [pending, paired] = await Promise.all([readJsonFile(pendingPath), readJsonFile(pairedPath)]); const state = { pendingById: pending ?? {}, pairedByDeviceId: paired ?? {} }; pruneExpiredPending(state.pendingById, Date.now(), PENDING_TTL_MS); return state; } async function persistState(state, baseDir) { const { pendingPath, pairedPath } = resolvePairingPaths(baseDir, "devices"); await Promise.all([writeJsonAtomic(pendingPath, state.pendingById), writeJsonAtomic(pairedPath, state.pairedByDeviceId)]); } function normalizeDeviceId(deviceId) { return deviceId.trim(); } function normalizeRole(role) { const trimmed = role?.trim(); return trimmed ? trimmed : null; } function mergeRoles(...items) { const roles = /* @__PURE__ */ new Set(); for (const item of items) { if (!item) continue; if (Array.isArray(item)) for (const role of item) { const trimmed = role.trim(); if (trimmed) roles.add(trimmed); } else { const trimmed = item.trim(); if (trimmed) roles.add(trimmed); } } if (roles.size === 0) return; return [...roles]; } function mergeScopes(...items) { const scopes = /* @__PURE__ */ new Set(); for (const item of items) { if (!item) continue; for (const scope of item) { const trimmed = scope.trim(); if (trimmed) scopes.add(trimmed); } } if (scopes.size === 0) return; return [...scopes]; } function mergePendingDevicePairingRequest(existing, incoming, isRepair) { const existingRole = normalizeRole(existing.role); const incomingRole = normalizeRole(incoming.role); return { ...existing, displayName: incoming.displayName ?? existing.displayName, platform: incoming.platform ?? existing.platform, clientId: incoming.clientId ?? existing.clientId, clientMode: incoming.clientMode ?? existing.clientMode, role: existingRole ?? incomingRole ?? void 0, roles: mergeRoles(existing.roles, existing.role, incoming.role), scopes: mergeScopes(existing.scopes, incoming.scopes), remoteIp: incoming.remoteIp ?? existing.remoteIp, silent: Boolean(existing.silent && incoming.silent), isRepair: existing.isRepair || isRepair, ts: Date.now() }; } function scopesAllow(requested, allowed) { if (requested.length === 0) return true; if (allowed.length === 0) return false; const allowedSet = new Set(allowed); return requested.every((scope) => allowedSet.has(scope)); } const DEVICE_SCOPE_IMPLICATIONS = { "operator.admin": [ "operator.read", "operator.write", "operator.approvals", "operator.pairing" ], "operator.write": ["operator.read"] }; function expandScopeImplications(scopes) { const expanded = new Set(scopes); const queue = [...scopes]; while (queue.length > 0) { const scope = queue.pop(); if (!scope) continue; for (const impliedScope of DEVICE_SCOPE_IMPLICATIONS[scope] ?? []) if (!expanded.has(impliedScope)) { expanded.add(impliedScope); queue.push(impliedScope); } } return [...expanded]; } function scopesAllowWithImplications(requested, allowed) { return scopesAllow(expandScopeImplications(requested), expandScopeImplications(allowed)); } function newToken() { return generatePairingToken(); } function getPairedDeviceFromState(state, deviceId) { return state.pairedByDeviceId[normalizeDeviceId(deviceId)] ?? null; } function cloneDeviceTokens(device) { return device.tokens ? { ...device.tokens } : {}; } function buildDeviceAuthToken(params) { return { token: newToken(), role: params.role, scopes: params.scopes, createdAtMs: params.existing?.createdAtMs ?? params.now, rotatedAtMs: params.rotatedAtMs, revokedAtMs: void 0, lastUsedAtMs: params.existing?.lastUsedAtMs }; } async function listDevicePairing(baseDir) { const state = await loadState(baseDir); return { pending: Object.values(state.pendingById).toSorted((a, b) => b.ts - a.ts), paired: Object.values(state.pairedByDeviceId).toSorted((a, b) => b.approvedAtMs - a.approvedAtMs) }; } async function getPairedDevice(deviceId, baseDir) { return (await loadState(baseDir)).pairedByDeviceId[normalizeDeviceId(deviceId)] ?? null; } async function requestDevicePairing(req, baseDir) { return await withLock(async () => { const state = await loadState(baseDir); const deviceId = normalizeDeviceId(req.deviceId); if (!deviceId) throw new Error("deviceId required"); const isRepair = Boolean(state.pairedByDeviceId[deviceId]); const existing = Object.values(state.pendingById).find((pending) => pending.deviceId === deviceId); if (existing) { const merged = mergePendingDevicePairingRequest(existing, req, isRepair); state.pendingById[existing.requestId] = merged; await persistState(state, baseDir); return { status: "pending", request: merged, created: false }; } const request = { requestId: randomUUID(), deviceId, publicKey: req.publicKey, displayName: req.displayName, platform: req.platform, clientId: req.clientId, clientMode: req.clientMode, role: req.role, roles: req.role ? [req.role] : void 0, scopes: req.scopes, remoteIp: req.remoteIp, silent: req.silent, isRepair, ts: Date.now() }; state.pendingById[request.requestId] = request; await persistState(state, baseDir); return { status: "pending", request, created: true }; }); } async function approveDevicePairing(requestId, baseDir) { return await withLock(async () => { const state = await loadState(baseDir); const pending = state.pendingById[requestId]; if (!pending) return null; const now = Date.now(); const existing = state.pairedByDeviceId[pending.deviceId]; const roles = mergeRoles(existing?.roles, existing?.role, pending.roles, pending.role); const approvedScopes = mergeScopes(existing?.approvedScopes ?? existing?.scopes, pending.scopes); const tokens = existing?.tokens ? { ...existing.tokens } : {}; const roleForToken = normalizeRole(pending.role); if (roleForToken) { const nextScopes = normalizeDeviceAuthScopes(pending.scopes); const existingToken = tokens[roleForToken]; const now = Date.now(); tokens[roleForToken] = { token: newToken(), role: roleForToken, scopes: nextScopes, createdAtMs: existingToken?.createdAtMs ?? now, rotatedAtMs: existingToken ? now : void 0, revokedAtMs: void 0, lastUsedAtMs: existingToken?.lastUsedAtMs }; } const device = { deviceId: pending.deviceId, publicKey: pending.publicKey, displayName: pending.displayName, platform: pending.platform, clientId: pending.clientId, clientMode: pending.clientMode, role: pending.role, roles, scopes: approvedScopes, approvedScopes, remoteIp: pending.remoteIp, tokens, createdAtMs: existing?.createdAtMs ?? now, approvedAtMs: now }; delete state.pendingById[requestId]; state.pairedByDeviceId[device.deviceId] = device; await persistState(state, baseDir); return { requestId, device }; }); } async function rejectDevicePairing(requestId, baseDir) { return await withLock(async () => { const state = await loadState(baseDir); const pending = state.pendingById[requestId]; if (!pending) return null; delete state.pendingById[requestId]; await persistState(state, baseDir); return { requestId, deviceId: pending.deviceId }; }); } async function removePairedDevice(deviceId, baseDir) { return await withLock(async () => { const state = await loadState(baseDir); const normalized = normalizeDeviceId(deviceId); if (!normalized || !state.pairedByDeviceId[normalized]) return null; delete state.pairedByDeviceId[normalized]; await persistState(state, baseDir); return { deviceId: normalized }; }); } async function updatePairedDeviceMetadata(deviceId, patch, baseDir) { return await withLock(async () => { const state = await loadState(baseDir); const existing = state.pairedByDeviceId[normalizeDeviceId(deviceId)]; if (!existing) return; const roles = mergeRoles(existing.roles, existing.role, patch.role); const scopes = mergeScopes(existing.scopes, patch.scopes); state.pairedByDeviceId[deviceId] = { ...existing, ...patch, deviceId: existing.deviceId, createdAtMs: existing.createdAtMs, approvedAtMs: existing.approvedAtMs, approvedScopes: existing.approvedScopes, role: patch.role ?? existing.role, roles, scopes }; await persistState(state, baseDir); }); } function summarizeDeviceTokens(tokens) { if (!tokens) return; const summaries = Object.values(tokens).map((token) => ({ role: token.role, scopes: token.scopes, createdAtMs: token.createdAtMs, rotatedAtMs: token.rotatedAtMs, revokedAtMs: token.revokedAtMs, lastUsedAtMs: token.lastUsedAtMs })).toSorted((a, b) => a.role.localeCompare(b.role)); return summaries.length > 0 ? summaries : void 0; } async function verifyDeviceToken(params) { return await withLock(async () => { const state = await loadState(params.baseDir); const device = getPairedDeviceFromState(state, params.deviceId); if (!device) return { ok: false, reason: "device-not-paired" }; const role = normalizeRole(params.role); if (!role) return { ok: false, reason: "role-missing" }; const entry = device.tokens?.[role]; if (!entry) return { ok: false, reason: "token-missing" }; if (entry.revokedAtMs) return { ok: false, reason: "token-revoked" }; if (!verifyPairingToken(params.token, entry.token)) return { ok: false, reason: "token-mismatch" }; if (!roleScopesAllow({ role, requestedScopes: normalizeDeviceAuthScopes(params.scopes), allowedScopes: entry.scopes })) return { ok: false, reason: "scope-mismatch" }; entry.lastUsedAtMs = Date.now(); device.tokens ??= {}; device.tokens[role] = entry; state.pairedByDeviceId[device.deviceId] = device; await persistState(state, params.baseDir); return { ok: true }; }); } async function ensureDeviceToken(params) { return await withLock(async () => { const state = await loadState(params.baseDir); const requestedScopes = normalizeDeviceAuthScopes(params.scopes); const context = resolveDeviceTokenUpdateContext({ state, deviceId: params.deviceId, role: params.role }); if (!context) return null; const { device, role, tokens, existing } = context; if (existing && !existing.revokedAtMs) { if (roleScopesAllow({ role, requestedScopes, allowedScopes: existing.scopes })) return existing; } const now = Date.now(); const next = buildDeviceAuthToken({ role, scopes: requestedScopes, existing, now, rotatedAtMs: existing ? now : void 0 }); tokens[role] = next; device.tokens = tokens; state.pairedByDeviceId[device.deviceId] = device; await persistState(state, params.baseDir); return next; }); } function resolveDeviceTokenUpdateContext(params) { const device = getPairedDeviceFromState(params.state, params.deviceId); if (!device) return null; const role = normalizeRole(params.role); if (!role) return null; const tokens = cloneDeviceTokens(device); return { device, role, tokens, existing: tokens[role] }; } async function rotateDeviceToken(params) { return await withLock(async () => { const state = await loadState(params.baseDir); const context = resolveDeviceTokenUpdateContext({ state, deviceId: params.deviceId, role: params.role }); if (!context) return null; const { device, role, tokens, existing } = context; const requestedScopes = normalizeDeviceAuthScopes(params.scopes ?? existing?.scopes ?? device.scopes); if (!scopesAllowWithImplications(requestedScopes, normalizeDeviceAuthScopes(device.approvedScopes ?? device.scopes ?? existing?.scopes))) return null; const now = Date.now(); const next = buildDeviceAuthToken({ role, scopes: requestedScopes, existing, now, rotatedAtMs: now }); tokens[role] = next; device.tokens = tokens; state.pairedByDeviceId[device.deviceId] = device; await persistState(state, params.baseDir); return next; }); } async function revokeDeviceToken(params) { return await withLock(async () => { const state = await loadState(params.baseDir); const device = state.pairedByDeviceId[normalizeDeviceId(params.deviceId)]; if (!device) return null; const role = normalizeRole(params.role); if (!role) return null; if (!device.tokens?.[role]) return null; const tokens = { ...device.tokens }; const entry = { ...tokens[role], revokedAtMs: Date.now() }; tokens[role] = entry; device.tokens = tokens; state.pairedByDeviceId[device.deviceId] = device; await persistState(state, params.baseDir); return entry; }); } async function clearDevicePairing(deviceId, baseDir) { return await withLock(async () => { const state = await loadState(baseDir); const normalizedId = normalizeDeviceId(deviceId); if (!state.pairedByDeviceId[normalizedId]) return false; delete state.pairedByDeviceId[normalizedId]; await persistState(state, baseDir); return true; }); } //#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: Type.Optional(Type.String()), 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()), threadId: 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 })), durationSeconds: Type.Optional(Type.Integer({ minimum: 1, maximum: 604800 })), durationHours: Type.Optional(Type.Integer({ minimum: 1 })), silent: Type.Optional(Type.Boolean()), isAnonymous: Type.Optional(Type.Boolean()), threadId: Type.Optional(Type.String()), 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()), inputProvenance: Type.Optional(Type.Object({ kind: Type.String({ enum: [...INPUT_PROVENANCE_KIND_VALUES] }), sourceSessionKey: Type.Optional(Type.String()), sourceChannel: Type.Optional(Type.String()), sourceTool: Type.Optional(Type.String()) }, { additionalProperties: false })), 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 AgentsCreateParamsSchema = Type.Object({ name: NonEmptyString, workspace: NonEmptyString, emoji: Type.Optional(Type.String()), avatar: Type.Optional(Type.String()) }, { additionalProperties: false }); const AgentsCreateResultSchema = Type.Object({ ok: Type.Literal(true), agentId: NonEmptyString, name: NonEmptyString, workspace: NonEmptyString }, { additionalProperties: false }); const AgentsUpdateParamsSchema = Type.Object({ agentId: NonEmptyString, name: Type.Optional(NonEmptyString), workspace: Type.Optional(NonEmptyString), model: Type.Optional(NonEmptyString), avatar: Type.Optional(Type.String()) }, { additionalProperties: false }); const AgentsUpdateResultSchema = Type.Object({ ok: Type.Literal(true), agentId: NonEmptyString }, { additionalProperties: false }); const AgentsDeleteParamsSchema = Type.Object({ agentId: NonEmptyString, deleteFiles: Type.Optional(Type.Boolean()) }, { additionalProperties: false }); const AgentsDeleteResultSchema = Type.Object({ ok: Type.Literal(true), agentId: NonEmptyString, removedBindings: Type.Integer({ minimum: 0 }) }, { 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 TalkConfigParamsSchema = Type.Object({ includeSecrets: Type.Optional(Type.Boolean()) }, { additionalProperties: false }); const TalkConfigResultSchema = Type.Object({ config: Type.Object({ talk: Type.Optional(Type.Object({ voiceId: Type.Optional(Type.String()), voiceAliases: Type.Optional(Type.Record(Type.String(), Type.String())), modelId: Type.Optional(Type.String()), outputFormat: Type.Optional(Type.String()), apiKey: Type.Optional(Type.String()), interruptOnSpeech: Type.Optional(Type.Boolean()) }, { additionalProperties: false })), session: Type.Optional(Type.Object({ mainKey: Type.Optional(Type.String()) }, { additionalProperties: false })), ui: Type.Optional(Type.Object({ seamColor: Type.Optional(Type.String()) }, { additionalProperties: false })) }, { additionalProperties: false }) }, { 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 ConfigApplyLikeParamsSchema = 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 ConfigApplyParamsSchema = ConfigApplyLikeParamsSchema; const ConfigPatchParamsSchema = ConfigApplyLikeParamsSchema; 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 function cronAgentTurnPayloadSchema(params) { return Type.Object({ kind: Type.Literal("agentTurn"), message: params.message, model: Type.Optional(Type.String()), thinking: Type.Optional(Type.String()), timeoutSeconds: Type.Optional(Type.Integer({ minimum: 0 })), allowUnsafeExternalContent: Type.Optional(Type.Boolean()), deliver: Type.Optional(Type.Boolean()), channel: Type.Optional(Type.String()), to: Type.Optional(Type.String()), bestEffortDeliver: Type.Optional(Type.Boolean()) }, { additionalProperties: false }); } const CronSessionTargetSchema = Type.Union([Type.Literal("main"), Type.Literal("isolated")]); const CronWakeModeSchema = Type.Union([Type.Literal("next-heartbeat"), Type.Literal("now")]); const CronCommonOptionalFields = { agentId: Type.Optional(Type.Union([NonEmptyString, Type.Null()])), sessionKey: Type.Optional(Type.Union([NonEmptyString, Type.Null()])), description: Type.Optional(Type.String()), enabled: Type.Optional(Type.Boolean()), deleteAfterRun: Type.Optional(Type.Boolean()) }; function cronIdOrJobIdParams(extraFields) { return Type.Union([Type.Object({ id: NonEmptyString, ...extraFields }, { additionalProperties: false }), Type.Object({ jobId: NonEmptyString, ...extraFields }, { additionalProperties: false })]); } 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()), staggerMs: Type.Optional(Type.Integer({ minimum: 0 })) }, { additionalProperties: false }) ]); const CronPayloadSchema = Type.Union([Type.Object({ kind: Type.Literal("systemEvent"), text: NonEmptyString }, { additionalProperties: false }), cronAgentTurnPayloadSchema({ message: NonEmptyString })]); const CronPayloadPatchSchema = Type.Union([Type.Object({ kind: Type.Literal("systemEvent"), text: Type.Optional(NonEmptyString) }, { additionalProperties: false }), cronAgentTurnPayloadSchema({ message: Type.Optional(NonEmptyString) })]); const CronDeliverySharedProperties = { channel: Type.Optional(Type.Union([Type.Literal("last"), NonEmptyString])), bestEffort: Type.Optional(Type.Boolean()) }; const CronDeliveryNoopSchema = Type.Object({ mode: Type.Literal("none"), ...CronDeliverySharedProperties, to: Type.Optional(Type.String()) }, { additionalProperties: false }); const CronDeliveryAnnounceSchema = Type.Object({ mode: Type.Literal("announce"), ...CronDeliverySharedProperties, to: Type.Optional(Type.String()) }, { additionalProperties: false }); const CronDeliveryWebhookSchema = Type.Object({ mode: Type.Literal("webhook"), ...CronDeliverySharedProperties, to: NonEmptyString }, { additionalProperties: false }); const CronDeliverySchema = Type.Union([ CronDeliveryNoopSchema, CronDeliveryAnnounceSchema, CronDeliveryWebhookSchema ]); const CronDeliveryPatchSchema = Type.Object({ mode: Type.Optional(Type.Union([ Type.Literal("none"), Type.Literal("announce"), Type.Literal("webhook") ])), ...CronDeliverySharedProperties, to: Type.Optional(Type.String()) }, { 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 })), consecutiveErrors: Type.Optional(Type.Integer({ minimum: 0 })) }, { additionalProperties: false }); const CronJobSchema = Type.Object({ id: NonEmptyString, agentId: Type.Optional(NonEmptyString), sessionKey: 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: CronSessionTargetSchema, wakeMode: CronWakeModeSchema, 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, ...CronCommonOptionalFields, schedule: CronScheduleSchema, sessionTarget: CronSessionTargetSchema, wakeMode: CronWakeModeSchema, payload: CronPayloadSchema, delivery: Type.Optional(CronDeliverySchema) }, { additionalProperties: false }); const CronJobPatchSchema = Type.Object({ name: Type.Optional(NonEmptyString), ...CronCommonOptionalFields, schedule: Type.Optional(CronScheduleSchema), sessionTarget: Type.Optional(CronSessionTargetSchema), wakeMode: Type.Optional(CronWakeModeSchema), payload: Type.Optional(CronPayloadPatchSchema), delivery: Type.Optional(CronDeliveryPatchSchema), state: Type.Optional(Type.Partial(CronJobStateSchema)) }, { additionalProperties: false }); const CronUpdateParamsSchema = cronIdOrJobIdParams({ patch: CronJobPatchSchema }); const CronRemoveParamsSchema = cronIdOrJobIdParams({}); const CronRunParamsSchema = cronIdOrJobIdParams({ mode: Type.Optional(Type.Union([Type.Literal("due"), Type.Literal("force")])) }); const CronRunsParamsSchema = cronIdOrJobIdParams({ limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 5e3 })) }); 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()), sessionId: Type.Optional(NonEmptyString), sessionKey: Type.Optional(NonEmptyString), 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 })), twoPhase: Type.Optional(Type.Boolean()) }, { 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 DevicePairRemoveParamsSchema = Type.Object({ deviceId: 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, mainSessi