@agentconnect.md/daemon 1.0.0-rc.239 → 1.0.0-rc.240

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.
@@ -1,6 +1,6 @@
1
1
  import { a as agentChildEnv, i as selectAgent, o as loadConfig } from "./load-agents-C3pJSVeT.js";
2
2
  import { l as resolveRoot } from "./paths-DEcUO8lC.js";
3
- import { C as resolveRuntimes, i as prepareWorkspace, t as AcpHost } from "./acp-host-BADva9E0.js";
3
+ import { C as resolveRuntimes, i as prepareWorkspace, t as AcpHost } from "./acp-host-M9PGQrVJ.js";
4
4
  import { createInterface } from "node:readline";
5
5
  //#region src/cli/chat.ts
6
6
  function renderUpdate(out) {
@@ -76,4 +76,4 @@ async function runChat(opts) {
76
76
  //#endregion
77
77
  export { renderUpdate, runChat };
78
78
 
79
- //# sourceMappingURL=chat-C1jdOMwG.js.map
79
+ //# sourceMappingURL=chat-CeThir-Y.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-C1jdOMwG.js","names":[],"sources":["../src/cli/chat.ts"],"sourcesContent":["import { createInterface } from 'node:readline'\nimport type { ContentBlock, SessionUpdate } from '@agentclientprotocol/sdk'\nimport { loadConfig } from '../config/load-config.js'\nimport { resolveRuntimes } from '../runtimes/registry.js'\nimport { selectAgent } from '../agents/load-agents.js'\nimport { agentChildEnv } from '../agents/agent-env.js'\nimport { prepareWorkspace } from '../workspace/workspace-manager.js'\nimport { AcpHost } from '../acp/acp-host.js'\nimport { resolveRoot } from '../paths.js'\n\nexport function renderUpdate(out: NodeJS.WritableStream) {\n return (_sessionId: string, update: SessionUpdate): void => {\n if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') {\n out.write(update.content.text)\n } else if (update.sessionUpdate === 'tool_call') {\n const title = ('title' in update && update.title) || update.toolCallId || 'tool'\n out.write(`\\n[tool] ${title}\\n`)\n }\n }\n}\n\nexport interface RunChatOpts {\n agentsDir?: string\n agentName?: string\n message?: string\n configPath?: string\n root?: string\n out?: NodeJS.WritableStream\n input?: NodeJS.ReadableStream\n}\n\nexport async function runChat(opts: RunChatOpts): Promise<void> {\n const out = opts.out ?? process.stdout\n const root = resolveRoot(opts.root)\n const cfg = loadConfig({\n root: opts.root,\n configPath: opts.configPath,\n optional: true,\n overrides: { agentsDir: opts.agentsDir }\n })\n const agent = selectAgent(cfg.agentsDir!, opts.agentName)\n\n const runtimes = await resolveRuntimes(cfg, root, { neededRuntimes: [agent.runtime], mode: 'cache-first' })\n const runtime = runtimes[agent.runtime]\n if (!runtime) {\n const available = Object.keys(runtimes).sort().join(', ') || '(none)'\n throw new Error(`runtime \"${agent.runtime}\" not found. Available: ${available}`)\n }\n\n const host = new AcpHost(runtime, {\n onUpdate: renderUpdate(out),\n env: agentChildEnv(agent),\n configPrefs: {\n model: agent.runtimeOverrides?.model,\n permissionMode: agent.permissionMode,\n reasoningEffort: agent.reasoningEffort,\n fastMode: agent.fastMode\n }\n })\n await host.start()\n try {\n const cwd = await prepareWorkspace(agent)\n const sessionId = await host.newSession(cwd)\n\n const send = async (text: string) => {\n const blocks: ContentBlock[] = [{ type: 'text', text }]\n await host.prompt(sessionId, blocks)\n out.write('\\n')\n }\n\n if (typeof opts.message === 'string' && opts.message.length > 0) {\n await send(opts.message)\n return\n }\n\n const rl = createInterface({ input: opts.input ?? process.stdin })\n out.write(`Chatting with ${agent.name} (${agent.id}). Type .exit to quit.\\n> `)\n try {\n for await (const line of rl) {\n const text = line.trim()\n if (text === '.exit' || text === '.quit') break\n if (text.length > 0) await send(text)\n out.write('> ')\n }\n } finally {\n rl.close()\n }\n } finally {\n await host.stop()\n }\n}\n"],"mappings":";;;;;AAUA,SAAgB,aAAa,KAA4B;CACvD,QAAQ,YAAoB,WAAgC;EAC1D,IAAI,OAAO,kBAAkB,yBAAyB,OAAO,QAAQ,SAAS,QAC5E,IAAI,MAAM,OAAO,QAAQ,IAAI;OACxB,IAAI,OAAO,kBAAkB,aAAa;GAC/C,MAAM,QAAS,WAAW,UAAU,OAAO,SAAU,OAAO,cAAc;GAC1E,IAAI,MAAM,YAAY,MAAM,GAAG;EACjC;CACF;AACF;AAYA,eAAsB,QAAQ,MAAkC;CAC9D,MAAM,MAAM,KAAK,OAAO,QAAQ;CAChC,MAAM,OAAO,YAAY,KAAK,IAAI;CAClC,MAAM,MAAM,WAAW;EACrB,MAAM,KAAK;EACX,YAAY,KAAK;EACjB,UAAU;EACV,WAAW,EAAE,WAAW,KAAK,UAAU;CACzC,CAAC;CACD,MAAM,QAAQ,YAAY,IAAI,WAAY,KAAK,SAAS;CAExD,MAAM,WAAW,MAAM,gBAAgB,KAAK,MAAM;EAAE,gBAAgB,CAAC,MAAM,OAAO;EAAG,MAAM;CAAc,CAAC;CAC1G,MAAM,UAAU,SAAS,MAAM;CAC/B,IAAI,CAAC,SAAS;EACZ,MAAM,YAAY,OAAO,KAAK,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,KAAK;EAC7D,MAAM,IAAI,MAAM,YAAY,MAAM,QAAQ,0BAA0B,WAAW;CACjF;CAEA,MAAM,OAAO,IAAI,QAAQ,SAAS;EAChC,UAAU,aAAa,GAAG;EAC1B,KAAK,cAAc,KAAK;EACxB,aAAa;GACX,OAAO,MAAM,kBAAkB;GAC/B,gBAAgB,MAAM;GACtB,iBAAiB,MAAM;GACvB,UAAU,MAAM;EAClB;CACF,CAAC;CACD,MAAM,KAAK,MAAM;CACjB,IAAI;EACF,MAAM,MAAM,MAAM,iBAAiB,KAAK;EACxC,MAAM,YAAY,MAAM,KAAK,WAAW,GAAG;EAE3C,MAAM,OAAO,OAAO,SAAiB;GACnC,MAAM,SAAyB,CAAC;IAAE,MAAM;IAAQ;GAAK,CAAC;GACtD,MAAM,KAAK,OAAO,WAAW,MAAM;GACnC,IAAI,MAAM,IAAI;EAChB;EAEA,IAAI,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,SAAS,GAAG;GAC/D,MAAM,KAAK,KAAK,OAAO;GACvB;EACF;EAEA,MAAM,KAAK,gBAAgB,EAAE,OAAO,KAAK,SAAS,QAAQ,MAAM,CAAC;EACjE,IAAI,MAAM,iBAAiB,MAAM,KAAK,IAAI,MAAM,GAAG,2BAA2B;EAC9E,IAAI;GACF,WAAW,MAAM,QAAQ,IAAI;IAC3B,MAAM,OAAO,KAAK,KAAK;IACvB,IAAI,SAAS,WAAW,SAAS,SAAS;IAC1C,IAAI,KAAK,SAAS,GAAG,MAAM,KAAK,IAAI;IACpC,IAAI,MAAM,IAAI;GAChB;EACF,UAAU;GACR,GAAG,MAAM;EACX;CACF,UAAU;EACR,MAAM,KAAK,KAAK;CAClB;AACF"}
1
+ {"version":3,"file":"chat-CeThir-Y.js","names":[],"sources":["../src/cli/chat.ts"],"sourcesContent":["import { createInterface } from 'node:readline'\nimport type { ContentBlock, SessionUpdate } from '@agentclientprotocol/sdk'\nimport { loadConfig } from '../config/load-config.js'\nimport { resolveRuntimes } from '../runtimes/registry.js'\nimport { selectAgent } from '../agents/load-agents.js'\nimport { agentChildEnv } from '../agents/agent-env.js'\nimport { prepareWorkspace } from '../workspace/workspace-manager.js'\nimport { AcpHost } from '../acp/acp-host.js'\nimport { resolveRoot } from '../paths.js'\n\nexport function renderUpdate(out: NodeJS.WritableStream) {\n return (_sessionId: string, update: SessionUpdate): void => {\n if (update.sessionUpdate === 'agent_message_chunk' && update.content.type === 'text') {\n out.write(update.content.text)\n } else if (update.sessionUpdate === 'tool_call') {\n const title = ('title' in update && update.title) || update.toolCallId || 'tool'\n out.write(`\\n[tool] ${title}\\n`)\n }\n }\n}\n\nexport interface RunChatOpts {\n agentsDir?: string\n agentName?: string\n message?: string\n configPath?: string\n root?: string\n out?: NodeJS.WritableStream\n input?: NodeJS.ReadableStream\n}\n\nexport async function runChat(opts: RunChatOpts): Promise<void> {\n const out = opts.out ?? process.stdout\n const root = resolveRoot(opts.root)\n const cfg = loadConfig({\n root: opts.root,\n configPath: opts.configPath,\n optional: true,\n overrides: { agentsDir: opts.agentsDir }\n })\n const agent = selectAgent(cfg.agentsDir!, opts.agentName)\n\n const runtimes = await resolveRuntimes(cfg, root, { neededRuntimes: [agent.runtime], mode: 'cache-first' })\n const runtime = runtimes[agent.runtime]\n if (!runtime) {\n const available = Object.keys(runtimes).sort().join(', ') || '(none)'\n throw new Error(`runtime \"${agent.runtime}\" not found. Available: ${available}`)\n }\n\n const host = new AcpHost(runtime, {\n onUpdate: renderUpdate(out),\n env: agentChildEnv(agent),\n configPrefs: {\n model: agent.runtimeOverrides?.model,\n permissionMode: agent.permissionMode,\n reasoningEffort: agent.reasoningEffort,\n fastMode: agent.fastMode\n }\n })\n await host.start()\n try {\n const cwd = await prepareWorkspace(agent)\n const sessionId = await host.newSession(cwd)\n\n const send = async (text: string) => {\n const blocks: ContentBlock[] = [{ type: 'text', text }]\n await host.prompt(sessionId, blocks)\n out.write('\\n')\n }\n\n if (typeof opts.message === 'string' && opts.message.length > 0) {\n await send(opts.message)\n return\n }\n\n const rl = createInterface({ input: opts.input ?? process.stdin })\n out.write(`Chatting with ${agent.name} (${agent.id}). Type .exit to quit.\\n> `)\n try {\n for await (const line of rl) {\n const text = line.trim()\n if (text === '.exit' || text === '.quit') break\n if (text.length > 0) await send(text)\n out.write('> ')\n }\n } finally {\n rl.close()\n }\n } finally {\n await host.stop()\n }\n}\n"],"mappings":";;;;;AAUA,SAAgB,aAAa,KAA4B;CACvD,QAAQ,YAAoB,WAAgC;EAC1D,IAAI,OAAO,kBAAkB,yBAAyB,OAAO,QAAQ,SAAS,QAC5E,IAAI,MAAM,OAAO,QAAQ,IAAI;OACxB,IAAI,OAAO,kBAAkB,aAAa;GAC/C,MAAM,QAAS,WAAW,UAAU,OAAO,SAAU,OAAO,cAAc;GAC1E,IAAI,MAAM,YAAY,MAAM,GAAG;EACjC;CACF;AACF;AAYA,eAAsB,QAAQ,MAAkC;CAC9D,MAAM,MAAM,KAAK,OAAO,QAAQ;CAChC,MAAM,OAAO,YAAY,KAAK,IAAI;CAClC,MAAM,MAAM,WAAW;EACrB,MAAM,KAAK;EACX,YAAY,KAAK;EACjB,UAAU;EACV,WAAW,EAAE,WAAW,KAAK,UAAU;CACzC,CAAC;CACD,MAAM,QAAQ,YAAY,IAAI,WAAY,KAAK,SAAS;CAExD,MAAM,WAAW,MAAM,gBAAgB,KAAK,MAAM;EAAE,gBAAgB,CAAC,MAAM,OAAO;EAAG,MAAM;CAAc,CAAC;CAC1G,MAAM,UAAU,SAAS,MAAM;CAC/B,IAAI,CAAC,SAAS;EACZ,MAAM,YAAY,OAAO,KAAK,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,KAAK;EAC7D,MAAM,IAAI,MAAM,YAAY,MAAM,QAAQ,0BAA0B,WAAW;CACjF;CAEA,MAAM,OAAO,IAAI,QAAQ,SAAS;EAChC,UAAU,aAAa,GAAG;EAC1B,KAAK,cAAc,KAAK;EACxB,aAAa;GACX,OAAO,MAAM,kBAAkB;GAC/B,gBAAgB,MAAM;GACtB,iBAAiB,MAAM;GACvB,UAAU,MAAM;EAClB;CACF,CAAC;CACD,MAAM,KAAK,MAAM;CACjB,IAAI;EACF,MAAM,MAAM,MAAM,iBAAiB,KAAK;EACxC,MAAM,YAAY,MAAM,KAAK,WAAW,GAAG;EAE3C,MAAM,OAAO,OAAO,SAAiB;GACnC,MAAM,SAAyB,CAAC;IAAE,MAAM;IAAQ;GAAK,CAAC;GACtD,MAAM,KAAK,OAAO,WAAW,MAAM;GACnC,IAAI,MAAM,IAAI;EAChB;EAEA,IAAI,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,SAAS,GAAG;GAC/D,MAAM,KAAK,KAAK,OAAO;GACvB;EACF;EAEA,MAAM,KAAK,gBAAgB,EAAE,OAAO,KAAK,SAAS,QAAQ,MAAM,CAAC;EACjE,IAAI,MAAM,iBAAiB,MAAM,KAAK,IAAI,MAAM,GAAG,2BAA2B;EAC9E,IAAI;GACF,WAAW,MAAM,QAAQ,IAAI;IAC3B,MAAM,OAAO,KAAK,KAAK;IACvB,IAAI,SAAS,WAAW,SAAS,SAAS;IAC1C,IAAI,KAAK,SAAS,GAAG,MAAM,KAAK,IAAI;IACpC,IAAI,MAAM,IAAI;GAChB;EACF,UAAU;GACR,GAAG,MAAM;EACX;CACF,UAAU;EACR,MAAM,KAAK,KAAK;CAClB;AACF"}
package/dist/index.js CHANGED
@@ -68492,9 +68492,9 @@ const telemetry = startDaemonOpenTelemetry({ serviceVersion: DAEMON_VERSION });
68492
68492
  const [{ Command }, { fileURLToPath }, { runChat }, { runLogin }, { runForeground }, { resolveController }, { acquireSingletonLock }, { resolveRoot }] = await Promise.all([
68493
68493
  import("./commander-B5QVEFzH.js"),
68494
68494
  import("node:url"),
68495
- import("./chat-C1jdOMwG.js"),
68496
- import("./login-DAAg53hd.js"),
68497
- import("./run-foreground-Dd5XN9F6.js"),
68495
+ import("./chat-CeThir-Y.js"),
68496
+ import("./login-CoeR7Gr0.js"),
68497
+ import("./run-foreground-B8NoERhV.js"),
68498
68498
  import("./service-6w0WKhdx.js"),
68499
68499
  import("./lock-CA9Bm8_s.js"),
68500
68500
  import("./paths-CcRUjFjy.js")
@@ -1,8 +1,8 @@
1
1
  import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
2
- import { i as ClientTransport, t as runForeground } from "./run-foreground-C4Ip8lzS.js";
2
+ import { i as ClientTransport, t as runForeground } from "./run-foreground-Cdxkfe7g.js";
3
3
  import { c as ConfigSchema } from "./load-agents-C3pJSVeT.js";
4
4
  import { l as resolveRoot, t as configPath } from "./paths-DEcUO8lC.js";
5
- import { _ as encode, g as decodeEnvelope, h as buildEnvelope, l as isFrame } from "./acp-host-BADva9E0.js";
5
+ import { _ as encode, g as decodeEnvelope, h as buildEnvelope, l as isFrame } from "./acp-host-M9PGQrVJ.js";
6
6
  import { n as resolveController } from "./service-BQVw7wbz.js";
7
7
  import { dirname } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
@@ -229,4 +229,4 @@ async function runLogin(opts, partial = {}) {
229
229
  //#endregion
230
230
  export { buildInstallOpts, persistCredentials, runLogin };
231
231
 
232
- //# sourceMappingURL=login-DAAg53hd.js.map
232
+ //# sourceMappingURL=login-CoeR7Gr0.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"login-DAAg53hd.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-CoeR7Gr0.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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"}
@@ -0,0 +1,2 @@
1
+ import { t as runForeground } from "./run-foreground-Cdxkfe7g.js";
2
+ export { runForeground };
@@ -5,7 +5,7 @@ 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
6
  import { a as agentChildEnv, i as selectAgent, n as findAgentFiles, o as loadConfig, r as loadAgents, s as persistDaemonId } from "./load-agents-C3pJSVeT.js";
7
7
  import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
8
- import { C as resolveRuntimes, S as cachedRuntimeVersions, _ as encode, a as initGitInjection, b as RESERVED_MCP_SERVER_NAME, c as simpleGit, d as RELAY_DAEMON_SUBPROTOCOL, f as RELAY_DAEMON_WS_PATH, g as decodeEnvelope, h as buildEnvelope, i as prepareWorkspace, m as decodeRelayDaemonFrame, n as installedRuntimes, o as probeGitVersion, p as buildRelayDaemonFrame, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as normalizeGitUrl, v as MAX_FRAME_BYTES, x as cachedRuntimeNames, y as MEMORY_INDEX } from "./acp-host-BADva9E0.js";
8
+ import { C as resolveRuntimes, S as cachedRuntimeVersions, _ as encode, a as initGitInjection, b as RESERVED_MCP_SERVER_NAME, c as simpleGit, d as RELAY_DAEMON_SUBPROTOCOL, f as RELAY_DAEMON_WS_PATH, g as decodeEnvelope, h as buildEnvelope, i as prepareWorkspace, m as decodeRelayDaemonFrame, n as installedRuntimes, o as probeGitVersion, p as buildRelayDaemonFrame, r as prefetchWorkspace, s as sessionGitEnv, t as AcpHost, u as normalizeGitUrl, v as MAX_FRAME_BYTES, x as cachedRuntimeNames, y as MEMORY_INDEX } from "./acp-host-M9PGQrVJ.js";
9
9
  import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-Dcsq6QJe.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";
@@ -4529,22 +4529,6 @@ var CpClient = class {
4529
4529
  if (this.state !== "READY" && this.state !== "DRAINING") return;
4530
4530
  this.transport?.send(encode(buildEnvelope("cron/report", report)));
4531
4531
  }
4532
- /**
4533
- * Emit one chunk of a webchat turn's reply stream (D→C `webchat/output` EVT).
4534
- * Ordered per-connection (TCP); `output.index` is the per-turn assembly counter.
4535
- * No-op unless READY/DRAINING — a dropped chunk is lost (unlike latest-wins
4536
- * telemetry), so the daemon only emits while connected. Content is inline, bounded
4537
- * by the 256 KiB frame cap; the caller chunks any oversize single event.
4538
- */
4539
- emitWebchatOutput(output) {
4540
- if (this.state !== "READY" && this.state !== "DRAINING") return;
4541
- this.transport?.send(encode(buildEnvelope("webchat/output", output)));
4542
- }
4543
- /** Emit a webchat turn's completion (D→C `webchat/done` EVT). No-op unless READY/DRAINING. */
4544
- emitWebchatDone(done) {
4545
- if (this.state !== "READY" && this.state !== "DRAINING") return;
4546
- this.transport?.send(encode(buildEnvelope("webchat/done", done)));
4547
- }
4548
4532
  request(type, payload) {
4549
4533
  const frame = buildEnvelope(type, payload);
4550
4534
  return this.correlator.request(frame, (e) => this.transport.send(e));
@@ -4753,35 +4737,6 @@ var CpClient = class {
4753
4737
  case "memory/write":
4754
4738
  this.deps.memoryReader.write(frame.payload).then((ok) => this.reply(frame, "memory/write/ok", ok)).catch((err) => this.memoryError(frame.id, "memory/write", err));
4755
4739
  return;
4756
- case "webchat/message":
4757
- if (!this.deps.onWebchatMessage) {
4758
- this.sendError(frame.id, "INTERNAL", "webchat not supported", false);
4759
- return;
4760
- }
4761
- try {
4762
- this.reply(frame, "webchat/ack", this.deps.onWebchatMessage(frame.payload));
4763
- } catch (err) {
4764
- this.sendError(frame.id, "INTERNAL", `webchat/message failed: ${err.message}`, false);
4765
- }
4766
- return;
4767
- case "webchat/close":
4768
- this.deps.onWebchatClose?.(frame.payload);
4769
- return;
4770
- case "webchat/set-model":
4771
- this.deps.onWebchatSetModel?.(frame.payload);
4772
- return;
4773
- case "webchat/set-effort":
4774
- this.deps.onWebchatSetEffort?.(frame.payload);
4775
- return;
4776
- case "webchat/set-permission-mode":
4777
- this.deps.onWebchatSetPermissionMode?.(frame.payload);
4778
- return;
4779
- case "webchat/set-fast":
4780
- this.deps.onWebchatSetFast?.(frame.payload);
4781
- return;
4782
- case "webchat/cancel":
4783
- this.deps.onWebchatCancel?.(frame.payload);
4784
- return;
4785
4740
  default:
4786
4741
  this.deps.log.debug(`cp: ignoring ${frame.type}`);
4787
4742
  return;
@@ -197033,6 +196988,7 @@ var RelayClient = class {
197033
196988
  stopped = false;
197034
196989
  fatal = false;
197035
196990
  reconnectTimer;
196991
+ seqByChat = /* @__PURE__ */ new Map();
197036
196992
  constructor(relayId, url, deps) {
197037
196993
  this.relayId = relayId;
197038
196994
  this.url = url;
@@ -197105,8 +197061,28 @@ var RelayClient = class {
197105
197061
  if (!decoded.ok) return;
197106
197062
  const frame = decoded.frame;
197107
197063
  if (frame.corr && this.correlator.settle(frame)) return;
197064
+ if (frame.type === "rd/msg") {
197065
+ this.handleMsg(frame.id, frame.payload);
197066
+ return;
197067
+ }
197108
197068
  this.deps.log.debug(`relay(${this.relayId}): ignoring ${frame.type}`);
197109
197069
  }
197070
+ /** An inbound webchat op: dispatch it, reply `rd/ack`, and let the daemon stream `rd/chat`. */
197071
+ handleMsg(reqId, msg) {
197072
+ const ack = this.deps.onRelayMsg(msg, (event) => this.sendChat(msg.chatId, event));
197073
+ this.transport?.send(JSON.stringify(buildRelayDaemonFrame("rd/ack", ack, { corr: reqId })));
197074
+ }
197075
+ /** Send one reply chunk back over THIS relay's socket, stamping the per-chat `seq`. */
197076
+ sendChat(chatId, event) {
197077
+ const seq = (this.seqByChat.get(chatId) ?? 0) + 1;
197078
+ this.seqByChat.set(chatId, seq);
197079
+ this.transport?.send(JSON.stringify(buildRelayDaemonFrame("rd/chat", {
197080
+ chatId,
197081
+ seq,
197082
+ event
197083
+ })));
197084
+ if (event.kind === "done") this.seqByChat.delete(chatId);
197085
+ }
197110
197086
  onClose(code, _reason) {
197111
197087
  this.transport = void 0;
197112
197088
  this.correlator.rejectAll(new WireError("INTERNAL", "connection closed", true));
@@ -214547,24 +214523,19 @@ var Daemon = class Daemon {
214547
214523
  await this.dispatch(agentId, msg, integrationId);
214548
214524
  }
214549
214525
  /**
214550
- * Handle an inbound webchat turn (C→D `webchat/message`). webchat is the one
214551
- * integration whose data plane is the daemon↔CP control WS itself — the far end is
214552
- * the CP/browser, so the body arrives inline and the reply streams back over the
214553
- * same WS. The message names its target agent explicitly (no trigger matching), so
214554
- * this normalizes the turn and hands it to the same dispatch path as any session:
214555
- * the turn is a REAL session (recorded to the transcript, visible in session/list),
214556
- * only its transport differs. Returns the ack: accepted + a fresh turnId that
214557
- * correlates the streamed webchat/output, or accepted:false / no_agent when the
214558
- * named agent isn't runnable here.
214526
+ * Dispatch one webchat turn onto the named agent and return the ack — transport-neutral:
214527
+ * the reply stream flows to `sink`. A webchat turn is a REAL session (recorded to the
214528
+ * transcript, visible in session/list); only its transport differs. The op names its
214529
+ * target agent explicitly (no trigger matching). Fed by the relay path (`rd/msg`).
214559
214530
  */
214560
- handleWebchatMessage(m) {
214531
+ dispatchWebchatTurn(agentId, chatId, text, user, sink) {
214561
214532
  const turnId = randomUUID();
214562
214533
  const result = routeRules({
214563
214534
  platform: "webchat",
214564
- channel: m.conversationId
214565
- }, this.mergedRules(), () => null, m.agentId);
214535
+ channel: chatId
214536
+ }, this.mergedRules(), () => null, agentId);
214566
214537
  if (!result || !this.agents.has(result.agentId)) {
214567
- this.log.warn(`webchat: no agent "${m.agentId}" on this daemon — rejecting turn`);
214538
+ this.log.warn(`webchat: no agent "${agentId}" on this daemon — rejecting turn`);
214568
214539
  return {
214569
214540
  accepted: false,
214570
214541
  turnId,
@@ -214579,24 +214550,33 @@ var Daemon = class Daemon {
214579
214550
  reason: "paused"
214580
214551
  };
214581
214552
  }
214553
+ if (this.draining || this.drainingAgents.has(result.agentId)) {
214554
+ this.log.info(`webchat: agent "${result.agentId}" is draining — rejecting turn`);
214555
+ return {
214556
+ accepted: false,
214557
+ turnId,
214558
+ reason: "draining"
214559
+ };
214560
+ }
214582
214561
  const msg = {
214583
- msgId: `webchat:${m.conversationId}`,
214562
+ msgId: `webchat:${chatId}`,
214584
214563
  traceId: turnId,
214585
214564
  source: "user",
214586
214565
  platform: "webchat",
214587
- channel: m.conversationId,
214566
+ channel: chatId,
214588
214567
  sender: {
214589
- id: m.user ?? "webchat",
214568
+ id: user,
214590
214569
  isBot: false
214591
214570
  },
214592
- text: m.text,
214571
+ text,
214593
214572
  mentionedBots: [],
214594
214573
  isDm: true,
214595
214574
  trigger: "dm"
214596
214575
  };
214597
214576
  this.dispatch(result.agentId, msg, void 0, {
214598
- conversationId: m.conversationId,
214599
- turnId
214577
+ conversationId: chatId,
214578
+ turnId,
214579
+ sink
214600
214580
  }).catch((err) => this.log.error(`webchat dispatch failed for agent "${result.agentId}": ${formatErr(err)}`));
214601
214581
  return {
214602
214582
  accepted: true,
@@ -214606,10 +214586,10 @@ var Daemon = class Daemon {
214606
214586
  /** Handle a webchat conversation close (C→D `webchat/close`). No live resources are
214607
214587
  * bound per-conversation (the session TTL-closes like any other), so this is
214608
214588
  * currently just observability — the in-flight turn, if any, runs to completion. */
214609
- handleWebchatClose(m) {
214610
- this.log.debug(`webchat: conversation ${m.conversationId} closed by client`);
214589
+ handleWebchatClose(conversationId) {
214590
+ this.log.debug(`webchat: conversation ${conversationId} closed by client`);
214611
214591
  }
214612
- /** The local session key a webchat conversation maps to — mirrors `handleWebchatMessage`
214592
+ /** The local session key a webchat conversation maps to — mirrors `dispatchWebchatTurn`
214613
214593
  * (channel = conversationId, statusThread = the stable `webchat:<id>` msgId, no thread). */
214614
214594
  webchatSessionKey(conversationId, agentId) {
214615
214595
  return sessionKey("webchat", conversationId, `webchat:${conversationId}`, agentId);
@@ -214703,31 +214683,100 @@ var Daemon = class Daemon {
214703
214683
  this.log.info(`session ${key} output-mode override → "${mode}"`);
214704
214684
  this.refreshStatusBarForKey(key);
214705
214685
  }
214706
- /** Handle a webchat in-session model switch (C→D `webchat/set-model`). */
214707
- handleWebchatSetModel(m) {
214708
- this.setModelByKey(this.webchatSessionKey(m.conversationId, m.agentId), m.model);
214709
- }
214710
- /** Handle a webchat in-session effort switch (C→D `webchat/set-effort`). */
214711
- handleWebchatSetEffort(m) {
214712
- this.setEffortByKey(this.webchatSessionKey(m.conversationId, m.agentId), m.effort);
214713
- }
214714
- /** Handle a webchat in-session permission-mode switch (C→D `webchat/set-permission-mode`). */
214715
- handleWebchatSetPermissionMode(m) {
214716
- this.setPermissionModeByKey(this.webchatSessionKey(m.conversationId, m.agentId), m.permissionMode);
214717
- }
214718
- /** Handle a webchat in-session fast-mode toggle (C→D `webchat/set-fast`). */
214719
- handleWebchatSetFast(m) {
214720
- this.setFastByKey(this.webchatSessionKey(m.conversationId, m.agentId), m.fastMode);
214721
- }
214722
- /** Handle a webchat cancel (C→D `webchat/cancel`) — the status-bar "Cancel". Interrupts
214686
+ /** Handle a webchat cancel (the relay `cancel` op / status-bar "Cancel"). Interrupts
214723
214687
  * the conversation's in-flight turn like `!cancel` (no mute; follow-ups still dispatch).
214724
- * Resolved by conversationId (the frame carries no agentId). No-op when idle. */
214725
- handleWebchatCancel(m) {
214726
- for (const [acpSessionId, p] of this.pending) if (p.webchat?.conversationId === m.conversationId) {
214688
+ * Resolved by conversationId (the op carries no agentId). No-op when idle. */
214689
+ handleWebchatCancel(conversationId) {
214690
+ for (const [acpSessionId, p] of this.pending) if (p.webchat?.conversationId === conversationId) {
214727
214691
  this.interruptTurn(p.agentId, acpSessionId, p.sessionKey, "cancel");
214728
214692
  return;
214729
214693
  }
214730
- this.log.debug(`webchat cancel: no in-flight turn for conversation ${m.conversationId}`);
214694
+ this.log.debug(`webchat cancel: no in-flight turn for conversation ${conversationId}`);
214695
+ }
214696
+ relayMsgAcks = /* @__PURE__ */ new Map();
214697
+ /**
214698
+ * Dispatch one inbound relay webchat op (`rd/msg`) and return the `rd/ack` verdict —
214699
+ * the relay-path entry, reusing the SAME turn engine + by-key cores as the (retiring)
214700
+ * CP path. The `chat` callback streams the reply back as `rd/chat` over the relay
214701
+ * socket the op arrived on (wrapped into the sink).
214702
+ *
214703
+ * Deduped by the relay-minted (sessionKey, msgId): the wire is at-least-once, so a
214704
+ * retransmitted rd/msg (identical msgId) must NOT run the user's turn a second time —
214705
+ * replay the original ack (so the relay settles) without re-dispatching.
214706
+ */
214707
+ handleRelayMsg(msg, chat) {
214708
+ const dedupKey = `${msg.sessionKey}:${msg.msgId}`;
214709
+ const prior = this.relayMsgAcks.get(dedupKey);
214710
+ if (prior) {
214711
+ this.log.debug(`relay: duplicate rd/msg ${dedupKey} — replaying ack (no re-dispatch)`);
214712
+ return prior;
214713
+ }
214714
+ const ack = this.dispatchRelayOp(msg, chat);
214715
+ if (this.relayMsgAcks.size >= 2e3) this.relayMsgAcks.clear();
214716
+ this.relayMsgAcks.set(dedupKey, ack);
214717
+ return ack;
214718
+ }
214719
+ /** The op-switch behind {@link handleRelayMsg} (dedup handled by the caller). */
214720
+ dispatchRelayOp(msg, chat) {
214721
+ const sink = {
214722
+ output: (o) => chat({
214723
+ kind: "output",
214724
+ output: o
214725
+ }),
214726
+ done: (d) => chat({
214727
+ kind: "done",
214728
+ done: d
214729
+ })
214730
+ };
214731
+ const op = msg.payload;
214732
+ const key = () => this.webchatSessionKey(msg.chatId, msg.agentId);
214733
+ switch (op.op) {
214734
+ case "turn": {
214735
+ const ack = this.dispatchWebchatTurn(msg.agentId, msg.chatId, op.text, op.user ?? "webchat", sink);
214736
+ return {
214737
+ msgId: msg.msgId,
214738
+ accepted: ack.accepted,
214739
+ turnId: ack.turnId,
214740
+ ...ack.reason ? { reason: ack.reason } : {}
214741
+ };
214742
+ }
214743
+ case "set_model":
214744
+ this.setModelByKey(key(), op.model);
214745
+ return {
214746
+ msgId: msg.msgId,
214747
+ accepted: true
214748
+ };
214749
+ case "set_effort":
214750
+ this.setEffortByKey(key(), op.effort);
214751
+ return {
214752
+ msgId: msg.msgId,
214753
+ accepted: true
214754
+ };
214755
+ case "set_permission_mode":
214756
+ this.setPermissionModeByKey(key(), op.permissionMode);
214757
+ return {
214758
+ msgId: msg.msgId,
214759
+ accepted: true
214760
+ };
214761
+ case "set_fast":
214762
+ this.setFastByKey(key(), op.fastMode);
214763
+ return {
214764
+ msgId: msg.msgId,
214765
+ accepted: true
214766
+ };
214767
+ case "cancel":
214768
+ this.handleWebchatCancel(msg.chatId);
214769
+ return {
214770
+ msgId: msg.msgId,
214771
+ accepted: true
214772
+ };
214773
+ case "close":
214774
+ this.handleWebchatClose(msg.chatId);
214775
+ return {
214776
+ msgId: msg.msgId,
214777
+ accepted: true
214778
+ };
214779
+ }
214731
214780
  }
214732
214781
  /** Route a Slack status-bar Block Kit interaction (model / effort / fast select or the
214733
214782
  * Cancel button) to the shared key-based cores. `sessionKey` rides the block; no-op on
@@ -215151,7 +215200,7 @@ var Daemon = class Daemon {
215151
215200
  */
215152
215201
  surfaceTurnFailure(err, ctx) {
215153
215202
  const reason = err?.message ?? String(err);
215154
- if (ctx.webchat) this.cpClient?.emitWebchatDone({
215203
+ if (ctx.webchat) ctx.webchat.sink.done({
215155
215204
  conversationId: ctx.webchat.conversationId,
215156
215205
  turnId: ctx.webchat.turnId,
215157
215206
  error: reason
@@ -215273,7 +215322,7 @@ var Daemon = class Daemon {
215273
215322
  kind: "text",
215274
215323
  text: p.webchat.replyText
215275
215324
  });
215276
- this.cpClient?.emitWebchatDone({
215325
+ p.webchat.sink.done({
215277
215326
  conversationId: p.webchat.conversationId,
215278
215327
  turnId: p.webchat.turnId,
215279
215328
  ...stopReason ? { stopReason } : {},
@@ -215699,7 +215748,7 @@ var Daemon = class Daemon {
215699
215748
  if (!Object.entries(info).some(([k, v]) => k !== "sessionId" && v !== void 0)) return;
215700
215749
  p.lastStatusBar = key;
215701
215750
  const wc = p.webchat;
215702
- this.cpClient?.emitWebchatOutput({
215751
+ wc.sink.output({
215703
215752
  conversationId: wc.conversationId,
215704
215753
  turnId: wc.turnId,
215705
215754
  index: wc.index++,
@@ -215776,16 +215825,15 @@ var Daemon = class Daemon {
215776
215825
  for (const ev of p.rec.onUpdate(update)) this.recordEvent(p.agentId, p.channel, p.statusThread, ev);
215777
215826
  }
215778
215827
  /**
215779
- * Map one ACP SessionUpdate to a WebchatEvent and stream it to the CP as a
215780
- * `webchat/output` EVT (webchat's "send"). Only the streamable chunk kinds map;
215781
- * usage/plan/session_info are handled elsewhere or dropped. A single event whose
215782
- * inline text would blow the 256 KiB frame cap is split across multiple chunks,
215783
- * each with its own monotonic `index`.
215828
+ * Map one ACP SessionUpdate to a WebchatEvent and stream it through the sink (→ relay
215829
+ * `rd/chat`, webchat's "send"). Only the streamable chunk kinds map; usage/plan/
215830
+ * session_info are handled elsewhere or dropped. A single event whose inline text would
215831
+ * blow the 256 KiB frame cap is split across multiple chunks, each with its own `index`.
215784
215832
  */
215785
215833
  emitWebchatUpdate(p, update) {
215786
215834
  const wc = p.webchat;
215787
215835
  const emit = (event) => {
215788
- this.cpClient?.emitWebchatOutput({
215836
+ wc.sink.output({
215789
215837
  conversationId: wc.conversationId,
215790
215838
  turnId: wc.turnId,
215791
215839
  index: wc.index++,
@@ -216251,7 +216299,8 @@ var Daemon = class Daemon {
216251
216299
  subprotocol: RELAY_DAEMON_SUBPROTOCOL,
216252
216300
  path: RELAY_DAEMON_WS_PATH
216253
216301
  }),
216254
- log: this.log
216302
+ log: this.log,
216303
+ onRelayMsg: (msg, chat) => this.handleRelayMsg(msg, chat)
216255
216304
  });
216256
216305
  this.cpClient = new CpClient({
216257
216306
  url,
@@ -216321,13 +216370,6 @@ var Daemon = class Daemon {
216321
216370
  workspaceRead: createWorkspaceReader((id) => this.agents.get(id)?.workspace.path),
216322
216371
  workspaceGit: createWorkspaceGit((id) => this.agents.get(id)?.workspace.path),
216323
216372
  memoryReader: createMemoryReader((id) => this.agents.get(id)?.dir),
216324
- onWebchatMessage: (m) => this.handleWebchatMessage(m),
216325
- onWebchatClose: (m) => this.handleWebchatClose(m),
216326
- onWebchatSetModel: (m) => this.handleWebchatSetModel(m),
216327
- onWebchatSetEffort: (m) => this.handleWebchatSetEffort(m),
216328
- onWebchatSetPermissionMode: (m) => this.handleWebchatSetPermissionMode(m),
216329
- onWebchatSetFast: (m) => this.handleWebchatSetFast(m),
216330
- onWebchatCancel: (m) => this.handleWebchatCancel(m),
216331
216373
  clock: systemClock,
216332
216374
  connect: () => ClientTransport.dial(url, {
216333
216375
  subprotocol: CP_SUBPROTOCOL,
@@ -216459,4 +216501,4 @@ async function runForeground(opts, deps = {}) {
216459
216501
  //#endregion
216460
216502
  export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
216461
216503
 
216462
- //# sourceMappingURL=run-foreground-C4Ip8lzS.js.map
216504
+ //# sourceMappingURL=run-foreground-Cdxkfe7g.js.map