@agentconnect.md/daemon 1.0.0-rc.281 → 1.0.0-rc.283
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-host-CrjtbS3A.js → acp-host-C-4zFhF8.js} +2 -2
- package/dist/{acp-host-CrjtbS3A.js.map → acp-host-C-4zFhF8.js.map} +1 -1
- package/dist/{agent-list-DRSg2Fhd.js → agent-list-BK1xbKJd.js} +2 -2
- package/dist/{agent-list-DRSg2Fhd.js.map → agent-list-BK1xbKJd.js.map} +1 -1
- package/dist/{chat-4Is9zl7L.js → chat-B8VEWuix.js} +3 -3
- package/dist/{chat-4Is9zl7L.js.map → chat-B8VEWuix.js.map} +1 -1
- package/dist/index.js +4 -4
- package/dist/{load-agents-Amic2B8b.js → load-agents-DzCnR0cj.js} +150 -1
- package/dist/load-agents-DzCnR0cj.js.map +1 -0
- package/dist/{login-BPKCyqR_.js → login-eP4k_Gf_.js} +3 -3
- package/dist/{login-BPKCyqR_.js.map → login-eP4k_Gf_.js.map} +1 -1
- package/dist/run-foreground-BFk_9xwW.js +2 -0
- package/dist/{run-foreground-BBBkQ93M.js → run-foreground-Cennaxiw.js} +356 -10
- package/dist/{run-foreground-BBBkQ93M.js.map → run-foreground-Cennaxiw.js.map} +1 -1
- package/package.json +1 -1
- package/dist/load-agents-Amic2B8b.js.map +0 -1
- package/dist/run-foreground-DA_KUM1q.js +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-BPKCyqR_.js","names":["realRunForeground"],"sources":["../src/cp/auth-probe.ts","../src/cli/login.ts"],"sourcesContent":["/**\n * `probeAuth` — a one-shot CP auth check used by `agentconnect login`. It dials\n * the CP, sends ONLY the `auth` frame (no `register`), and resolves with the\n * authoritative daemonId from `auth/ok`, or a human-readable failure reason on\n * a `4401` close, a correlated `error`, a dial failure, or a timeout. It always\n * closes the socket and never leaves a connection open.\n */\nimport { buildEnvelope, decodeEnvelope, encode, isFrame } from '@agentconnect.md/protocol'\nimport { ClientTransport, type Transport } from '@agentconnect.md/connection'\nimport { CP_SUBPROTOCOL, CP_WS_PATH } from './client.js'\n\nexport interface ProbeResult {\n ok: boolean\n daemonId?: string\n reason?: string\n}\n\nexport interface ProbeOpts {\n url: string\n token: string\n daemonId?: string\n agentVersion?: string\n timeoutMs?: number\n /** Injectable for tests; defaults to dialing the real WebSocket. */\n connect?: () => Promise<Transport>\n}\n\nexport async function probeAuth(opts: ProbeOpts): Promise<ProbeResult> {\n const timeoutMs = opts.timeoutMs ?? 10_000\n const dial = opts.connect ?? (() => ClientTransport.dial(opts.url, { subprotocol: CP_SUBPROTOCOL, path: CP_WS_PATH }))\n\n let transport: Transport\n try {\n transport = await dial()\n } catch (err) {\n return { ok: false, reason: `cannot reach ${opts.url}: ${(err as Error).message}` }\n }\n\n const authPayload: Record<string, unknown> = {\n apiKey: opts.token,\n agentVersion: opts.agentVersion ?? 'probe'\n }\n if (opts.daemonId) authPayload.daemonId = opts.daemonId\n const frame = buildEnvelope('auth', authPayload)\n\n return await new Promise<ProbeResult>((resolve) => {\n let settled = false\n const done = (r: ProbeResult): void => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n try {\n transport.close(1000, 'probe done')\n } catch {\n // socket may already be closed\n }\n resolve(r)\n }\n\n const timer = setTimeout(\n () => done({ ok: false, reason: `timed out after ${timeoutMs}ms waiting for auth/ok` }),\n timeoutMs\n )\n\n transport.onMessage((text) => {\n const decoded = decodeEnvelope(text)\n if (!decoded.ok) return\n const f = decoded.frame\n if (f.corr !== frame.id) return\n if (f.type === 'error') {\n const e = f.payload as { code: string; message: string }\n done({ ok: false, reason: `${e.code}: ${e.message}` })\n return\n }\n if (isFrame('auth/ok')(f)) {\n const ok = f.payload as { daemonId: string }\n done({ ok: true, daemonId: ok.daemonId })\n }\n })\n\n transport.onClose((code) => {\n if (code === 4401) done({ ok: false, reason: 'authentication failed (4401) — check the daemon token' })\n else done({ ok: false, reason: `connection closed (${code}) before auth/ok` })\n })\n\n transport.send(encode(frame))\n })\n}\n","/**\n * `agentconnect login` — interactive onboarding. It tests the Control Plane auth\n * flow, persists credentials only on success, then offers to install the OS\n * service. If installed it starts the service (`up`) and returns; otherwise it\n * runs the daemon in the foreground (the same path as `agentconnect run`).\n *\n * `persistCredentials` is the pure config-merge half (token-only onboarding:\n * `daemonId` is omitted unless given — the first connect adopts it from the\n * token's `sub`). `runLogin` is the orchestrator, with injectable deps for tests.\n */\nimport { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { createInterface } from 'node:readline'\nimport { fileURLToPath } from 'node:url'\nimport { ConfigSchema } from '../config/config-schema.js'\nimport { resolveRoot, configPath } from '../paths.js'\nimport { DAEMON_VERSION } from '../version.js'\nimport { probeAuth, type ProbeResult } from '../cp/auth-probe.js'\nimport { resolveController, type InstallOpts } from '../service/index.js'\nimport { runForeground as realRunForeground } from './run-foreground.js'\n\nexport interface PersistCredsOpts {\n cpUrl: string\n cpKey: string\n daemonId?: string\n root?: string\n configPath?: string\n}\n\n/** Merge url/token into config.json (validated), returning the written path. */\nexport function persistCredentials(opts: PersistCredsOpts): string {\n const root = resolveRoot(opts.root)\n const file = opts.configPath ?? configPath(root)\n const raw: Record<string, unknown> = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { version: 1 }\n\n const cp = (raw.controlPlane as Record<string, unknown> | undefined) ?? {}\n raw.controlPlane = { ...cp, url: opts.cpUrl, key: opts.cpKey, enabled: true }\n if (opts.daemonId) raw.daemonId = opts.daemonId\n\n ConfigSchema.parse(raw) // fail before writing if the merge is invalid\n\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(raw, null, 2) + '\\n')\n return file\n}\n\nexport interface RunLoginOpts {\n cpUrl?: string\n cpKey?: string\n daemonId?: string\n root?: string\n configPath?: string\n /** The resolved CLI entry path. Must be supplied by the CLI entry point\n * (index.ts) so the correct bundle path is used rather than inferring it\n * from login.ts's own import.meta.url (which is the same bundle but the\n * `../` path calculation would be wrong). Falls back to import.meta.url\n * for the pure test path where login.ts is loaded standalone. */\n cliEntry?: string\n}\n\n/**\n * Build the InstallOpts for the OS service installer.\n *\n * Exported as a pure helper so it can be unit-tested independently of the\n * full `runLogin` orchestration. The caller (index.ts) passes `cliEntry` so\n * the correct bundle path always flows through; if omitted we fall back to\n * `import.meta.url` (correct only when login.ts is the entry, e.g. in tests).\n */\nexport function buildInstallOpts(opts: { root?: string; cliEntry?: string }): InstallOpts {\n return {\n execPath: process.execPath,\n cliEntry: opts.cliEntry ?? fileURLToPath(import.meta.url),\n includeRootEnv: resolveRoot(opts.root) !== resolveRoot(undefined)\n }\n}\n\nexport interface LoginDeps {\n probe: (o: { url: string; token: string }) => Promise<ProbeResult>\n installService: () => Promise<void>\n runForeground: () => Promise<void>\n out: NodeJS.WritableStream\n input: NodeJS.ReadableStream\n isTTY: boolean\n}\n\nfunction realDeps(opts: RunLoginOpts): LoginDeps {\n return {\n probe: (o) => probeAuth({ url: o.url, token: o.token, agentVersion: DAEMON_VERSION }),\n installService: async () => {\n const controller = resolveController({ root: opts.root })\n await controller.install(buildInstallOpts(opts))\n await controller.up()\n },\n runForeground: () =>\n realRunForeground({\n root: opts.root,\n overrides: { cpUrl: opts.cpUrl, cpKey: opts.cpKey, daemonId: opts.daemonId }\n }),\n out: process.stdout,\n input: process.stdin,\n isTTY: Boolean(process.stdin.isTTY)\n }\n}\n\n/** Read the next line from a readline interface via async iterator. */\nasync function nextLine(\n iter: AsyncIterableIterator<string>,\n prompt: string,\n out: NodeJS.WritableStream\n): Promise<string> {\n out.write(prompt)\n const { value, done } = await iter.next()\n if (done) throw new Error(`unexpected end of input waiting for: ${prompt}`)\n return (value as string).trim()\n}\n\nexport async function runLogin(opts: RunLoginOpts, partial: Partial<LoginDeps> = {}): Promise<void> {\n const deps: LoginDeps = { ...realDeps(opts), ...partial }\n const { out } = deps\n\n // ── Non-interactive: flags required; probe; persist. No prompts, no handoff. ──\n if (!deps.isTTY) {\n if (!opts.cpUrl) throw new Error('login requires --cp-url <url>')\n if (!opts.cpKey) throw new Error('login requires --cp-key <key>')\n const r = await deps.probe({ url: opts.cpUrl, token: opts.cpKey })\n if (!r.ok) throw new Error(`authentication failed: ${r.reason}`)\n persistCredentials({ ...opts, cpUrl: opts.cpUrl, cpKey: opts.cpKey })\n out.write(`✓ authenticated${r.daemonId ? ` (daemon ${r.daemonId})` : ''} — credentials saved\\n`)\n return\n }\n\n // Both interactive handoffs read `<root>/config.json`: the installed service\n // runs `… run` (no `--config`), and the foreground `Daemon` loads by root. A\n // custom `--config` would persist credentials the daemon never reads, so the\n // install/run branch would look successful but start without them. Reject it;\n // `--root` is the knob that keeps persist + service + foreground in sync.\n if (opts.configPath) {\n throw new Error(\n 'interactive login does not support --config — use --root <dir> so the daemon and the installed service read the same config'\n )\n }\n\n // ── Interactive ──\n const rl = createInterface({ input: deps.input, output: out })\n const iter = rl[Symbol.asyncIterator]()\n let url = opts.cpUrl\n let token = opts.cpKey\n try {\n if (!url) url = await nextLine(iter, 'Control Plane URL: ', out)\n\n for (let attempt = 0; ; attempt++) {\n if (!token) token = await nextLine(iter, 'Daemon API key: ', out)\n out.write(`Testing connection to ${url}…\\n`)\n const r = await deps.probe({ url: url!, token: token! })\n if (r.ok) {\n out.write(`✓ authenticated${r.daemonId ? ` (daemon ${r.daemonId})` : ''}\\n`)\n break\n }\n out.write(`✗ ${r.reason}\\n`)\n if (attempt >= 1) throw new Error('authentication failed')\n token = undefined // re-prompt on retry\n }\n\n persistCredentials({ ...opts, cpUrl: url!, cpKey: token! })\n\n const ans = (await nextLine(iter, 'Install AgentConnect as a background service? (y/N) ', out)).toLowerCase()\n if (ans === 'y' || ans === 'yes') {\n await deps.installService()\n out.write('Service installed and started. Manage it with `agentconnect up` / `down` / `status`.\\n')\n return\n }\n out.write('Starting in the foreground (Ctrl-C to stop)…\\n')\n } finally {\n rl.close()\n }\n\n await deps.runForeground()\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"login-eP4k_Gf_.js","names":["realRunForeground"],"sources":["../src/cp/auth-probe.ts","../src/cli/login.ts"],"sourcesContent":["/**\n * `probeAuth` — a one-shot CP auth check used by `agentconnect login`. It dials\n * the CP, sends ONLY the `auth` frame (no `register`), and resolves with the\n * authoritative daemonId from `auth/ok`, or a human-readable failure reason on\n * a `4401` close, a correlated `error`, a dial failure, or a timeout. It always\n * closes the socket and never leaves a connection open.\n */\nimport { buildEnvelope, decodeEnvelope, encode, isFrame } from '@agentconnect.md/protocol'\nimport { ClientTransport, type Transport } from '@agentconnect.md/connection'\nimport { CP_SUBPROTOCOL, CP_WS_PATH } from './client.js'\n\nexport interface ProbeResult {\n ok: boolean\n daemonId?: string\n reason?: string\n}\n\nexport interface ProbeOpts {\n url: string\n token: string\n daemonId?: string\n agentVersion?: string\n timeoutMs?: number\n /** Injectable for tests; defaults to dialing the real WebSocket. */\n connect?: () => Promise<Transport>\n}\n\nexport async function probeAuth(opts: ProbeOpts): Promise<ProbeResult> {\n const timeoutMs = opts.timeoutMs ?? 10_000\n const dial = opts.connect ?? (() => ClientTransport.dial(opts.url, { subprotocol: CP_SUBPROTOCOL, path: CP_WS_PATH }))\n\n let transport: Transport\n try {\n transport = await dial()\n } catch (err) {\n return { ok: false, reason: `cannot reach ${opts.url}: ${(err as Error).message}` }\n }\n\n const authPayload: Record<string, unknown> = {\n apiKey: opts.token,\n agentVersion: opts.agentVersion ?? 'probe'\n }\n if (opts.daemonId) authPayload.daemonId = opts.daemonId\n const frame = buildEnvelope('auth', authPayload)\n\n return await new Promise<ProbeResult>((resolve) => {\n let settled = false\n const done = (r: ProbeResult): void => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n try {\n transport.close(1000, 'probe done')\n } catch {\n // socket may already be closed\n }\n resolve(r)\n }\n\n const timer = setTimeout(\n () => done({ ok: false, reason: `timed out after ${timeoutMs}ms waiting for auth/ok` }),\n timeoutMs\n )\n\n transport.onMessage((text) => {\n const decoded = decodeEnvelope(text)\n if (!decoded.ok) return\n const f = decoded.frame\n if (f.corr !== frame.id) return\n if (f.type === 'error') {\n const e = f.payload as { code: string; message: string }\n done({ ok: false, reason: `${e.code}: ${e.message}` })\n return\n }\n if (isFrame('auth/ok')(f)) {\n const ok = f.payload as { daemonId: string }\n done({ ok: true, daemonId: ok.daemonId })\n }\n })\n\n transport.onClose((code) => {\n if (code === 4401) done({ ok: false, reason: 'authentication failed (4401) — check the daemon token' })\n else done({ ok: false, reason: `connection closed (${code}) before auth/ok` })\n })\n\n transport.send(encode(frame))\n })\n}\n","/**\n * `agentconnect login` — interactive onboarding. It tests the Control Plane auth\n * flow, persists credentials only on success, then offers to install the OS\n * service. If installed it starts the service (`up`) and returns; otherwise it\n * runs the daemon in the foreground (the same path as `agentconnect run`).\n *\n * `persistCredentials` is the pure config-merge half (token-only onboarding:\n * `daemonId` is omitted unless given — the first connect adopts it from the\n * token's `sub`). `runLogin` is the orchestrator, with injectable deps for tests.\n */\nimport { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { createInterface } from 'node:readline'\nimport { fileURLToPath } from 'node:url'\nimport { ConfigSchema } from '../config/config-schema.js'\nimport { resolveRoot, configPath } from '../paths.js'\nimport { DAEMON_VERSION } from '../version.js'\nimport { probeAuth, type ProbeResult } from '../cp/auth-probe.js'\nimport { resolveController, type InstallOpts } from '../service/index.js'\nimport { runForeground as realRunForeground } from './run-foreground.js'\n\nexport interface PersistCredsOpts {\n cpUrl: string\n cpKey: string\n daemonId?: string\n root?: string\n configPath?: string\n}\n\n/** Merge url/token into config.json (validated), returning the written path. */\nexport function persistCredentials(opts: PersistCredsOpts): string {\n const root = resolveRoot(opts.root)\n const file = opts.configPath ?? configPath(root)\n const raw: Record<string, unknown> = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { version: 1 }\n\n const cp = (raw.controlPlane as Record<string, unknown> | undefined) ?? {}\n raw.controlPlane = { ...cp, url: opts.cpUrl, key: opts.cpKey, enabled: true }\n if (opts.daemonId) raw.daemonId = opts.daemonId\n\n ConfigSchema.parse(raw) // fail before writing if the merge is invalid\n\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(file, JSON.stringify(raw, null, 2) + '\\n')\n return file\n}\n\nexport interface RunLoginOpts {\n cpUrl?: string\n cpKey?: string\n daemonId?: string\n root?: string\n configPath?: string\n /** The resolved CLI entry path. Must be supplied by the CLI entry point\n * (index.ts) so the correct bundle path is used rather than inferring it\n * from login.ts's own import.meta.url (which is the same bundle but the\n * `../` path calculation would be wrong). Falls back to import.meta.url\n * for the pure test path where login.ts is loaded standalone. */\n cliEntry?: string\n}\n\n/**\n * Build the InstallOpts for the OS service installer.\n *\n * Exported as a pure helper so it can be unit-tested independently of the\n * full `runLogin` orchestration. The caller (index.ts) passes `cliEntry` so\n * the correct bundle path always flows through; if omitted we fall back to\n * `import.meta.url` (correct only when login.ts is the entry, e.g. in tests).\n */\nexport function buildInstallOpts(opts: { root?: string; cliEntry?: string }): InstallOpts {\n return {\n execPath: process.execPath,\n cliEntry: opts.cliEntry ?? fileURLToPath(import.meta.url),\n includeRootEnv: resolveRoot(opts.root) !== resolveRoot(undefined)\n }\n}\n\nexport interface LoginDeps {\n probe: (o: { url: string; token: string }) => Promise<ProbeResult>\n installService: () => Promise<void>\n runForeground: () => Promise<void>\n out: NodeJS.WritableStream\n input: NodeJS.ReadableStream\n isTTY: boolean\n}\n\nfunction realDeps(opts: RunLoginOpts): LoginDeps {\n return {\n probe: (o) => probeAuth({ url: o.url, token: o.token, agentVersion: DAEMON_VERSION }),\n installService: async () => {\n const controller = resolveController({ root: opts.root })\n await controller.install(buildInstallOpts(opts))\n await controller.up()\n },\n runForeground: () =>\n realRunForeground({\n root: opts.root,\n overrides: { cpUrl: opts.cpUrl, cpKey: opts.cpKey, daemonId: opts.daemonId }\n }),\n out: process.stdout,\n input: process.stdin,\n isTTY: Boolean(process.stdin.isTTY)\n }\n}\n\n/** Read the next line from a readline interface via async iterator. */\nasync function nextLine(\n iter: AsyncIterableIterator<string>,\n prompt: string,\n out: NodeJS.WritableStream\n): Promise<string> {\n out.write(prompt)\n const { value, done } = await iter.next()\n if (done) throw new Error(`unexpected end of input waiting for: ${prompt}`)\n return (value as string).trim()\n}\n\nexport async function runLogin(opts: RunLoginOpts, partial: Partial<LoginDeps> = {}): Promise<void> {\n const deps: LoginDeps = { ...realDeps(opts), ...partial }\n const { out } = deps\n\n // ── Non-interactive: flags required; probe; persist. No prompts, no handoff. ──\n if (!deps.isTTY) {\n if (!opts.cpUrl) throw new Error('login requires --cp-url <url>')\n if (!opts.cpKey) throw new Error('login requires --cp-key <key>')\n const r = await deps.probe({ url: opts.cpUrl, token: opts.cpKey })\n if (!r.ok) throw new Error(`authentication failed: ${r.reason}`)\n persistCredentials({ ...opts, cpUrl: opts.cpUrl, cpKey: opts.cpKey })\n out.write(`✓ authenticated${r.daemonId ? ` (daemon ${r.daemonId})` : ''} — credentials saved\\n`)\n return\n }\n\n // Both interactive handoffs read `<root>/config.json`: the installed service\n // runs `… run` (no `--config`), and the foreground `Daemon` loads by root. A\n // custom `--config` would persist credentials the daemon never reads, so the\n // install/run branch would look successful but start without them. Reject it;\n // `--root` is the knob that keeps persist + service + foreground in sync.\n if (opts.configPath) {\n throw new Error(\n 'interactive login does not support --config — use --root <dir> so the daemon and the installed service read the same config'\n )\n }\n\n // ── Interactive ──\n const rl = createInterface({ input: deps.input, output: out })\n const iter = rl[Symbol.asyncIterator]()\n let url = opts.cpUrl\n let token = opts.cpKey\n try {\n if (!url) url = await nextLine(iter, 'Control Plane URL: ', out)\n\n for (let attempt = 0; ; attempt++) {\n if (!token) token = await nextLine(iter, 'Daemon API key: ', out)\n out.write(`Testing connection to ${url}…\\n`)\n const r = await deps.probe({ url: url!, token: token! })\n if (r.ok) {\n out.write(`✓ authenticated${r.daemonId ? ` (daemon ${r.daemonId})` : ''}\\n`)\n break\n }\n out.write(`✗ ${r.reason}\\n`)\n if (attempt >= 1) throw new Error('authentication failed')\n token = undefined // re-prompt on retry\n }\n\n persistCredentials({ ...opts, cpUrl: url!, cpKey: token! })\n\n const ans = (await nextLine(iter, 'Install AgentConnect as a background service? (y/N) ', out)).toLowerCase()\n if (ans === 'y' || ans === 'yes') {\n await deps.installService()\n out.write('Service installed and started. Manage it with `agentconnect up` / `down` / `status`.\\n')\n return\n }\n out.write('Starting in the foreground (Ctrl-C to stop)…\\n')\n } finally {\n rl.close()\n }\n\n await deps.runForeground()\n}\n"],"mappings":"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"}
|
|
@@ -3,9 +3,9 @@ import { n as require_ms, t as require_src$3 } from "./src-DDtlVutj.js";
|
|
|
3
3
|
import { n as init_esm_shims, t as __dirname } from "./esm-shims-DxGcm4Hw.js";
|
|
4
4
|
import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
|
|
5
5
|
import { C as _coercedNumber, T as NEVER, a as array, d as literal, f as looseObject, i as any, m as object, o as boolean, p as number$1, t as ZodNumber, v as string, x as url$4 } from "./schemas-CQkO9hC5.js";
|
|
6
|
-
import { C as ZodIssueCode, S as RESERVED_MCP_SERVER_NAME, _ as buildEnvelope, a as agentChildEnv, b as MAX_FRAME_BYTES, c as persistRelays, d as normalizeGitUrl, f as RELAY_DAEMON_SUBPROTOCOL, g as SHARED_CONFIG_ACTION_ID, h as decodeRelayDaemonFrame, i as selectAgent, m as buildRelayDaemonFrame, n as findAgentFiles, o as loadConfig, p as RELAY_DAEMON_WS_PATH, r as loadAgents, s as persistDaemonId, v as decodeEnvelope, y as encode } from "./load-agents-
|
|
6
|
+
import { C as ZodIssueCode, S as RESERVED_MCP_SERVER_NAME, _ as buildEnvelope, a as agentChildEnv, b as MAX_FRAME_BYTES, c as persistRelays, d as normalizeGitUrl, f as RELAY_DAEMON_SUBPROTOCOL, g as SHARED_CONFIG_ACTION_ID, h as decodeRelayDaemonFrame, i as selectAgent, m as buildRelayDaemonFrame, n as findAgentFiles, o as loadConfig, p as RELAY_DAEMON_WS_PATH, r as loadAgents, s as persistDaemonId, v as decodeEnvelope, y as encode } from "./load-agents-DzCnR0cj.js";
|
|
7
7
|
import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
|
|
8
|
-
import { S as resolveRuntimes, _ as writeMemoryFile, a as initGitInjection, b as cachedRuntimeNames, c as simpleGit, d as memoryProviderFor, f as MemoryConflictError, g as resolveInMemoryDir, h as listMemory, i as prepareWorkspace, l as createMemoryProvider, m as MemoryTooLargeError, n as installedRuntimes, o as probeGitVersion, p as MemoryPathError, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as memoryKindOf, v as MEMORY_TOOL_NAMES, x as cachedRuntimeVersions, y as toolsForIntegrations } from "./acp-host-
|
|
8
|
+
import { S as resolveRuntimes, _ as writeMemoryFile, a as initGitInjection, b as cachedRuntimeNames, c as simpleGit, d as memoryProviderFor, f as MemoryConflictError, g as resolveInMemoryDir, h as listMemory, i as prepareWorkspace, l as createMemoryProvider, m as MemoryTooLargeError, n as installedRuntimes, o as probeGitVersion, p as MemoryPathError, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as memoryKindOf, v as MEMORY_TOOL_NAMES, x as cachedRuntimeVersions, y as toolsForIntegrations } from "./acp-host-C-4zFhF8.js";
|
|
9
9
|
import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-CMC80O_1.js";
|
|
10
10
|
import { B as ToolListChangedNotificationSchema, D as ListPromptsResultSchema, E as ListChangedOptionsBaseSchema, F as PromptListChangedNotificationSchema, G as isZ4Schema, H as isJSONRPCRequest, I as ReadResourceResultSchema, J as encodeFrame, K as safeParse, L as ResourceListChangedNotificationSchema, M as ListToolsResultSchema, O as ListResourceTemplatesResultSchema, P as McpError, R as SUPPORTED_PROTOCOL_VERSIONS, S as InitializeResultSchema, T as LATEST_PROTOCOL_VERSION, U as isJSONRPCResultResponse, V as isInitializedNotification, W as getObjectShape, _ as ElicitResultSchema, a as AjvJsonSchemaValidator, b as GetPromptResultSchema, c as mergeCapabilities, d as CompleteResultSchema, f as CreateMessageRequestSchema, g as ElicitRequestSchema, h as CreateTaskResultSchema, i as assertToolsCallTaskCapability, k as ListResourcesResultSchema, m as CreateMessageResultWithToolsSchema, n as serializeMessage, o as require_fast_deep_equal, p as CreateMessageResultSchema, q as decodeFrames, r as assertClientRequestTaskCapability, s as Protocol, t as ReadBuffer, u as CallToolResultSchema, v as EmptyResultSchema, w as JSONRPCMessageSchema, y as ErrorCode } from "./stdio-DnS8WcXi.js";
|
|
11
11
|
import { URL as URL$1 } from "url";
|
|
@@ -4516,6 +4516,9 @@ var CpClient = class {
|
|
|
4516
4516
|
case "relay/roster":
|
|
4517
4517
|
this.deps.configApply.applyRelayRoster(frame.payload.relays);
|
|
4518
4518
|
return;
|
|
4519
|
+
case "collaboration/routes":
|
|
4520
|
+
this.deps.configApply.applyCollabRoutes(frame.payload);
|
|
4521
|
+
return;
|
|
4519
4522
|
case "agent/upsert": {
|
|
4520
4523
|
const u = frame.payload;
|
|
4521
4524
|
this.deps.configApply.applyAgentUpsert(u.agentId, u.spec);
|
|
@@ -6370,6 +6373,26 @@ var LocalStore = class {
|
|
|
6370
6373
|
CREATE TABLE IF NOT EXISTS cron_runs (
|
|
6371
6374
|
key TEXT PRIMARY KEY, lastRunAt INTEGER NOT NULL
|
|
6372
6375
|
);
|
|
6376
|
+
-- §6.9 #353 durable inbox: an ADMITTED-but-QUEUED message persisted BEFORE the
|
|
6377
|
+
-- admission ACK, so a hard kill / agent move can't lose a message the caller was
|
|
6378
|
+
-- already told delivered:true. Replayed FIFO-by-sessionKey on startup and removed
|
|
6379
|
+
-- on every terminal path (success / reject / cancel / gate-drop). The id column is the
|
|
6380
|
+
-- message's STABLE deliveryId/msgId (§6.3) so re-append and replay are idempotent
|
|
6381
|
+
-- against the existing admission-idempotency maps. Webchat turns are NOT persisted
|
|
6382
|
+
-- here (their live sink can't be restored — see §6.9 #367). enqueuedAt is a
|
|
6383
|
+
-- monotonic decimal string (fixed-width ⇒ string order == numeric order) driving
|
|
6384
|
+
-- FIFO within a sessionKey.
|
|
6385
|
+
CREATE TABLE IF NOT EXISTS inbox (
|
|
6386
|
+
id TEXT PRIMARY KEY,
|
|
6387
|
+
sessionKey TEXT NOT NULL,
|
|
6388
|
+
agentId TEXT NOT NULL,
|
|
6389
|
+
msg TEXT NOT NULL,
|
|
6390
|
+
integrationId TEXT,
|
|
6391
|
+
callMeta TEXT,
|
|
6392
|
+
isQueueCmd INTEGER,
|
|
6393
|
+
enqueuedAt TEXT NOT NULL
|
|
6394
|
+
);
|
|
6395
|
+
CREATE INDEX IF NOT EXISTS inbox_fifo ON inbox (sessionKey, enqueuedAt);
|
|
6373
6396
|
`);
|
|
6374
6397
|
this.migrateSessionUsage();
|
|
6375
6398
|
this.migrateTranscriptToolBody();
|
|
@@ -6724,6 +6747,32 @@ var LocalStore = class {
|
|
|
6724
6747
|
getCronLastRun(key) {
|
|
6725
6748
|
return this.db.prepare("SELECT lastRunAt FROM cron_runs WHERE key = ?").get(key)?.lastRunAt;
|
|
6726
6749
|
}
|
|
6750
|
+
/** §6.9 #353 durable inbox: persist an admitted message BEFORE its admission ACK. Keyed
|
|
6751
|
+
* by the stable id (deliveryId/msgId) with INSERT OR IGNORE, so a re-append of the same
|
|
6752
|
+
* delivery (P2 retry / replay) is a no-op — idempotent against the admission maps. */
|
|
6753
|
+
appendInbox(row) {
|
|
6754
|
+
this.db.prepare(`INSERT OR IGNORE INTO inbox (id, sessionKey, agentId, msg, integrationId, callMeta, isQueueCmd, enqueuedAt)
|
|
6755
|
+
VALUES (@id, @sessionKey, @agentId, @msg, @integrationId, @callMeta, @isQueueCmd, @enqueuedAt)`).run({
|
|
6756
|
+
id: row.id,
|
|
6757
|
+
sessionKey: row.sessionKey,
|
|
6758
|
+
agentId: row.agentId,
|
|
6759
|
+
msg: row.msg,
|
|
6760
|
+
integrationId: row.integrationId ?? null,
|
|
6761
|
+
callMeta: row.callMeta ?? null,
|
|
6762
|
+
isQueueCmd: row.isQueueCmd ?? null,
|
|
6763
|
+
enqueuedAt: row.enqueuedAt
|
|
6764
|
+
});
|
|
6765
|
+
}
|
|
6766
|
+
/** Remove an inbox row once its turn reaches a terminal state (success / reject / cancel /
|
|
6767
|
+
* gate-drop) so it is never replayed. No-op on an unknown id. */
|
|
6768
|
+
removeInbox(id) {
|
|
6769
|
+
this.db.prepare("DELETE FROM inbox WHERE id = ?").run(id);
|
|
6770
|
+
}
|
|
6771
|
+
/** All pending inbox rows, ordered FIFO-by-sessionKey (sessionKey, then enqueuedAt) for
|
|
6772
|
+
* startup replay (§6.9 #353). Order within a sessionKey is preserved by `enqueuedAt`. */
|
|
6773
|
+
listInboxBySessionKeyFifo() {
|
|
6774
|
+
return this.db.prepare("SELECT * FROM inbox ORDER BY sessionKey ASC, enqueuedAt ASC").all();
|
|
6775
|
+
}
|
|
6727
6776
|
close() {
|
|
6728
6777
|
this.db.close();
|
|
6729
6778
|
}
|
|
@@ -197213,6 +197262,18 @@ var RelayClient = class {
|
|
|
197213
197262
|
isReady() {
|
|
197214
197263
|
return this.state === "READY";
|
|
197215
197264
|
}
|
|
197265
|
+
/**
|
|
197266
|
+
* Send a cross-daemon agent-call to the relay (`rd/agentmsg` REQ, agent-collaboration
|
|
197267
|
+
* §2.3/§6.4) and resolve with the relay's admission verdict. `claimedFromAgentId` is
|
|
197268
|
+
* this daemon's trusted caller — the relay re-validates it against the socket's
|
|
197269
|
+
* authenticated daemonId. Rejects (retryable) if the socket isn't READY.
|
|
197270
|
+
*/
|
|
197271
|
+
async sendAgentMsg(payload) {
|
|
197272
|
+
if (this.state !== "READY") throw new WireError("INTERNAL", `rd link not ready (${this.state})`, true);
|
|
197273
|
+
const rep = await this.sendRequest(buildRelayDaemonFrame("rd/agentmsg", payload));
|
|
197274
|
+
if (rep.type !== "rd/agentmsg/ack") throw new WireError("INTERNAL", `expected rd/agentmsg/ack, got ${rep.type}`, false);
|
|
197275
|
+
return rep.payload;
|
|
197276
|
+
}
|
|
197216
197277
|
async attemptConnect() {
|
|
197217
197278
|
if (this.stopped || this.fatal) return;
|
|
197218
197279
|
const daemonId = this.deps.daemonId();
|
|
@@ -197264,6 +197325,10 @@ var RelayClient = class {
|
|
|
197264
197325
|
this.handleMsg(frame.id, frame.payload);
|
|
197265
197326
|
return;
|
|
197266
197327
|
}
|
|
197328
|
+
if (frame.type === "rd/agentmsg/fwd") {
|
|
197329
|
+
this.handleAgentMsgFwd(frame.id, frame.payload);
|
|
197330
|
+
return;
|
|
197331
|
+
}
|
|
197267
197332
|
this.deps.log.debug(`relay(${this.relayId}): ignoring ${frame.type}`);
|
|
197268
197333
|
}
|
|
197269
197334
|
/** An inbound rd/msg: dispatch it, reply `rd/ack`; only a webchat op streams
|
|
@@ -197274,7 +197339,22 @@ var RelayClient = class {
|
|
|
197274
197339
|
const ack = this.deps.onRelayMsg(msg, chat);
|
|
197275
197340
|
this.transport?.send(JSON.stringify(buildRelayDaemonFrame("rd/ack", ack, { corr: reqId })));
|
|
197276
197341
|
}
|
|
197277
|
-
/**
|
|
197342
|
+
/** A forwarded cross-daemon agent-call: terminal-verify + dispatch, reply the typed
|
|
197343
|
+
* admission verdict (`rd/agentmsg/ack`, corr = the relay's fwd id). */
|
|
197344
|
+
async handleAgentMsgFwd(reqId, msg) {
|
|
197345
|
+
let ack;
|
|
197346
|
+
try {
|
|
197347
|
+
ack = await this.deps.onRelayAgentMsg(msg);
|
|
197348
|
+
} catch (err) {
|
|
197349
|
+
this.deps.log.error(`relay(${this.relayId}): rd/agentmsg/fwd handler threw: ${err.message}`);
|
|
197350
|
+
ack = {
|
|
197351
|
+
deliveryId: msg.deliveryId,
|
|
197352
|
+
delivered: false,
|
|
197353
|
+
reason: "offline"
|
|
197354
|
+
};
|
|
197355
|
+
}
|
|
197356
|
+
this.transport?.send(JSON.stringify(buildRelayDaemonFrame("rd/agentmsg/ack", ack, { corr: reqId })));
|
|
197357
|
+
}
|
|
197278
197358
|
sendChat(chatId, event) {
|
|
197279
197359
|
const seq = (this.seqByChat.get(chatId) ?? 0) + 1;
|
|
197280
197360
|
this.seqByChat.set(chatId, seq);
|
|
@@ -197346,6 +197426,45 @@ var RelayManager = class {
|
|
|
197346
197426
|
size() {
|
|
197347
197427
|
return this.clients.size;
|
|
197348
197428
|
}
|
|
197429
|
+
/**
|
|
197430
|
+
* Send a cross-daemon agent-call over ANY READY relay (agent-collaboration §2.3/§6.4).
|
|
197431
|
+
* The all-to-all roster means any relay this daemon holds can route the call to the
|
|
197432
|
+
* target's owning daemon via the CP's collaboration snapshot — so we pick the first
|
|
197433
|
+
* READY client. Throws if none is READY (the caller reports the call undeliverable).
|
|
197434
|
+
*/
|
|
197435
|
+
async sendAgentMsg(payload) {
|
|
197436
|
+
for (const client of this.clients.values()) if (client.isReady()) return client.sendAgentMsg(payload);
|
|
197437
|
+
throw new Error("no READY relay to route agent-call");
|
|
197438
|
+
}
|
|
197439
|
+
};
|
|
197440
|
+
//#endregion
|
|
197441
|
+
//#region src/cp/cp-collab-routes.ts
|
|
197442
|
+
var CpCollabRoutes = class {
|
|
197443
|
+
generation = -1;
|
|
197444
|
+
channels = /* @__PURE__ */ new Map();
|
|
197445
|
+
key(orgId, platform, channelId) {
|
|
197446
|
+
return orgId + "\0" + platform + "\0" + channelId;
|
|
197447
|
+
}
|
|
197448
|
+
/** FULL-REPLACE from a CP snapshot (converge-don't-diff); ignore an older generation.
|
|
197449
|
+
* Tolerates an absent snapshot (a pre-collab CP / hand-built test fixture) as a no-op. */
|
|
197450
|
+
replace(snap) {
|
|
197451
|
+
if (!snap) return;
|
|
197452
|
+
if (snap.generation < this.generation) return;
|
|
197453
|
+
this.generation = snap.generation;
|
|
197454
|
+
this.channels.clear();
|
|
197455
|
+
for (const ch of snap.channels) {
|
|
197456
|
+
const byAgent = /* @__PURE__ */ new Map();
|
|
197457
|
+
for (const a of ch.agents) byAgent.set(a.agentId, {
|
|
197458
|
+
...a,
|
|
197459
|
+
orgId: ch.orgId
|
|
197460
|
+
});
|
|
197461
|
+
this.channels.set(this.key(ch.orgId, ch.platform, ch.channelId), byAgent);
|
|
197462
|
+
}
|
|
197463
|
+
}
|
|
197464
|
+
/** The placement of `agentId` in `(orgId,platform,channel)`, or undefined. */
|
|
197465
|
+
resolve(orgId, platform, channelId, agentId) {
|
|
197466
|
+
return this.channels.get(this.key(orgId, platform, channelId))?.get(agentId);
|
|
197467
|
+
}
|
|
197349
197468
|
};
|
|
197350
197469
|
//#endregion
|
|
197351
197470
|
//#region src/slack/permalink.ts
|
|
@@ -213917,6 +214036,10 @@ const IDLE_FLUSH_MS = 2e3;
|
|
|
213917
214036
|
/** Public Web App console origin used for session deep links when neither a local
|
|
213918
214037
|
* `webAppUrl` config nor a CP-provided one is set — so "View session" always resolves. */
|
|
213919
214038
|
const DEFAULT_WEB_APP_URL = "https://app.agentconnect.md";
|
|
214039
|
+
/** Per-in-flight-turn rendering state, keyed by ACP sessionId in `this.pending`. */
|
|
214040
|
+
/** Cap on agent→agent hop depth (design §2.4/§4.5) — reject a `messageAgent` that
|
|
214041
|
+
* would push the outgoing hopCount past this, so an A↔B wake loop can't run away. */
|
|
214042
|
+
const MAX_AGENT_CALL_HOPS = 8;
|
|
213920
214043
|
/** Build the wire SessionKey (protocol §5) for a pending turn — what `drain/done`
|
|
213921
214044
|
* reports as released so the CP may reassign it. Uses the real `thread` (absent for
|
|
213922
214045
|
* a channel-root message), NOT `statusThread` (which falls back to msgId): the CP
|
|
@@ -213992,6 +214115,7 @@ var Daemon = class Daemon {
|
|
|
213992
214115
|
cpIntegrations;
|
|
213993
214116
|
botUserIds = {};
|
|
213994
214117
|
cpRouting;
|
|
214118
|
+
cpCollab = new CpCollabRoutes();
|
|
213995
214119
|
clock;
|
|
213996
214120
|
requestExit;
|
|
213997
214121
|
draining = false;
|
|
@@ -214217,6 +214341,7 @@ var Daemon = class Daemon {
|
|
|
214217
214341
|
};
|
|
214218
214342
|
this.watcher.on("add", debounced).on("change", debounced).on("unlink", debounced);
|
|
214219
214343
|
this.log.info(`watching ${this.agentsDir} for agent changes`);
|
|
214344
|
+
this.replayInbox();
|
|
214220
214345
|
this.startCpClient(root);
|
|
214221
214346
|
this.armIdleSweep();
|
|
214222
214347
|
this.log.info("daemon ready");
|
|
@@ -215027,6 +215152,74 @@ var Daemon = class Daemon {
|
|
|
215027
215152
|
accepted: true
|
|
215028
215153
|
};
|
|
215029
215154
|
}
|
|
215155
|
+
relayAgentMsgAcks = /* @__PURE__ */ new Map();
|
|
215156
|
+
/**
|
|
215157
|
+
* TARGET side of a cross-daemon `messageAgent` (agent-collaboration §2.3/§6.2, P2) —
|
|
215158
|
+
* the mirror of {@link handleRelayIm} for a relay-forwarded `rd/agentmsg/fwd`. The
|
|
215159
|
+
* relay already validated the caller and minted a TRUSTED claim
|
|
215160
|
+
* (`trustedFromAgentId` + org/channel assertion), but we TERMINAL-VERIFY it against
|
|
215161
|
+
* our OWN local snapshot/spec (defense in depth, §2.5 #4) before dispatching — never
|
|
215162
|
+
* blindly trusting the relay. The ACK is returned on durable ADMISSION (the P4-gate
|
|
215163
|
+
* enqueue), NOT after the model turn (§6.4).
|
|
215164
|
+
*/
|
|
215165
|
+
async handleRelayAgentMsg(msg) {
|
|
215166
|
+
const nak = (reason) => ({
|
|
215167
|
+
deliveryId: msg.deliveryId,
|
|
215168
|
+
delivered: false,
|
|
215169
|
+
...reason ? { reason } : {}
|
|
215170
|
+
});
|
|
215171
|
+
const dedupKey = `${msg.trustedFromAgentId}:${msg.deliveryId}`;
|
|
215172
|
+
const prior = this.relayAgentMsgAcks.get(dedupKey);
|
|
215173
|
+
if (prior) return prior;
|
|
215174
|
+
const record = (ack) => {
|
|
215175
|
+
if (this.relayAgentMsgAcks.size >= 2e3) this.relayAgentMsgAcks.clear();
|
|
215176
|
+
this.relayAgentMsgAcks.set(dedupKey, ack);
|
|
215177
|
+
return ack;
|
|
215178
|
+
};
|
|
215179
|
+
if (msg.hopCount > MAX_AGENT_CALL_HOPS) return record(nak("hop_limit"));
|
|
215180
|
+
const { platform, channel, thread } = msg.coords;
|
|
215181
|
+
if (!this.agents.get(msg.toAgentId)) return record(nak("not_found"));
|
|
215182
|
+
const target = this.cpCollab.resolve(msg.orgId, platform, channel, msg.toAgentId);
|
|
215183
|
+
const caller = this.cpCollab.resolve(msg.orgId, platform, channel, msg.trustedFromAgentId);
|
|
215184
|
+
if (!target || !caller) {
|
|
215185
|
+
this.log.warn(`relay: rd/agentmsg/fwd terminal-verify failed (no placement) for ${msg.toAgentId} — fail closed`);
|
|
215186
|
+
return record(nak("not_allowed"));
|
|
215187
|
+
}
|
|
215188
|
+
if (target.callPolicy === "selected" && !target.allowedCallerAgentIds.includes(msg.trustedFromAgentId)) {
|
|
215189
|
+
this.log.info(`relay: rd/agentmsg/fwd not_allowed — ${msg.trustedFromAgentId} → ${msg.toAgentId} (selected)`);
|
|
215190
|
+
return record(nak("not_allowed"));
|
|
215191
|
+
}
|
|
215192
|
+
const callMeta = {
|
|
215193
|
+
callFrom: msg.trustedFromAgentId,
|
|
215194
|
+
...msg.correlationId !== void 0 ? { correlationId: msg.correlationId } : {},
|
|
215195
|
+
hopCount: msg.hopCount,
|
|
215196
|
+
deliveryId: msg.deliveryId
|
|
215197
|
+
};
|
|
215198
|
+
const narrowed = this.narrowPlatform(platform);
|
|
215199
|
+
const resolved = this.resolveCpAgent(msg.toAgentId);
|
|
215200
|
+
const integrationId = msg.integrationId ?? resolved?.integrationId;
|
|
215201
|
+
const normalized = {
|
|
215202
|
+
msgId: `agentcall:${channel}:${msg.deliveryId}`,
|
|
215203
|
+
traceId: msg.deliveryId,
|
|
215204
|
+
source: "agent",
|
|
215205
|
+
platform: narrowed,
|
|
215206
|
+
channel,
|
|
215207
|
+
...thread !== void 0 ? { thread } : {},
|
|
215208
|
+
sender: {
|
|
215209
|
+
id: msg.trustedFromAgentId,
|
|
215210
|
+
isBot: true
|
|
215211
|
+
},
|
|
215212
|
+
text: `@${msg.trustedFromAgentId} asked:\n${msg.text}`,
|
|
215213
|
+
mentionedBots: resolved?.botUserId ? [resolved.botUserId] : [],
|
|
215214
|
+
isDm: false
|
|
215215
|
+
};
|
|
215216
|
+
this.dispatch(msg.toAgentId, normalized, integrationId, void 0, callMeta).catch((err) => this.log.error(`relay agentmsg dispatch failed for agent "${msg.toAgentId}": ${formatErr(err)}`));
|
|
215217
|
+
this.log.info(`relay: rd/agentmsg/fwd ${msg.trustedFromAgentId} → ${msg.toAgentId} delivery=${msg.deliveryId}`);
|
|
215218
|
+
return record({
|
|
215219
|
+
deliveryId: msg.deliveryId,
|
|
215220
|
+
delivered: true
|
|
215221
|
+
});
|
|
215222
|
+
}
|
|
215030
215223
|
/**
|
|
215031
215224
|
* Same-daemon agent→agent delivery — the impl behind the `messageAgent` MCP tool
|
|
215032
215225
|
* (design §2.2/§2.3, P1). Wakes `toAgentId` by constructing a NormalizedMessage at
|
|
@@ -215061,11 +215254,16 @@ var Daemon = class Daemon {
|
|
|
215061
215254
|
return result;
|
|
215062
215255
|
};
|
|
215063
215256
|
const target = this.agents.get(req.toAgentId);
|
|
215064
|
-
if (!target)
|
|
215065
|
-
|
|
215066
|
-
|
|
215067
|
-
|
|
215068
|
-
|
|
215257
|
+
if (!target) {
|
|
215258
|
+
const coordPlatform = platform === "hook" ? "slack" : platform;
|
|
215259
|
+
return record(await this.routeAgentMsgCrossDaemon(req, {
|
|
215260
|
+
platform: coordPlatform,
|
|
215261
|
+
channel,
|
|
215262
|
+
thread,
|
|
215263
|
+
deliveryId,
|
|
215264
|
+
targetSession
|
|
215265
|
+
}));
|
|
215266
|
+
}
|
|
215069
215267
|
if (target.callPolicy === "selected" && !target.allowedCallerAgentIds.includes(req.callerAgentId)) {
|
|
215070
215268
|
this.log.info(`messageAgent: ${req.callerAgentId} not allowed to call ${req.toAgentId} (policy=selected)`);
|
|
215071
215269
|
return record({
|
|
@@ -215106,6 +215304,53 @@ var Daemon = class Daemon {
|
|
|
215106
215304
|
targetSession
|
|
215107
215305
|
});
|
|
215108
215306
|
}
|
|
215307
|
+
/**
|
|
215308
|
+
* Route a cross-daemon `messageAgent` over the relay data plane (agent-collaboration
|
|
215309
|
+
* §2.3/§6.2/§6.4, P2) and map the relay's typed admission verdict to a
|
|
215310
|
+
* {@link MessageAgentResult}. The `claimedFromAgentId` is our trusted caller — the
|
|
215311
|
+
* relay re-validates it against our authenticated daemonId (a forged claim is
|
|
215312
|
+
* rejected there). `hopCount` is the source turn's depth (0 in P1's minimal
|
|
215313
|
+
* cross-turn model); the relay increments it (+1, cap 8). The body never touches
|
|
215314
|
+
* the CP — only the relay.
|
|
215315
|
+
*/
|
|
215316
|
+
async routeAgentMsgCrossDaemon(req, ctx) {
|
|
215317
|
+
if (!this.relays) return {
|
|
215318
|
+
delivered: false,
|
|
215319
|
+
targetSession: ctx.targetSession,
|
|
215320
|
+
reason: "not_local"
|
|
215321
|
+
};
|
|
215322
|
+
try {
|
|
215323
|
+
const ack = await this.relays.sendAgentMsg({
|
|
215324
|
+
claimedFromAgentId: req.callerAgentId,
|
|
215325
|
+
toAgentId: req.toAgentId,
|
|
215326
|
+
text: req.text,
|
|
215327
|
+
coords: {
|
|
215328
|
+
platform: ctx.platform,
|
|
215329
|
+
channel: ctx.channel,
|
|
215330
|
+
...ctx.thread !== void 0 ? { thread: ctx.thread } : {}
|
|
215331
|
+
},
|
|
215332
|
+
...req.correlationId !== void 0 ? { correlationId: req.correlationId } : {},
|
|
215333
|
+
hopCount: 0,
|
|
215334
|
+
deliveryId: ctx.deliveryId
|
|
215335
|
+
});
|
|
215336
|
+
if (ack.delivered) return {
|
|
215337
|
+
delivered: true,
|
|
215338
|
+
targetSession: ctx.targetSession
|
|
215339
|
+
};
|
|
215340
|
+
return {
|
|
215341
|
+
delivered: false,
|
|
215342
|
+
targetSession: ctx.targetSession,
|
|
215343
|
+
...ack.reason ? { reason: ack.reason } : {}
|
|
215344
|
+
};
|
|
215345
|
+
} catch (err) {
|
|
215346
|
+
this.log.warn(`messageAgent: cross-daemon route failed for ${req.toAgentId}: ${formatErr(err)}`);
|
|
215347
|
+
return {
|
|
215348
|
+
delivered: false,
|
|
215349
|
+
targetSession: ctx.targetSession,
|
|
215350
|
+
reason: "offline"
|
|
215351
|
+
};
|
|
215352
|
+
}
|
|
215353
|
+
}
|
|
215109
215354
|
/** Narrow the trusted session-context platform string to the NormalizedMessage union;
|
|
215110
215355
|
* falls back to 'slack' for an unrecognized value (coords still resolve — the union is
|
|
215111
215356
|
* a routing/key detail, not the trust basis). */
|
|
@@ -215339,6 +215584,10 @@ var Daemon = class Daemon {
|
|
|
215339
215584
|
}
|
|
215340
215585
|
inflight = /* @__PURE__ */ new Set();
|
|
215341
215586
|
serialQueue = /* @__PURE__ */ new Map();
|
|
215587
|
+
/** §6.9 #353: stable ids of messages currently backed by a durable inbox row (admitted,
|
|
215588
|
+
* not yet terminal). Guards startup replay from re-admitting a row whose entry is already
|
|
215589
|
+
* live in the gate (idempotency — a duplicate/in-flight id is not double-processed). */
|
|
215590
|
+
liveInboxIds = /* @__PURE__ */ new Set();
|
|
215342
215591
|
/**
|
|
215343
215592
|
* Resolve a command's target from the channel's latest session when the routing ladder
|
|
215344
215593
|
* couldn't (no mention entity / thread / dm rule matched — e.g. a group `/status@bot`).
|
|
@@ -215670,6 +215919,49 @@ var Daemon = class Daemon {
|
|
|
215670
215919
|
}
|
|
215671
215920
|
}
|
|
215672
215921
|
/**
|
|
215922
|
+
* §6.9 #353 durable inbox: persist an ADMITTED entry BEFORE its admission ACK/return, so a
|
|
215923
|
+
* hard kill / agent move can't lose a message the caller was already told delivered:true.
|
|
215924
|
+
* Called ONLY on the two genuine admission branches in dispatch() (claim + enqueue), never
|
|
215925
|
+
* on a rejected/gate-dropped admission — a queue-full or paused/draining drop persists
|
|
215926
|
+
* nothing. WEBCHAT turns are skipped (§6.9 #367): their `sink` is a live in-memory transport
|
|
215927
|
+
* that can't be restored across a restart and a dead browser socket can't be resumed, so a
|
|
215928
|
+
* durable row would be un-replayable. Sets `entry.inboxId` so every terminal path can delete
|
|
215929
|
+
* the row. The row id is the message's stable deliveryId/msgId (§6.3) — idempotent re-append.
|
|
215930
|
+
*/
|
|
215931
|
+
persistInbox(entry, key) {
|
|
215932
|
+
if (entry.webchat) return;
|
|
215933
|
+
const id = entry.callMeta?.deliveryId ?? entry.msg.msgId;
|
|
215934
|
+
try {
|
|
215935
|
+
this.store.appendInbox({
|
|
215936
|
+
id,
|
|
215937
|
+
sessionKey: key,
|
|
215938
|
+
agentId: entry.agentId,
|
|
215939
|
+
msg: JSON.stringify(entry.msg),
|
|
215940
|
+
integrationId: entry.integrationId ?? null,
|
|
215941
|
+
callMeta: entry.callMeta ? JSON.stringify(entry.callMeta) : null,
|
|
215942
|
+
isQueueCmd: entry.isQueueCmd ? 1 : null,
|
|
215943
|
+
enqueuedAt: monotonicTs()
|
|
215944
|
+
});
|
|
215945
|
+
entry.inboxId = id;
|
|
215946
|
+
this.liveInboxIds.add(id);
|
|
215947
|
+
} catch (err) {
|
|
215948
|
+
this.log.warn(`durable inbox: append failed for ${id}: ${err.message}`);
|
|
215949
|
+
}
|
|
215950
|
+
}
|
|
215951
|
+
/** Remove an entry's durable inbox row once its turn reaches ANY terminal state (success,
|
|
215952
|
+
* reject/fail-stop, cancel, gate-drop) so it is not replayed on the next startup. No-op
|
|
215953
|
+
* for a non-persisted entry (webchat / never-admitted). NOT called on graceful-shutdown
|
|
215954
|
+
* settle: those admitted-but-unrun rows are intentionally LEFT for startup replay. */
|
|
215955
|
+
removeInbox(entry) {
|
|
215956
|
+
if (!entry.inboxId) return;
|
|
215957
|
+
this.liveInboxIds.delete(entry.inboxId);
|
|
215958
|
+
try {
|
|
215959
|
+
this.store.removeInbox(entry.inboxId);
|
|
215960
|
+
} catch (err) {
|
|
215961
|
+
this.log.warn(`durable inbox: remove failed for ${entry.inboxId}: ${err.message}`);
|
|
215962
|
+
}
|
|
215963
|
+
}
|
|
215964
|
+
/**
|
|
215673
215965
|
* Terminate a QUEUED entry's webchat sink when it is gate-dropped/rejected without ever
|
|
215674
215966
|
* entering dispatchOne (drain/pause gate-drop, fail-stop of the queued rest, `!cancel`/`!stop`
|
|
215675
215967
|
* queue drop, shutdown). A webchat turn signals its client via the WebchatSink terminal
|
|
@@ -215717,9 +216009,11 @@ var Daemon = class Daemon {
|
|
|
215717
216009
|
}
|
|
215718
216010
|
q.push(entry);
|
|
215719
216011
|
this.serialQueue.set(key, q);
|
|
216012
|
+
this.persistInbox(entry, key);
|
|
215720
216013
|
this.log.debug(`dispatch: queued behind in-flight turn for session ${key} (depth ${q.length})`);
|
|
215721
216014
|
return;
|
|
215722
216015
|
}
|
|
216016
|
+
this.persistInbox(entry, key);
|
|
215723
216017
|
this.inflight.add(key);
|
|
215724
216018
|
this.runLoop(key, entry);
|
|
215725
216019
|
});
|
|
@@ -215741,11 +216035,13 @@ var Daemon = class Daemon {
|
|
|
215741
216035
|
while (entry) {
|
|
215742
216036
|
if (entry !== firstEntry && (this.draining || this.drainingAgents.has(entry.agentId) || this.paused(entry.agentId))) {
|
|
215743
216037
|
this.terminateQueuedSink(entry);
|
|
216038
|
+
if (!this.draining) this.removeInbox(entry);
|
|
215744
216039
|
entry.resolve(null);
|
|
215745
216040
|
const rest = this.serialQueue.get(key) ?? [];
|
|
215746
216041
|
this.serialQueue.delete(key);
|
|
215747
216042
|
for (const e of rest) {
|
|
215748
216043
|
this.terminateQueuedSink(e);
|
|
216044
|
+
if (!this.draining) this.removeInbox(e);
|
|
215749
216045
|
e.resolve(null);
|
|
215750
216046
|
}
|
|
215751
216047
|
this.inflight.delete(key);
|
|
@@ -215753,14 +216049,17 @@ var Daemon = class Daemon {
|
|
|
215753
216049
|
}
|
|
215754
216050
|
try {
|
|
215755
216051
|
const sessionId = await this.dispatchOne(entry, key);
|
|
216052
|
+
this.removeInbox(entry);
|
|
215756
216053
|
entry.resolve(sessionId);
|
|
215757
216054
|
} catch (err) {
|
|
216055
|
+
if (!this.draining) this.removeInbox(entry);
|
|
215758
216056
|
entry.reject(err);
|
|
215759
216057
|
const rest = this.serialQueue.get(key) ?? [];
|
|
215760
216058
|
this.serialQueue.delete(key);
|
|
215761
216059
|
for (const e of rest) {
|
|
215762
216060
|
const failStop = new FailStopError(key);
|
|
215763
216061
|
this.terminateQueuedSink(e, failStop.message);
|
|
216062
|
+
if (!this.draining) this.removeInbox(e);
|
|
215764
216063
|
e.reject(failStop);
|
|
215765
216064
|
}
|
|
215766
216065
|
this.inflight.delete(key);
|
|
@@ -215932,6 +216231,7 @@ var Daemon = class Daemon {
|
|
|
215932
216231
|
this.serialQueue.delete(key);
|
|
215933
216232
|
for (const e of queued) {
|
|
215934
216233
|
this.terminateQueuedSink(e, reason);
|
|
216234
|
+
this.removeInbox(e);
|
|
215935
216235
|
e.reject(new FailStopError(key));
|
|
215936
216236
|
}
|
|
215937
216237
|
}
|
|
@@ -216870,6 +217170,49 @@ var Daemon = class Daemon {
|
|
|
216870
217170
|
}
|
|
216871
217171
|
this.serialQueue.clear();
|
|
216872
217172
|
}
|
|
217173
|
+
/**
|
|
217174
|
+
* §6.9 #353 startup replay: re-admit durably-persisted admitted-but-not-completed inbox rows
|
|
217175
|
+
* through the SAME serial gate (`dispatch`), FIFO-by-sessionKey (rows come back ordered by
|
|
217176
|
+
* (sessionKey, enqueuedAt), so order within a sessionKey is preserved and the gate keeps them
|
|
217177
|
+
* serial). Runs once on start() after the store is open, agents are loaded into `this.agents`,
|
|
217178
|
+
* and platform connections/crons are up (so a re-admitted turn has its reply transport), as
|
|
217179
|
+
* normal inbound begins. Idempotency: a row whose id is already live in the gate (`liveInboxIds`)
|
|
217180
|
+
* is skipped — dispatch re-appends the same id with INSERT OR IGNORE so no double row is written,
|
|
217181
|
+
* and the re-admitted entry adopts the existing row for later removal. Rows for an agent that no
|
|
217182
|
+
* longer exists on this daemon are SKIPPED + logged (the simplest choice — another owner/daemon
|
|
217183
|
+
* may hold that agent; we neither drop the row nor block on it). Webchat turns were never
|
|
217184
|
+
* persisted, so none appear here.
|
|
217185
|
+
*/
|
|
217186
|
+
replayInbox() {
|
|
217187
|
+
let rows;
|
|
217188
|
+
try {
|
|
217189
|
+
rows = this.store.listInboxBySessionKeyFifo();
|
|
217190
|
+
} catch (err) {
|
|
217191
|
+
this.log.warn(`durable inbox: replay read failed: ${err.message}`);
|
|
217192
|
+
return;
|
|
217193
|
+
}
|
|
217194
|
+
if (rows.length === 0) return;
|
|
217195
|
+
let replayed = 0;
|
|
217196
|
+
for (const row of rows) {
|
|
217197
|
+
if (this.liveInboxIds.has(row.id)) continue;
|
|
217198
|
+
if (!this.agents.has(row.agentId)) {
|
|
217199
|
+
this.log.warn(`durable inbox: skipping replay of ${row.id} — unknown agent "${row.agentId}"`);
|
|
217200
|
+
continue;
|
|
217201
|
+
}
|
|
217202
|
+
let msg;
|
|
217203
|
+
let callMeta;
|
|
217204
|
+
try {
|
|
217205
|
+
msg = JSON.parse(row.msg);
|
|
217206
|
+
callMeta = row.callMeta ? JSON.parse(row.callMeta) : void 0;
|
|
217207
|
+
} catch (err) {
|
|
217208
|
+
this.log.warn(`durable inbox: skipping corrupt row ${row.id}: ${err.message}`);
|
|
217209
|
+
continue;
|
|
217210
|
+
}
|
|
217211
|
+
this.dispatch(row.agentId, msg, row.integrationId ?? void 0, void 0, callMeta, row.isQueueCmd ? { isQueueCmd: true } : void 0).catch(() => {});
|
|
217212
|
+
replayed++;
|
|
217213
|
+
}
|
|
217214
|
+
if (replayed) this.log.info(`durable inbox: replayed ${replayed} admitted message(s) through the serial gate`);
|
|
217215
|
+
}
|
|
216873
217216
|
/** daemon/restart + daemon/upgrade (§8.3): ack now, then drain + stop + exit so
|
|
216874
217217
|
* the supervisor relaunches (the new binary, for upgrade). */
|
|
216875
217218
|
scheduleFleetExit(kind, targetVersion) {
|
|
@@ -216901,6 +217244,7 @@ var Daemon = class Daemon {
|
|
|
216901
217244
|
this.cpAgents?.converge(snap.agents);
|
|
216902
217245
|
this.cpIntegrations?.converge(snap.integrations);
|
|
216903
217246
|
this.convergeRelays(snap.relays);
|
|
217247
|
+
this.cpCollab.replace(snap.collabRoutes);
|
|
216904
217248
|
for (const id of snap.drop.crons) this.cpCrons?.remove(id);
|
|
216905
217249
|
this.cpCrons?.converge(snap.crons);
|
|
216906
217250
|
this.cpRouting?.converge({
|
|
@@ -216929,6 +217273,7 @@ var Daemon = class Daemon {
|
|
|
216929
217273
|
applyRouteAssign: (a) => this.cpRouting?.upsertAssign(a),
|
|
216930
217274
|
applyRouteUpdate: (u) => this.cpRouting?.applyUpdate(u),
|
|
216931
217275
|
applyRelayRoster: (relays) => this.convergeRelays(relays),
|
|
217276
|
+
applyCollabRoutes: (snap) => this.cpCollab.replace(snap),
|
|
216932
217277
|
applyAgentLaunch: async (launch) => {
|
|
216933
217278
|
const agent = this.agents.get(launch.agentId);
|
|
216934
217279
|
if (!agent) throw new Error(`agent/launch: unknown agent ${launch.agentId}`);
|
|
@@ -217065,7 +217410,8 @@ var Daemon = class Daemon {
|
|
|
217065
217410
|
path: RELAY_DAEMON_WS_PATH
|
|
217066
217411
|
}),
|
|
217067
217412
|
log: this.log,
|
|
217068
|
-
onRelayMsg: (msg, chat) => this.handleRelayMsg(msg, chat)
|
|
217413
|
+
onRelayMsg: (msg, chat) => this.handleRelayMsg(msg, chat),
|
|
217414
|
+
onRelayAgentMsg: (msg) => this.handleRelayAgentMsg(msg)
|
|
217069
217415
|
});
|
|
217070
217416
|
if (this.cfg.relays.length) this.relays.converge(this.cfg.relays);
|
|
217071
217417
|
this.cpClient = new CpClient({
|
|
@@ -217272,4 +217618,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
217272
217618
|
//#endregion
|
|
217273
217619
|
export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
|
|
217274
217620
|
|
|
217275
|
-
//# sourceMappingURL=run-foreground-
|
|
217621
|
+
//# sourceMappingURL=run-foreground-Cennaxiw.js.map
|