@agentconnect.md/daemon 1.0.0-rc.309 → 1.0.0-rc.310

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-CROyMyq9.js";
2
2
  import { l as resolveRoot } from "./paths-DEcUO8lC.js";
3
- import { C as resolveRuntimes, a as prepareWorkspace, f as memoryProviderFor, t as AcpHost } from "./acp-host-BDcV2bZY.js";
3
+ import { C as resolveRuntimes, a as prepareWorkspace, f as memoryProviderFor, t as AcpHost } from "./acp-host-DEjB1yFS.js";
4
4
  import { createInterface } from "node:readline";
5
5
  //#region src/cli/chat.ts
6
6
  function renderUpdate(out) {
@@ -84,4 +84,4 @@ async function runChat(opts) {
84
84
  //#endregion
85
85
  export { renderUpdate, runChat };
86
86
 
87
- //# sourceMappingURL=chat-BUFOmSMl.js.map
87
+ //# sourceMappingURL=chat-CfRG8UQR.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-BUFOmSMl.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 { memoryProviderFor } from '../agents/memory-provider.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 // Memory-backend env for parity with the daemon (managed disables the runtime's\n // own memory; native redirects it under the agent root).\n env: { ...agentChildEnv(agent), ...memoryProviderFor(agent, runtime).runtimeEnv() },\n configPrefs: {\n model: agent.runtimeOverrides?.model,\n permissionMode: agent.permissionMode,\n reasoningEffort: agent.reasoningEffort,\n fastMode: agent.fastMode\n }\n })\n // The adapter child runs in its own process group (see AcpHost.start), so a\n // terminal Ctrl-C no longer kills it as a side effect — Node's default SIGINT\n // exit would skip the finally below and leak it. Stop the host, then re-exit.\n // Registered BEFORE start(): a first-run `npx -y` runtime download takes long\n // enough that Ctrl-C mid-start is common, and the spawn happens early in start().\n const onSigint = () => {\n void host.stop().finally(() => process.exit(130))\n }\n process.once('SIGINT', onSigint)\n try {\n await host.start()\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 process.removeListener('SIGINT', onSigint)\n await host.stop()\n }\n}\n"],"mappings":";;;;;AAWA,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;EAG1B,KAAK;GAAE,GAAG,cAAc,KAAK;GAAG,GAAG,kBAAkB,OAAO,OAAO,CAAC,CAAC,WAAW;EAAE;EAClF,aAAa;GACX,OAAO,MAAM,kBAAkB;GAC/B,gBAAgB,MAAM;GACtB,iBAAiB,MAAM;GACvB,UAAU,MAAM;EAClB;CACF,CAAC;CAMD,MAAM,iBAAiB;EACrB,KAAU,KAAK,CAAC,CAAC,cAAc,QAAQ,KAAK,GAAG,CAAC;CAClD;CACA,QAAQ,KAAK,UAAU,QAAQ;CAC/B,IAAI;EACF,MAAM,KAAK,MAAM;EACjB,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,QAAQ,eAAe,UAAU,QAAQ;EACzC,MAAM,KAAK,KAAK;CAClB;AACF"}
1
+ {"version":3,"file":"chat-CfRG8UQR.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 { memoryProviderFor } from '../agents/memory-provider.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 // Memory-backend env for parity with the daemon (managed disables the runtime's\n // own memory; native redirects it under the agent root).\n env: { ...agentChildEnv(agent), ...memoryProviderFor(agent, runtime).runtimeEnv() },\n configPrefs: {\n model: agent.runtimeOverrides?.model,\n permissionMode: agent.permissionMode,\n reasoningEffort: agent.reasoningEffort,\n fastMode: agent.fastMode\n }\n })\n // The adapter child runs in its own process group (see AcpHost.start), so a\n // terminal Ctrl-C no longer kills it as a side effect — Node's default SIGINT\n // exit would skip the finally below and leak it. Stop the host, then re-exit.\n // Registered BEFORE start(): a first-run `npx -y` runtime download takes long\n // enough that Ctrl-C mid-start is common, and the spawn happens early in start().\n const onSigint = () => {\n void host.stop().finally(() => process.exit(130))\n }\n process.once('SIGINT', onSigint)\n try {\n await host.start()\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 process.removeListener('SIGINT', onSigint)\n await host.stop()\n }\n}\n"],"mappings":";;;;;AAWA,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;EAG1B,KAAK;GAAE,GAAG,cAAc,KAAK;GAAG,GAAG,kBAAkB,OAAO,OAAO,CAAC,CAAC,WAAW;EAAE;EAClF,aAAa;GACX,OAAO,MAAM,kBAAkB;GAC/B,gBAAgB,MAAM;GACtB,iBAAiB,MAAM;GACvB,UAAU,MAAM;EAClB;CACF,CAAC;CAMD,MAAM,iBAAiB;EACrB,KAAU,KAAK,CAAC,CAAC,cAAc,QAAQ,KAAK,GAAG,CAAC;CAClD;CACA,QAAQ,KAAK,UAAU,QAAQ;CAC/B,IAAI;EACF,MAAM,KAAK,MAAM;EACjB,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,QAAQ,eAAe,UAAU,QAAQ;EACzC,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-BUFOmSMl.js"),
68496
- import("./login-CcV7neWZ.js"),
68497
- import("./run-foreground-B-FPE0tJ.js"),
68495
+ import("./chat-CfRG8UQR.js"),
68496
+ import("./login-H-H07Q2J.js"),
68497
+ import("./run-foreground-BVC7qytz.js"),
68498
68498
  import("./service-6w0WKhdx.js"),
68499
68499
  import("./lock-CA9Bm8_s.js"),
68500
68500
  import("./paths-CcRUjFjy.js")
@@ -1,6 +1,6 @@
1
1
  import { t as DAEMON_VERSION } from "./version-DGaRXjBK.js";
2
2
  import { d as encode, l as buildEnvelope, t as isFrame, u as decodeEnvelope } from "./dist-BG425zHm.js";
3
- import { i as ClientTransport, t as runForeground } from "./run-foreground-DoM1PkHn.js";
3
+ import { i as ClientTransport, t as runForeground } from "./run-foreground-BZ63Vsmq.js";
4
4
  import { l as ConfigSchema } from "./load-agents-CROyMyq9.js";
5
5
  import { l as resolveRoot, t as configPath } from "./paths-DEcUO8lC.js";
6
6
  import { n as resolveController } from "./service-BQVw7wbz.js";
@@ -229,4 +229,4 @@ async function runLogin(opts, partial = {}) {
229
229
  //#endregion
230
230
  export { buildInstallOpts, persistCredentials, runLogin };
231
231
 
232
- //# sourceMappingURL=login-CcV7neWZ.js.map
232
+ //# sourceMappingURL=login-H-H07Q2J.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"login-CcV7neWZ.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-H-H07Q2J.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-BZ63Vsmq.js";
2
+ export { runForeground };
@@ -6,7 +6,7 @@ import { C as _coercedNumber, T as NEVER, a as array, d as literal, f as looseOb
6
6
  import { a as RELAY_DAEMON_WS_PATH, c as SHARED_CONFIG_ACTION_ID, d as encode, f as MAX_FRAME_BYTES, g as ZodIssueCode, h as RESERVED_MCP_SERVER_NAME, i as RELAY_DAEMON_SUBPROTOCOL, l as buildEnvelope, n as gitRepoLabel, o as buildRelayDaemonFrame, r as normalizeGitUrl, s as decodeRelayDaemonFrame, u as decodeEnvelope } from "./dist-BG425zHm.js";
7
7
  import { a as agentChildEnv, c as persistRelays, i as selectAgent, n as findAgentFiles, o as loadConfig, r as loadAgents, s as persistDaemonId } from "./load-agents-CROyMyq9.js";
8
8
  import { l as resolveRoot, o as mcpSocketPath, u as statePath } from "./paths-DEcUO8lC.js";
9
- import { C as resolveRuntimes, S as cachedRuntimeVersions, _ as resolveInMemoryDir, a as prepareWorkspace, b as toolsForIntegrations, c as sessionGitEnv, d as memoryKindOf, f as memoryProviderFor, g as listMemory, h as MemoryTooLargeError, i as prefetchWorkspace, l as simpleGit, m as MemoryPathError, n as turnFailureReason, o as initGitInjection, p as MemoryConflictError, r as installedRuntimes, s as probeGitVersion, t as AcpHost, u as createMemoryProvider, v as writeMemoryFile, x as cachedRuntimeNames, y as MEMORY_TOOL_NAMES } from "./acp-host-BDcV2bZY.js";
9
+ import { C as resolveRuntimes, S as cachedRuntimeVersions, _ as resolveInMemoryDir, a as prepareWorkspace, b as toolsForIntegrations, c as sessionGitEnv, d as memoryKindOf, f as memoryProviderFor, g as listMemory, h as MemoryTooLargeError, i as prefetchWorkspace, l as simpleGit, m as MemoryPathError, n as turnFailureReason, o as initGitInjection, p as MemoryConflictError, r as installedRuntimes, s as probeGitVersion, t as AcpHost, u as createMemoryProvider, v as writeMemoryFile, x as cachedRuntimeNames, y as MEMORY_TOOL_NAMES } from "./acp-host-DEjB1yFS.js";
10
10
  import { i as GitCredentialCache, n as gitcredSocketPath, r as writeGitcredShim, t as GitCredServer } from "./gitcred-server-Bu-Svl7L.js";
11
11
  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";
12
12
  import { createRequire } from "node:module";
@@ -6712,11 +6712,10 @@ var LocalStore = class {
6712
6712
  writeUsage(key, u) {
6713
6713
  this.db.prepare("UPDATE sessions SET usage = ? WHERE key = ?").run(JSON.stringify(u), key);
6714
6714
  }
6715
- /** Record the latest token counts for a session. ACP's `Usage` is already the
6716
- * running session total (not a per-turn delta), so this is latest-wins over the
6717
- * token fields — never additive. Only provided fields are updated; the
6718
- * context/cost snapshot is left intact. No-op on an unknown key (the row is
6719
- * created by the SessionManager first). */
6715
+ /** Record the latest token counts for a session when an adapter reports a
6716
+ * running session total. This is latest-wins over the token fields — never
6717
+ * additive. Only provided fields are updated; the context/cost snapshot is
6718
+ * left intact. No-op on an unknown key (the row is created first). */
6720
6719
  setTokenUsage(key, counts) {
6721
6720
  const u = this.getUsage(key);
6722
6721
  if (counts.totalTokens !== void 0) u.totalTokens = counts.totalTokens;
@@ -6727,6 +6726,22 @@ var LocalStore = class {
6727
6726
  if (counts.cachedWriteTokens !== void 0) u.cachedWriteTokens = counts.cachedWriteTokens;
6728
6727
  this.writeUsage(key, u);
6729
6728
  }
6729
+ /** Add one turn's token counts to the session total. codex-acp currently maps
6730
+ * Codex's `last_token_usage` into PromptResponse.usage, so its values are a
6731
+ * per-turn delta even though other ACP adapters return a session snapshot. */
6732
+ addTokenUsage(key, counts) {
6733
+ const u = this.getUsage(key);
6734
+ const add = (field, value) => {
6735
+ if (value !== void 0) u[field] = (u[field] ?? 0) + value;
6736
+ };
6737
+ add("totalTokens", counts.totalTokens);
6738
+ add("inputTokens", counts.inputTokens);
6739
+ add("outputTokens", counts.outputTokens);
6740
+ add("thoughtTokens", counts.thoughtTokens);
6741
+ add("cachedReadTokens", counts.cachedReadTokens);
6742
+ add("cachedWriteTokens", counts.cachedWriteTokens);
6743
+ this.writeUsage(key, u);
6744
+ }
6730
6745
  /** Overwrite the session's context-window + cost snapshot (latest `usage_update`
6731
6746
  * wins). Only provided fields are updated. No-op on an unknown key. */
6732
6747
  setUsageSnapshot(key, snap) {
@@ -6737,6 +6752,17 @@ var LocalStore = class {
6737
6752
  if (snap.costCurrency !== void 0) u.costCurrency = snap.costCurrency;
6738
6753
  this.writeUsage(key, u);
6739
6754
  }
6755
+ /** Add one turn's fallback cost to the session running total. Refuse to mix
6756
+ * currencies; a later ACP usage_update can still replace the total snapshot. */
6757
+ addCost(key, amount, currency) {
6758
+ if (!Number.isFinite(amount) || amount <= 0 || !currency) return false;
6759
+ const u = this.getUsage(key);
6760
+ if (u.costCurrency !== void 0 && u.costCurrency !== currency) return false;
6761
+ u.costAmount = (u.costAmount ?? 0) + amount;
6762
+ u.costCurrency = currency;
6763
+ this.writeUsage(key, u);
6764
+ return true;
6765
+ }
6740
6766
  /** Most-recent activity across an agent's non-closed sessions (epoch ms), or null
6741
6767
  * if it has none. Drives idle-host reaping (#111): a host with no recent session
6742
6768
  * activity AND no in-flight turn is past its idle window. */
@@ -215101,6 +215127,239 @@ var SystemMetrics = class {
215101
215127
  }
215102
215128
  };
215103
215129
  //#endregion
215130
+ //#region src/usage/openai-public-pricing.ts
215131
+ /**
215132
+ * Best-effort OpenAI Standard API list-price estimates for Codex turns.
215133
+ *
215134
+ * These values are deliberately static: the daemon must remain deterministic and
215135
+ * must not fetch pricing at runtime. Refresh the manifest with the repo-local
215136
+ * `$update-model-pricing` skill and keep the focused tests in sync.
215137
+ *
215138
+ * Source: https://developers.openai.com/api/docs/pricing
215139
+ * Verified: 2026-07-11
215140
+ */
215141
+ const OPENAI_PUBLIC_PRICING_AS_OF = "2026-07-11";
215142
+ const TOKENS_PER_MILLION = 1e6;
215143
+ const LONG_CONTEXT_THRESHOLD = 272e3;
215144
+ /** Exact public model ids only. Never prefix-match an unknown future model. */
215145
+ const MODEL_PRICING = {
215146
+ "gpt-5.6-sol": {
215147
+ standard: {
215148
+ input: 5,
215149
+ cachedInput: .5,
215150
+ cacheWrite: 6.25,
215151
+ output: 30
215152
+ },
215153
+ longContext: {
215154
+ input: 10,
215155
+ cachedInput: 1,
215156
+ cacheWrite: 12.5,
215157
+ output: 45
215158
+ }
215159
+ },
215160
+ "gpt-5.6-terra": {
215161
+ standard: {
215162
+ input: 2.5,
215163
+ cachedInput: .25,
215164
+ cacheWrite: 3.125,
215165
+ output: 15
215166
+ },
215167
+ longContext: {
215168
+ input: 5,
215169
+ cachedInput: .5,
215170
+ cacheWrite: 6.25,
215171
+ output: 22.5
215172
+ }
215173
+ },
215174
+ "gpt-5.6-luna": {
215175
+ standard: {
215176
+ input: 1,
215177
+ cachedInput: .1,
215178
+ cacheWrite: 1.25,
215179
+ output: 6
215180
+ },
215181
+ longContext: {
215182
+ input: 2,
215183
+ cachedInput: .2,
215184
+ cacheWrite: 2.5,
215185
+ output: 9
215186
+ }
215187
+ },
215188
+ "gpt-5.5": {
215189
+ standard: {
215190
+ input: 5,
215191
+ cachedInput: .5,
215192
+ output: 30
215193
+ },
215194
+ longContext: {
215195
+ input: 10,
215196
+ cachedInput: 1,
215197
+ output: 45
215198
+ }
215199
+ },
215200
+ "gpt-5.5-pro": {
215201
+ standard: {
215202
+ input: 30,
215203
+ output: 180
215204
+ },
215205
+ longContext: {
215206
+ input: 60,
215207
+ output: 270
215208
+ }
215209
+ },
215210
+ "gpt-5.4": {
215211
+ standard: {
215212
+ input: 2.5,
215213
+ cachedInput: .25,
215214
+ output: 15
215215
+ },
215216
+ longContext: {
215217
+ input: 5,
215218
+ cachedInput: .5,
215219
+ output: 22.5
215220
+ }
215221
+ },
215222
+ "gpt-5.4-pro": {
215223
+ standard: {
215224
+ input: 30,
215225
+ output: 180
215226
+ },
215227
+ longContext: {
215228
+ input: 60,
215229
+ output: 270
215230
+ }
215231
+ },
215232
+ "gpt-5.4-mini": { standard: {
215233
+ input: .75,
215234
+ cachedInput: .075,
215235
+ output: 4.5
215236
+ } },
215237
+ "gpt-5.3-codex": { standard: {
215238
+ input: 1.75,
215239
+ cachedInput: .175,
215240
+ output: 14
215241
+ } },
215242
+ "gpt-5.2-codex": { standard: {
215243
+ input: 1.75,
215244
+ cachedInput: .175,
215245
+ output: 14
215246
+ } },
215247
+ "gpt-5.2": { standard: {
215248
+ input: 1.75,
215249
+ cachedInput: .175,
215250
+ output: 14
215251
+ } },
215252
+ "gpt-5.2-pro": { standard: {
215253
+ input: 21,
215254
+ output: 168
215255
+ } },
215256
+ "gpt-5.1-codex-max": { standard: {
215257
+ input: 1.25,
215258
+ cachedInput: .125,
215259
+ output: 10
215260
+ } },
215261
+ "gpt-5.1-codex": { standard: {
215262
+ input: 1.25,
215263
+ cachedInput: .125,
215264
+ output: 10
215265
+ } },
215266
+ "gpt-5.1": { standard: {
215267
+ input: 1.25,
215268
+ cachedInput: .125,
215269
+ output: 10
215270
+ } },
215271
+ "gpt-5-codex": { standard: {
215272
+ input: 1.25,
215273
+ cachedInput: .125,
215274
+ output: 10
215275
+ } },
215276
+ "gpt-5": { standard: {
215277
+ input: 1.25,
215278
+ cachedInput: .125,
215279
+ output: 10
215280
+ } },
215281
+ "gpt-5.1-codex-mini": { standard: {
215282
+ input: .25,
215283
+ cachedInput: .025,
215284
+ output: 2
215285
+ } },
215286
+ "gpt-5-mini": { standard: {
215287
+ input: .25,
215288
+ cachedInput: .025,
215289
+ output: 2
215290
+ } },
215291
+ "codex-mini-latest": { standard: {
215292
+ input: 1.5,
215293
+ cachedInput: .375,
215294
+ output: 6
215295
+ } }
215296
+ };
215297
+ /** Aliases/snapshots that official model pages explicitly map to the rate above. */
215298
+ const MODEL_ALIASES = {
215299
+ "gpt-5.6": "gpt-5.6-sol",
215300
+ "gpt-5.5-2026-04-23": "gpt-5.5",
215301
+ "gpt-5.5-pro-2026-04-23": "gpt-5.5-pro",
215302
+ "gpt-5.4-2026-03-05": "gpt-5.4",
215303
+ "gpt-5.4-mini-2026-03-17": "gpt-5.4-mini",
215304
+ "gpt-5.4-pro-2026-03-05": "gpt-5.4-pro",
215305
+ "gpt-5.2-2025-12-11": "gpt-5.2",
215306
+ "gpt-5.2-pro-2025-12-11": "gpt-5.2-pro",
215307
+ "gpt-5.1-2025-11-13": "gpt-5.1",
215308
+ "gpt-5-2025-08-07": "gpt-5",
215309
+ "gpt-5-mini-2025-08-07": "gpt-5-mini"
215310
+ };
215311
+ function validCount(value) {
215312
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
215313
+ }
215314
+ /**
215315
+ * Estimate one Codex turn at public OpenAI Standard API token rates.
215316
+ *
215317
+ * codex-acp maps provider usage into disjoint ACP buckets: `inputTokens` is
215318
+ * non-cached input and `cachedReadTokens` is the cached subset it removed.
215319
+ * `thoughtTokens` is intentionally absent: reasoning is already a subset of output
215320
+ * and charging it again would double-count. The current adapter does not expose
215321
+ * GPT-5.6 cache writes, so those tokens remain in input at the regular input rate;
215322
+ * that known approximation is preferable to inventing an unavailable split.
215323
+ */
215324
+ function estimateOpenAiTurnCost(model, usage) {
215325
+ const rawModel = model?.trim();
215326
+ if (!rawModel) return {
215327
+ ok: false,
215328
+ reason: "model_missing"
215329
+ };
215330
+ const normalizedModel = MODEL_ALIASES[rawModel] ?? rawModel;
215331
+ const pricing = MODEL_PRICING[normalizedModel];
215332
+ if (!pricing) return {
215333
+ ok: false,
215334
+ reason: "model_unknown"
215335
+ };
215336
+ if (usage.inputTokens === void 0 || usage.outputTokens === void 0) return {
215337
+ ok: false,
215338
+ reason: "usage_incomplete"
215339
+ };
215340
+ if (!validCount(usage.inputTokens) || !validCount(usage.outputTokens)) return {
215341
+ ok: false,
215342
+ reason: "usage_invalid"
215343
+ };
215344
+ const cachedRead = usage.cachedReadTokens ?? 0;
215345
+ const cachedWrite = usage.cachedWriteTokens ?? 0;
215346
+ if (!validCount(cachedRead) || !validCount(cachedWrite)) return {
215347
+ ok: false,
215348
+ reason: "usage_invalid"
215349
+ };
215350
+ const isLongContext = usage.inputTokens + cachedRead + cachedWrite > LONG_CONTEXT_THRESHOLD && pricing.longContext !== void 0;
215351
+ const rates = isLongContext ? pricing.longContext : pricing.standard;
215352
+ return {
215353
+ ok: true,
215354
+ amount: (usage.inputTokens * rates.input + cachedRead * (rates.cachedInput ?? rates.input) + cachedWrite * (rates.cacheWrite ?? rates.input) + usage.outputTokens * rates.output) / TOKENS_PER_MILLION,
215355
+ currency: "USD",
215356
+ model: normalizedModel,
215357
+ pricingAsOf: OPENAI_PUBLIC_PRICING_AS_OF,
215358
+ basis: "public-api-standard",
215359
+ longContext: isLongContext
215360
+ };
215361
+ }
215362
+ //#endregion
215104
215363
  //#region src/daemon.ts
215105
215364
  /** Format an error for logs, surfacing a JSON-RPC/ACP RequestError's `code` and
215106
215365
  * `data` — for an agent-side `Internal error` the actionable detail (the adapter's
@@ -216125,6 +216384,18 @@ var Daemon = class Daemon {
216125
216384
  this.hostStartedAt.set(agentId, this.clock.now());
216126
216385
  return host;
216127
216386
  }
216387
+ /** Whether this agent is backed by Codex ACP. Registry ids are canonical, while
216388
+ * command/args matching keeps user-defined runtime aliases working. */
216389
+ isCodexRuntime(agentId) {
216390
+ const agent = this.agents.get(agentId);
216391
+ if (!agent) return false;
216392
+ const runtime = this.runtimes[agent.runtime];
216393
+ return [
216394
+ agent.runtime,
216395
+ runtime?.command,
216396
+ ...runtime?.args ?? []
216397
+ ].filter((part) => typeof part === "string").some((part) => /(?:^|[\\/])codex-acp(?:@[^\\/]*)?$/.test(part.toLowerCase()));
216398
+ }
216128
216399
  /** The bare Telegram message id from a normalized `telegram:<chatId>:<messageId>`
216129
216400
  * msgId (the last segment; chat ids may be negative, so split, don't parse). */
216130
216401
  telegramMessageId(msg) {
@@ -218118,6 +218389,8 @@ var Daemon = class Daemon {
218118
218389
  ...this.telegramReplyTarget(msg) !== void 0 ? { tgReplyTo: this.telegramReplyTarget(msg) } : {},
218119
218390
  statusThread,
218120
218391
  conn: replyConn,
218392
+ runtimeCostReported: false,
218393
+ usageReportSent: false,
218121
218394
  applyChain: Promise.resolve(),
218122
218395
  done,
218123
218396
  resolveDone,
@@ -218149,6 +218422,9 @@ var Daemon = class Daemon {
218149
218422
  this.log.info(`dispatch: skipped ready turn ${msg.msgId} (${readyGate})`);
218150
218423
  return null;
218151
218424
  }
218425
+ const modelOptions = host.modelOptions?.(sessionId) ?? null;
218426
+ const advertisedModel = modelOptions?.current;
218427
+ const turnModel = modelOptions === null ? override ?? agent.runtimeOverrides?.model : advertisedModel === "default" ? void 0 : advertisedModel;
218152
218428
  if (!wasRunning) this.showActivity(replyConn, msg.channel, statusThread, "is thinking…");
218153
218429
  this.emitStatusBar(p);
218154
218430
  const { stopReason, usage } = await host.prompt(sessionId, blocks);
@@ -218156,28 +218432,28 @@ var Daemon = class Daemon {
218156
218432
  if (p.outputSuppressed === "loop protection") finalPhase = "problem";
218157
218433
  return null;
218158
218434
  }
218159
- if (usage) this.store.setTokenUsage(key, {
218160
- totalTokens: usage.totalTokens,
218161
- inputTokens: usage.inputTokens,
218162
- outputTokens: usage.outputTokens,
218163
- thoughtTokens: usage.thoughtTokens ?? void 0,
218164
- cachedReadTokens: usage.cachedReadTokens ?? void 0,
218165
- cachedWriteTokens: usage.cachedWriteTokens ?? void 0
218166
- });
218167
- this.emitStatusBar(p);
218168
- const mergedUsage = this.store.getUsage(key);
218169
- if (Object.keys(mergedUsage).length > 0) try {
218170
- this.cpClient?.emitUsageReport({
218171
- sessionId,
218172
- agentId,
218173
- platform: msg.platform,
218174
- channel: msg.channel,
218175
- lastActivityAt: new Date(this.clock.now()).toISOString(),
218176
- usage: mergedUsage
218177
- });
218178
- } catch (err) {
218179
- this.log.debug(`usage report emit failed (session ${sessionId}): ${err.message}`);
218435
+ if (usage) {
218436
+ const counts = {
218437
+ totalTokens: usage.totalTokens,
218438
+ inputTokens: usage.inputTokens,
218439
+ outputTokens: usage.outputTokens,
218440
+ thoughtTokens: usage.thoughtTokens ?? void 0,
218441
+ cachedReadTokens: usage.cachedReadTokens ?? void 0,
218442
+ cachedWriteTokens: usage.cachedWriteTokens ?? void 0
218443
+ };
218444
+ if (this.isCodexRuntime(agentId)) {
218445
+ this.store.addTokenUsage(key, counts);
218446
+ if (!p.runtimeCostReported) {
218447
+ const estimate = estimateOpenAiTurnCost(turnModel, counts);
218448
+ if (estimate.ok) {
218449
+ if (!this.store.addCost(key, estimate.amount, estimate.currency)) this.log.debug(`cost fallback skipped for session ${sessionId}: existing currency differs from USD`);
218450
+ } else this.log.debug(`cost fallback unavailable for session ${sessionId} model=${turnModel ?? "(unknown)"}: ${estimate.reason}`);
218451
+ }
218452
+ } else this.store.setTokenUsage(key, counts);
218180
218453
  }
218454
+ this.emitStatusBar(p);
218455
+ this.emitStoredUsageReport(sessionId, agentId, msg.platform, msg.channel, key);
218456
+ p.usageReportSent = true;
218181
218457
  this.clearIdle(p);
218182
218458
  if (p.webchat) {
218183
218459
  if (p.webchat.replyText.trim()) this.store.appendTranscript({
@@ -218735,7 +219011,7 @@ var Daemon = class Daemon {
218735
219011
  const agent = this.agents.get(agentId);
218736
219012
  const usage = this.store.getUsage(sessionKey);
218737
219013
  const host = this.hosts.get(agentId);
218738
- const model = host?.modelOptions?.();
219014
+ const model = host?.modelOptions?.(acpSessionId);
218739
219015
  const effort = host?.effortOptions?.();
218740
219016
  const permissionMode = host?.permissionModeOptions?.();
218741
219017
  const fast = host?.fastModeOption?.();
@@ -219008,19 +219284,45 @@ var Daemon = class Daemon {
219008
219284
  rec.resolve({ action: "cancel" });
219009
219285
  }
219010
219286
  }
219287
+ /** Emit the daemon's latest merged usage snapshot. Used both at normal turn end
219288
+ * and when a late ACP usage_update corrects an already-reported fallback. */
219289
+ emitStoredUsageReport(sessionId, agentId, platform, channel, key, late = false) {
219290
+ const usage = this.store.getUsage(key);
219291
+ if (Object.keys(usage).length === 0) return;
219292
+ try {
219293
+ this.cpClient?.emitUsageReport({
219294
+ sessionId,
219295
+ agentId,
219296
+ platform,
219297
+ channel,
219298
+ lastActivityAt: new Date(this.clock.now()).toISOString(),
219299
+ usage
219300
+ });
219301
+ } catch (err) {
219302
+ this.log.debug(`${late ? "late " : ""}usage report emit failed (session ${sessionId}): ${err.message}`);
219303
+ }
219304
+ }
219011
219305
  onAcpUpdate(_agentId, sessionId, update) {
219012
219306
  const p = this.pending.get(sessionId);
219013
- if (!p) return;
219014
- if (p.outputSuppressed) return;
219307
+ if (p?.outputSuppressed) return;
219015
219308
  if (update?.sessionUpdate === "usage_update") {
219016
- this.store.setUsageSnapshot(p.sessionKey, {
219017
- contextUsed: update.used,
219018
- contextSize: update.size,
219019
- costAmount: update.cost?.amount ?? void 0,
219020
- costCurrency: update.cost?.currency ?? void 0
219021
- });
219022
- this.emitStatusBar(p);
219309
+ const rec = p ? void 0 : this.store.getSessionByAcpId(sessionId);
219310
+ const key = p?.sessionKey ?? rec?.key;
219311
+ if (key) {
219312
+ if (p && update.cost?.amount !== void 0) p.runtimeCostReported = true;
219313
+ this.store.setUsageSnapshot(key, {
219314
+ contextUsed: update.used,
219315
+ contextSize: update.size,
219316
+ costAmount: update.cost?.amount ?? void 0,
219317
+ costCurrency: update.cost?.currency ?? void 0
219318
+ });
219319
+ if (p) {
219320
+ this.emitStatusBar(p);
219321
+ if (p.usageReportSent) this.emitStoredUsageReport(sessionId, p.agentId, p.platform, p.channel, key, true);
219322
+ } else if (rec) this.emitStoredUsageReport(sessionId, rec.agentId, rec.platform, rec.channel, key, true);
219323
+ }
219023
219324
  }
219325
+ if (!p) return;
219024
219326
  if (update?.sessionUpdate === "session_info_update" && update.title !== void 0) {
219025
219327
  this.store.setSessionTitle(p.sessionKey, update.title);
219026
219328
  this.emitSessionMetadataSnapshot({
@@ -220150,4 +220452,4 @@ async function runForeground(opts, deps = {}) {
220150
220452
  //#endregion
220151
220453
  export { ClientTransport as i, CP_SUBPROTOCOL as n, CP_WS_PATH as r, runForeground as t };
220152
220454
 
220153
- //# sourceMappingURL=run-foreground-DoM1PkHn.js.map
220455
+ //# sourceMappingURL=run-foreground-BZ63Vsmq.js.map