@agentconnect.md/daemon 1.0.0-rc.281 → 1.0.0-rc.282
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-OT3DwN02.js} +3 -3
- package/dist/{login-BPKCyqR_.js.map → login-OT3DwN02.js.map} +1 -1
- package/dist/{run-foreground-BBBkQ93M.js → run-foreground-BV2scayf.js} +211 -10
- package/dist/{run-foreground-BBBkQ93M.js.map → run-foreground-BV2scayf.js.map} +1 -1
- package/dist/run-foreground-Cz_WXUfg.js +2 -0
- 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-OT3DwN02.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":";;;;;;;;;;;;;;;;;AA2BA,eAAsB,UAAU,MAAuC;CACrE,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,OAAO,KAAK,kBAAkB,gBAAgB,KAAK,KAAK,KAAK;EAAE,aAAA;EAA6B,MAAA;CAAiB,CAAC;CAEpH,IAAI;CACJ,IAAI;EACF,YAAY,MAAM,KAAK;CACzB,SAAS,KAAK;EACZ,OAAO;GAAE,IAAI;GAAO,QAAQ,gBAAgB,KAAK,IAAI,IAAK,IAAc;EAAU;CACpF;CAEA,MAAM,cAAuC;EAC3C,QAAQ,KAAK;EACb,cAAc,KAAK,gBAAgB;CACrC;CACA,IAAI,KAAK,UAAU,YAAY,WAAW,KAAK;CAC/C,MAAM,QAAQ,cAAc,QAAQ,WAAW;CAE/C,OAAO,MAAM,IAAI,SAAsB,YAAY;EACjD,IAAI,UAAU;EACd,MAAM,QAAQ,MAAyB;GACrC,IAAI,SAAS;GACb,UAAU;GACV,aAAa,KAAK;GAClB,IAAI;IACF,UAAU,MAAM,KAAM,YAAY;GACpC,QAAQ,CAER;GACA,QAAQ,CAAC;EACX;EAEA,MAAM,QAAQ,iBACN,KAAK;GAAE,IAAI;GAAO,QAAQ,mBAAmB,UAAU;EAAwB,CAAC,GACtF,SACF;EAEA,UAAU,WAAW,SAAS;GAC5B,MAAM,UAAU,eAAe,IAAI;GACnC,IAAI,CAAC,QAAQ,IAAI;GACjB,MAAM,IAAI,QAAQ;GAClB,IAAI,EAAE,SAAS,MAAM,IAAI;GACzB,IAAI,EAAE,SAAS,SAAS;IACtB,MAAM,IAAI,EAAE;IACZ,KAAK;KAAE,IAAI;KAAO,QAAQ,GAAG,EAAE,KAAK,IAAI,EAAE;IAAU,CAAC;IACrD;GACF;GACA,IAAI,QAAQ,SAAS,CAAC,CAAC,CAAC,GAAG;IACzB,MAAM,KAAK,EAAE;IACb,KAAK;KAAE,IAAI;KAAM,UAAU,GAAG;IAAS,CAAC;GAC1C;EACF,CAAC;EAED,UAAU,SAAS,SAAS;GAC1B,IAAI,SAAS,MAAM,KAAK;IAAE,IAAI;IAAO,QAAQ;GAAwD,CAAC;QACjG,KAAK;IAAE,IAAI;IAAO,QAAQ,sBAAsB,KAAK;GAAkB,CAAC;EAC/E,CAAC;EAED,UAAU,KAAK,OAAO,KAAK,CAAC;CAC9B,CAAC;AACH;;;;;;;;;;;;;;ACzDA,SAAgB,mBAAmB,MAAgC;CACjE,MAAM,OAAO,YAAY,KAAK,IAAI;CAClC,MAAM,OAAO,KAAK,cAAc,WAAW,IAAI;CAC/C,MAAM,MAA+B,WAAW,IAAI,IAAI,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE;CAG9G,IAAI,eAAe;EAAE,GADT,IAAI,gBAAwD,CAAC;EAC7C,KAAK,KAAK;EAAO,KAAK,KAAK;EAAO,SAAS;CAAK;CAC5E,IAAI,KAAK,UAAU,IAAI,WAAW,KAAK;CAEvC,aAAa,MAAM,GAAG;CAEtB,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CAC5C,cAAc,MAAM,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI,IAAI;CACvD,OAAO;AACT;;;;;;;;;AAwBA,SAAgB,iBAAiB,MAAyD;CACxF,OAAO;EACL,UAAU,QAAQ;EAClB,UAAU,KAAK,YAAY,cAAc,OAAO,KAAK,GAAG;EACxD,gBAAgB,YAAY,KAAK,IAAI,MAAM,YAAY,KAAA,CAAS;CAClE;AACF;AAWA,SAAS,SAAS,MAA+B;CAC/C,OAAO;EACL,QAAQ,MAAM,UAAU;GAAE,KAAK,EAAE;GAAK,OAAO,EAAE;GAAO,cAAc;EAAe,CAAC;EACpF,gBAAgB,YAAY;GAC1B,MAAM,aAAa,kBAAkB,EAAE,MAAM,KAAK,KAAK,CAAC;GACxD,MAAM,WAAW,QAAQ,iBAAiB,IAAI,CAAC;GAC/C,MAAM,WAAW,GAAG;EACtB;EACA,qBACEA,cAAkB;GAChB,MAAM,KAAK;GACX,WAAW;IAAE,OAAO,KAAK;IAAO,OAAO,KAAK;IAAO,UAAU,KAAK;GAAS;EAC7E,CAAC;EACH,KAAK,QAAQ;EACb,OAAO,QAAQ;EACf,OAAO,QAAQ,QAAQ,MAAM,KAAK;CACpC;AACF;;AAGA,eAAe,SACb,MACA,QACA,KACiB;CACjB,IAAI,MAAM,MAAM;CAChB,MAAM,EAAE,OAAO,SAAS,MAAM,KAAK,KAAK;CACxC,IAAI,MAAM,MAAM,IAAI,MAAM,wCAAwC,QAAQ;CAC1E,OAAQ,MAAiB,KAAK;AAChC;AAEA,eAAsB,SAAS,MAAoB,UAA8B,CAAC,GAAkB;CAClG,MAAM,OAAkB;EAAE,GAAG,SAAS,IAAI;EAAG,GAAG;CAAQ;CACxD,MAAM,EAAE,QAAQ;CAGhB,IAAI,CAAC,KAAK,OAAO;EACf,IAAI,CAAC,KAAK,OAAO,MAAM,IAAI,MAAM,+BAA+B;EAChE,IAAI,CAAC,KAAK,OAAO,MAAM,IAAI,MAAM,+BAA+B;EAChE,MAAM,IAAI,MAAM,KAAK,MAAM;GAAE,KAAK,KAAK;GAAO,OAAO,KAAK;EAAM,CAAC;EACjE,IAAI,CAAC,EAAE,IAAI,MAAM,IAAI,MAAM,0BAA0B,EAAE,QAAQ;EAC/D,mBAAmB;GAAE,GAAG;GAAM,OAAO,KAAK;GAAO,OAAO,KAAK;EAAM,CAAC;EACpE,IAAI,MAAM,kBAAkB,EAAE,WAAW,YAAY,EAAE,SAAS,KAAK,GAAG,uBAAuB;EAC/F;CACF;CAOA,IAAI,KAAK,YACP,MAAM,IAAI,MACR,6HACF;CAIF,MAAM,KAAK,gBAAgB;EAAE,OAAO,KAAK;EAAO,QAAQ;CAAI,CAAC;CAC7D,MAAM,OAAO,GAAG,OAAO,cAAc,CAAC;CACtC,IAAI,MAAM,KAAK;CACf,IAAI,QAAQ,KAAK;CACjB,IAAI;EACF,IAAI,CAAC,KAAK,MAAM,MAAM,SAAS,MAAM,uBAAuB,GAAG;EAE/D,KAAK,IAAI,UAAU,IAAK,WAAW;GACjC,IAAI,CAAC,OAAO,QAAQ,MAAM,SAAS,MAAM,oBAAoB,GAAG;GAChE,IAAI,MAAM,yBAAyB,IAAI,IAAI;GAC3C,MAAM,IAAI,MAAM,KAAK,MAAM;IAAO;IAAa;GAAO,CAAC;GACvD,IAAI,EAAE,IAAI;IACR,IAAI,MAAM,kBAAkB,EAAE,WAAW,YAAY,EAAE,SAAS,KAAK,GAAG,GAAG;IAC3E;GACF;GACA,IAAI,MAAM,KAAK,EAAE,OAAO,GAAG;GAC3B,IAAI,WAAW,GAAG,MAAM,IAAI,MAAM,uBAAuB;GACzD,QAAQ,KAAA;EACV;EAEA,mBAAmB;GAAE,GAAG;GAAM,OAAO;GAAM,OAAO;EAAO,CAAC;EAE1D,MAAM,OAAO,MAAM,SAAS,MAAM,wDAAwD,GAAG,EAAA,CAAG,YAAY;EAC5G,IAAI,QAAQ,OAAO,QAAQ,OAAO;GAChC,MAAM,KAAK,eAAe;GAC1B,IAAI,MAAM,wFAAwF;GAClG;EACF;EACA,IAAI,MAAM,gDAAgD;CAC5D,UAAU;EACR,GAAG,MAAM;CACX;CAEA,MAAM,KAAK,cAAc;AAC3B"}
|
|
@@ -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);
|
|
@@ -197213,6 +197216,18 @@ var RelayClient = class {
|
|
|
197213
197216
|
isReady() {
|
|
197214
197217
|
return this.state === "READY";
|
|
197215
197218
|
}
|
|
197219
|
+
/**
|
|
197220
|
+
* Send a cross-daemon agent-call to the relay (`rd/agentmsg` REQ, agent-collaboration
|
|
197221
|
+
* §2.3/§6.4) and resolve with the relay's admission verdict. `claimedFromAgentId` is
|
|
197222
|
+
* this daemon's trusted caller — the relay re-validates it against the socket's
|
|
197223
|
+
* authenticated daemonId. Rejects (retryable) if the socket isn't READY.
|
|
197224
|
+
*/
|
|
197225
|
+
async sendAgentMsg(payload) {
|
|
197226
|
+
if (this.state !== "READY") throw new WireError("INTERNAL", `rd link not ready (${this.state})`, true);
|
|
197227
|
+
const rep = await this.sendRequest(buildRelayDaemonFrame("rd/agentmsg", payload));
|
|
197228
|
+
if (rep.type !== "rd/agentmsg/ack") throw new WireError("INTERNAL", `expected rd/agentmsg/ack, got ${rep.type}`, false);
|
|
197229
|
+
return rep.payload;
|
|
197230
|
+
}
|
|
197216
197231
|
async attemptConnect() {
|
|
197217
197232
|
if (this.stopped || this.fatal) return;
|
|
197218
197233
|
const daemonId = this.deps.daemonId();
|
|
@@ -197264,6 +197279,10 @@ var RelayClient = class {
|
|
|
197264
197279
|
this.handleMsg(frame.id, frame.payload);
|
|
197265
197280
|
return;
|
|
197266
197281
|
}
|
|
197282
|
+
if (frame.type === "rd/agentmsg/fwd") {
|
|
197283
|
+
this.handleAgentMsgFwd(frame.id, frame.payload);
|
|
197284
|
+
return;
|
|
197285
|
+
}
|
|
197267
197286
|
this.deps.log.debug(`relay(${this.relayId}): ignoring ${frame.type}`);
|
|
197268
197287
|
}
|
|
197269
197288
|
/** An inbound rd/msg: dispatch it, reply `rd/ack`; only a webchat op streams
|
|
@@ -197274,7 +197293,22 @@ var RelayClient = class {
|
|
|
197274
197293
|
const ack = this.deps.onRelayMsg(msg, chat);
|
|
197275
197294
|
this.transport?.send(JSON.stringify(buildRelayDaemonFrame("rd/ack", ack, { corr: reqId })));
|
|
197276
197295
|
}
|
|
197277
|
-
/**
|
|
197296
|
+
/** A forwarded cross-daemon agent-call: terminal-verify + dispatch, reply the typed
|
|
197297
|
+
* admission verdict (`rd/agentmsg/ack`, corr = the relay's fwd id). */
|
|
197298
|
+
async handleAgentMsgFwd(reqId, msg) {
|
|
197299
|
+
let ack;
|
|
197300
|
+
try {
|
|
197301
|
+
ack = await this.deps.onRelayAgentMsg(msg);
|
|
197302
|
+
} catch (err) {
|
|
197303
|
+
this.deps.log.error(`relay(${this.relayId}): rd/agentmsg/fwd handler threw: ${err.message}`);
|
|
197304
|
+
ack = {
|
|
197305
|
+
deliveryId: msg.deliveryId,
|
|
197306
|
+
delivered: false,
|
|
197307
|
+
reason: "offline"
|
|
197308
|
+
};
|
|
197309
|
+
}
|
|
197310
|
+
this.transport?.send(JSON.stringify(buildRelayDaemonFrame("rd/agentmsg/ack", ack, { corr: reqId })));
|
|
197311
|
+
}
|
|
197278
197312
|
sendChat(chatId, event) {
|
|
197279
197313
|
const seq = (this.seqByChat.get(chatId) ?? 0) + 1;
|
|
197280
197314
|
this.seqByChat.set(chatId, seq);
|
|
@@ -197346,6 +197380,45 @@ var RelayManager = class {
|
|
|
197346
197380
|
size() {
|
|
197347
197381
|
return this.clients.size;
|
|
197348
197382
|
}
|
|
197383
|
+
/**
|
|
197384
|
+
* Send a cross-daemon agent-call over ANY READY relay (agent-collaboration §2.3/§6.4).
|
|
197385
|
+
* The all-to-all roster means any relay this daemon holds can route the call to the
|
|
197386
|
+
* target's owning daemon via the CP's collaboration snapshot — so we pick the first
|
|
197387
|
+
* READY client. Throws if none is READY (the caller reports the call undeliverable).
|
|
197388
|
+
*/
|
|
197389
|
+
async sendAgentMsg(payload) {
|
|
197390
|
+
for (const client of this.clients.values()) if (client.isReady()) return client.sendAgentMsg(payload);
|
|
197391
|
+
throw new Error("no READY relay to route agent-call");
|
|
197392
|
+
}
|
|
197393
|
+
};
|
|
197394
|
+
//#endregion
|
|
197395
|
+
//#region src/cp/cp-collab-routes.ts
|
|
197396
|
+
var CpCollabRoutes = class {
|
|
197397
|
+
generation = -1;
|
|
197398
|
+
channels = /* @__PURE__ */ new Map();
|
|
197399
|
+
key(orgId, platform, channelId) {
|
|
197400
|
+
return orgId + "\0" + platform + "\0" + channelId;
|
|
197401
|
+
}
|
|
197402
|
+
/** FULL-REPLACE from a CP snapshot (converge-don't-diff); ignore an older generation.
|
|
197403
|
+
* Tolerates an absent snapshot (a pre-collab CP / hand-built test fixture) as a no-op. */
|
|
197404
|
+
replace(snap) {
|
|
197405
|
+
if (!snap) return;
|
|
197406
|
+
if (snap.generation < this.generation) return;
|
|
197407
|
+
this.generation = snap.generation;
|
|
197408
|
+
this.channels.clear();
|
|
197409
|
+
for (const ch of snap.channels) {
|
|
197410
|
+
const byAgent = /* @__PURE__ */ new Map();
|
|
197411
|
+
for (const a of ch.agents) byAgent.set(a.agentId, {
|
|
197412
|
+
...a,
|
|
197413
|
+
orgId: ch.orgId
|
|
197414
|
+
});
|
|
197415
|
+
this.channels.set(this.key(ch.orgId, ch.platform, ch.channelId), byAgent);
|
|
197416
|
+
}
|
|
197417
|
+
}
|
|
197418
|
+
/** The placement of `agentId` in `(orgId,platform,channel)`, or undefined. */
|
|
197419
|
+
resolve(orgId, platform, channelId, agentId) {
|
|
197420
|
+
return this.channels.get(this.key(orgId, platform, channelId))?.get(agentId);
|
|
197421
|
+
}
|
|
197349
197422
|
};
|
|
197350
197423
|
//#endregion
|
|
197351
197424
|
//#region src/slack/permalink.ts
|
|
@@ -213917,6 +213990,10 @@ const IDLE_FLUSH_MS = 2e3;
|
|
|
213917
213990
|
/** Public Web App console origin used for session deep links when neither a local
|
|
213918
213991
|
* `webAppUrl` config nor a CP-provided one is set — so "View session" always resolves. */
|
|
213919
213992
|
const DEFAULT_WEB_APP_URL = "https://app.agentconnect.md";
|
|
213993
|
+
/** Per-in-flight-turn rendering state, keyed by ACP sessionId in `this.pending`. */
|
|
213994
|
+
/** Cap on agent→agent hop depth (design §2.4/§4.5) — reject a `messageAgent` that
|
|
213995
|
+
* would push the outgoing hopCount past this, so an A↔B wake loop can't run away. */
|
|
213996
|
+
const MAX_AGENT_CALL_HOPS = 8;
|
|
213920
213997
|
/** Build the wire SessionKey (protocol §5) for a pending turn — what `drain/done`
|
|
213921
213998
|
* reports as released so the CP may reassign it. Uses the real `thread` (absent for
|
|
213922
213999
|
* a channel-root message), NOT `statusThread` (which falls back to msgId): the CP
|
|
@@ -213992,6 +214069,7 @@ var Daemon = class Daemon {
|
|
|
213992
214069
|
cpIntegrations;
|
|
213993
214070
|
botUserIds = {};
|
|
213994
214071
|
cpRouting;
|
|
214072
|
+
cpCollab = new CpCollabRoutes();
|
|
213995
214073
|
clock;
|
|
213996
214074
|
requestExit;
|
|
213997
214075
|
draining = false;
|
|
@@ -215027,6 +215105,74 @@ var Daemon = class Daemon {
|
|
|
215027
215105
|
accepted: true
|
|
215028
215106
|
};
|
|
215029
215107
|
}
|
|
215108
|
+
relayAgentMsgAcks = /* @__PURE__ */ new Map();
|
|
215109
|
+
/**
|
|
215110
|
+
* TARGET side of a cross-daemon `messageAgent` (agent-collaboration §2.3/§6.2, P2) —
|
|
215111
|
+
* the mirror of {@link handleRelayIm} for a relay-forwarded `rd/agentmsg/fwd`. The
|
|
215112
|
+
* relay already validated the caller and minted a TRUSTED claim
|
|
215113
|
+
* (`trustedFromAgentId` + org/channel assertion), but we TERMINAL-VERIFY it against
|
|
215114
|
+
* our OWN local snapshot/spec (defense in depth, §2.5 #4) before dispatching — never
|
|
215115
|
+
* blindly trusting the relay. The ACK is returned on durable ADMISSION (the P4-gate
|
|
215116
|
+
* enqueue), NOT after the model turn (§6.4).
|
|
215117
|
+
*/
|
|
215118
|
+
async handleRelayAgentMsg(msg) {
|
|
215119
|
+
const nak = (reason) => ({
|
|
215120
|
+
deliveryId: msg.deliveryId,
|
|
215121
|
+
delivered: false,
|
|
215122
|
+
...reason ? { reason } : {}
|
|
215123
|
+
});
|
|
215124
|
+
const dedupKey = `${msg.trustedFromAgentId}:${msg.deliveryId}`;
|
|
215125
|
+
const prior = this.relayAgentMsgAcks.get(dedupKey);
|
|
215126
|
+
if (prior) return prior;
|
|
215127
|
+
const record = (ack) => {
|
|
215128
|
+
if (this.relayAgentMsgAcks.size >= 2e3) this.relayAgentMsgAcks.clear();
|
|
215129
|
+
this.relayAgentMsgAcks.set(dedupKey, ack);
|
|
215130
|
+
return ack;
|
|
215131
|
+
};
|
|
215132
|
+
if (msg.hopCount > MAX_AGENT_CALL_HOPS) return record(nak("hop_limit"));
|
|
215133
|
+
const { platform, channel, thread } = msg.coords;
|
|
215134
|
+
if (!this.agents.get(msg.toAgentId)) return record(nak("not_found"));
|
|
215135
|
+
const target = this.cpCollab.resolve(msg.orgId, platform, channel, msg.toAgentId);
|
|
215136
|
+
const caller = this.cpCollab.resolve(msg.orgId, platform, channel, msg.trustedFromAgentId);
|
|
215137
|
+
if (!target || !caller) {
|
|
215138
|
+
this.log.warn(`relay: rd/agentmsg/fwd terminal-verify failed (no placement) for ${msg.toAgentId} — fail closed`);
|
|
215139
|
+
return record(nak("not_allowed"));
|
|
215140
|
+
}
|
|
215141
|
+
if (target.callPolicy === "selected" && !target.allowedCallerAgentIds.includes(msg.trustedFromAgentId)) {
|
|
215142
|
+
this.log.info(`relay: rd/agentmsg/fwd not_allowed — ${msg.trustedFromAgentId} → ${msg.toAgentId} (selected)`);
|
|
215143
|
+
return record(nak("not_allowed"));
|
|
215144
|
+
}
|
|
215145
|
+
const callMeta = {
|
|
215146
|
+
callFrom: msg.trustedFromAgentId,
|
|
215147
|
+
...msg.correlationId !== void 0 ? { correlationId: msg.correlationId } : {},
|
|
215148
|
+
hopCount: msg.hopCount,
|
|
215149
|
+
deliveryId: msg.deliveryId
|
|
215150
|
+
};
|
|
215151
|
+
const narrowed = this.narrowPlatform(platform);
|
|
215152
|
+
const resolved = this.resolveCpAgent(msg.toAgentId);
|
|
215153
|
+
const integrationId = msg.integrationId ?? resolved?.integrationId;
|
|
215154
|
+
const normalized = {
|
|
215155
|
+
msgId: `agentcall:${channel}:${msg.deliveryId}`,
|
|
215156
|
+
traceId: msg.deliveryId,
|
|
215157
|
+
source: "agent",
|
|
215158
|
+
platform: narrowed,
|
|
215159
|
+
channel,
|
|
215160
|
+
...thread !== void 0 ? { thread } : {},
|
|
215161
|
+
sender: {
|
|
215162
|
+
id: msg.trustedFromAgentId,
|
|
215163
|
+
isBot: true
|
|
215164
|
+
},
|
|
215165
|
+
text: `@${msg.trustedFromAgentId} asked:\n${msg.text}`,
|
|
215166
|
+
mentionedBots: resolved?.botUserId ? [resolved.botUserId] : [],
|
|
215167
|
+
isDm: false
|
|
215168
|
+
};
|
|
215169
|
+
this.dispatch(msg.toAgentId, normalized, integrationId, void 0, callMeta).catch((err) => this.log.error(`relay agentmsg dispatch failed for agent "${msg.toAgentId}": ${formatErr(err)}`));
|
|
215170
|
+
this.log.info(`relay: rd/agentmsg/fwd ${msg.trustedFromAgentId} → ${msg.toAgentId} delivery=${msg.deliveryId}`);
|
|
215171
|
+
return record({
|
|
215172
|
+
deliveryId: msg.deliveryId,
|
|
215173
|
+
delivered: true
|
|
215174
|
+
});
|
|
215175
|
+
}
|
|
215030
215176
|
/**
|
|
215031
215177
|
* Same-daemon agent→agent delivery — the impl behind the `messageAgent` MCP tool
|
|
215032
215178
|
* (design §2.2/§2.3, P1). Wakes `toAgentId` by constructing a NormalizedMessage at
|
|
@@ -215061,11 +215207,16 @@ var Daemon = class Daemon {
|
|
|
215061
215207
|
return result;
|
|
215062
215208
|
};
|
|
215063
215209
|
const target = this.agents.get(req.toAgentId);
|
|
215064
|
-
if (!target)
|
|
215065
|
-
|
|
215066
|
-
|
|
215067
|
-
|
|
215068
|
-
|
|
215210
|
+
if (!target) {
|
|
215211
|
+
const coordPlatform = platform === "hook" ? "slack" : platform;
|
|
215212
|
+
return record(await this.routeAgentMsgCrossDaemon(req, {
|
|
215213
|
+
platform: coordPlatform,
|
|
215214
|
+
channel,
|
|
215215
|
+
thread,
|
|
215216
|
+
deliveryId,
|
|
215217
|
+
targetSession
|
|
215218
|
+
}));
|
|
215219
|
+
}
|
|
215069
215220
|
if (target.callPolicy === "selected" && !target.allowedCallerAgentIds.includes(req.callerAgentId)) {
|
|
215070
215221
|
this.log.info(`messageAgent: ${req.callerAgentId} not allowed to call ${req.toAgentId} (policy=selected)`);
|
|
215071
215222
|
return record({
|
|
@@ -215106,6 +215257,53 @@ var Daemon = class Daemon {
|
|
|
215106
215257
|
targetSession
|
|
215107
215258
|
});
|
|
215108
215259
|
}
|
|
215260
|
+
/**
|
|
215261
|
+
* Route a cross-daemon `messageAgent` over the relay data plane (agent-collaboration
|
|
215262
|
+
* §2.3/§6.2/§6.4, P2) and map the relay's typed admission verdict to a
|
|
215263
|
+
* {@link MessageAgentResult}. The `claimedFromAgentId` is our trusted caller — the
|
|
215264
|
+
* relay re-validates it against our authenticated daemonId (a forged claim is
|
|
215265
|
+
* rejected there). `hopCount` is the source turn's depth (0 in P1's minimal
|
|
215266
|
+
* cross-turn model); the relay increments it (+1, cap 8). The body never touches
|
|
215267
|
+
* the CP — only the relay.
|
|
215268
|
+
*/
|
|
215269
|
+
async routeAgentMsgCrossDaemon(req, ctx) {
|
|
215270
|
+
if (!this.relays) return {
|
|
215271
|
+
delivered: false,
|
|
215272
|
+
targetSession: ctx.targetSession,
|
|
215273
|
+
reason: "not_local"
|
|
215274
|
+
};
|
|
215275
|
+
try {
|
|
215276
|
+
const ack = await this.relays.sendAgentMsg({
|
|
215277
|
+
claimedFromAgentId: req.callerAgentId,
|
|
215278
|
+
toAgentId: req.toAgentId,
|
|
215279
|
+
text: req.text,
|
|
215280
|
+
coords: {
|
|
215281
|
+
platform: ctx.platform,
|
|
215282
|
+
channel: ctx.channel,
|
|
215283
|
+
...ctx.thread !== void 0 ? { thread: ctx.thread } : {}
|
|
215284
|
+
},
|
|
215285
|
+
...req.correlationId !== void 0 ? { correlationId: req.correlationId } : {},
|
|
215286
|
+
hopCount: 0,
|
|
215287
|
+
deliveryId: ctx.deliveryId
|
|
215288
|
+
});
|
|
215289
|
+
if (ack.delivered) return {
|
|
215290
|
+
delivered: true,
|
|
215291
|
+
targetSession: ctx.targetSession
|
|
215292
|
+
};
|
|
215293
|
+
return {
|
|
215294
|
+
delivered: false,
|
|
215295
|
+
targetSession: ctx.targetSession,
|
|
215296
|
+
...ack.reason ? { reason: ack.reason } : {}
|
|
215297
|
+
};
|
|
215298
|
+
} catch (err) {
|
|
215299
|
+
this.log.warn(`messageAgent: cross-daemon route failed for ${req.toAgentId}: ${formatErr(err)}`);
|
|
215300
|
+
return {
|
|
215301
|
+
delivered: false,
|
|
215302
|
+
targetSession: ctx.targetSession,
|
|
215303
|
+
reason: "offline"
|
|
215304
|
+
};
|
|
215305
|
+
}
|
|
215306
|
+
}
|
|
215109
215307
|
/** Narrow the trusted session-context platform string to the NormalizedMessage union;
|
|
215110
215308
|
* falls back to 'slack' for an unrecognized value (coords still resolve — the union is
|
|
215111
215309
|
* a routing/key detail, not the trust basis). */
|
|
@@ -216901,6 +217099,7 @@ var Daemon = class Daemon {
|
|
|
216901
217099
|
this.cpAgents?.converge(snap.agents);
|
|
216902
217100
|
this.cpIntegrations?.converge(snap.integrations);
|
|
216903
217101
|
this.convergeRelays(snap.relays);
|
|
217102
|
+
this.cpCollab.replace(snap.collabRoutes);
|
|
216904
217103
|
for (const id of snap.drop.crons) this.cpCrons?.remove(id);
|
|
216905
217104
|
this.cpCrons?.converge(snap.crons);
|
|
216906
217105
|
this.cpRouting?.converge({
|
|
@@ -216929,6 +217128,7 @@ var Daemon = class Daemon {
|
|
|
216929
217128
|
applyRouteAssign: (a) => this.cpRouting?.upsertAssign(a),
|
|
216930
217129
|
applyRouteUpdate: (u) => this.cpRouting?.applyUpdate(u),
|
|
216931
217130
|
applyRelayRoster: (relays) => this.convergeRelays(relays),
|
|
217131
|
+
applyCollabRoutes: (snap) => this.cpCollab.replace(snap),
|
|
216932
217132
|
applyAgentLaunch: async (launch) => {
|
|
216933
217133
|
const agent = this.agents.get(launch.agentId);
|
|
216934
217134
|
if (!agent) throw new Error(`agent/launch: unknown agent ${launch.agentId}`);
|
|
@@ -217065,7 +217265,8 @@ var Daemon = class Daemon {
|
|
|
217065
217265
|
path: RELAY_DAEMON_WS_PATH
|
|
217066
217266
|
}),
|
|
217067
217267
|
log: this.log,
|
|
217068
|
-
onRelayMsg: (msg, chat) => this.handleRelayMsg(msg, chat)
|
|
217268
|
+
onRelayMsg: (msg, chat) => this.handleRelayMsg(msg, chat),
|
|
217269
|
+
onRelayAgentMsg: (msg) => this.handleRelayAgentMsg(msg)
|
|
217069
217270
|
});
|
|
217070
217271
|
if (this.cfg.relays.length) this.relays.converge(this.cfg.relays);
|
|
217071
217272
|
this.cpClient = new CpClient({
|
|
@@ -217272,4 +217473,4 @@ async function runForeground(opts, deps = {}) {
|
|
|
217272
217473
|
//#endregion
|
|
217273
217474
|
export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
|
|
217274
217475
|
|
|
217275
|
-
//# sourceMappingURL=run-foreground-
|
|
217476
|
+
//# sourceMappingURL=run-foreground-BV2scayf.js.map
|