agentix-cli 0.139.1 → 0.140.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/dist/{App-VINKWTYN.js → App-RJRJLBCF.js} +2 -2
- package/dist/{ChatApp-RGHQCRBX.js → ChatApp-YTGLBN47.js} +2 -2
- package/dist/{absorb-eval-AERHBOQA.js → absorb-eval-DZ6SCGT7.js} +2 -2
- package/dist/{agent-PRHZGF6J.js → agent-G32GSRUE.js} +2 -2
- package/dist/{board-dashboard-GBEBQUFJ.js → board-dashboard-HOZVSYMJ.js} +4 -4
- package/dist/{builtin-YD7MC572.js → builtin-U5S2XCNJ.js} +2 -2
- package/dist/{chunk-KO6BU3B3.js → chunk-2JQSWX7U.js} +3 -3
- package/dist/chunk-2JQSWX7U.js.map +1 -0
- package/dist/{chunk-JJZVFDG6.js → chunk-2NTRAVHC.js} +2 -2
- package/dist/{chunk-STIZFWDJ.js → chunk-4ZKXEUQP.js} +2 -2
- package/dist/chunk-6TTGRTDO.js +3 -0
- package/dist/chunk-6TTGRTDO.js.map +1 -0
- package/dist/{chunk-O6WW7ATO.js → chunk-BGKIKZMZ.js} +2 -2
- package/dist/{chunk-B65EC44C.js → chunk-C5ABUPRU.js} +2 -2
- package/dist/{chunk-QN2OTBVC.js → chunk-E47LEY5I.js} +2 -2
- package/dist/{chunk-IMUTN7PT.js → chunk-EBYG3NS3.js} +2 -2
- package/dist/{chunk-NQGLKWDO.js → chunk-EVQQZNZY.js} +2 -2
- package/dist/{chunk-J7U3BASX.js → chunk-FUGDD6UW.js} +2 -2
- package/dist/{chunk-ANDEMOYE.js → chunk-GG6K5ZL6.js} +2 -2
- package/dist/{chunk-JS2LEURJ.js → chunk-GPDVETDE.js} +2 -2
- package/dist/{chunk-RIYQNADV.js → chunk-GQOMV52Y.js} +57 -57
- package/dist/chunk-GQOMV52Y.js.map +1 -0
- package/dist/{chunk-FUJ4KEWS.js → chunk-HYVRS3G3.js} +301 -270
- package/dist/chunk-HYVRS3G3.js.map +1 -0
- package/dist/{chunk-XQVL2RYG.js → chunk-IOT5REOO.js} +2 -2
- package/dist/{chunk-2BEZU5OF.js → chunk-O6KRXVN7.js} +3 -3
- package/dist/chunk-O6KRXVN7.js.map +1 -0
- package/dist/{chunk-MUC7MMTT.js → chunk-OCTWRSOI.js} +2 -2
- package/dist/{chunk-EJIY45FZ.js → chunk-P5G6EYBL.js} +2 -2
- package/dist/{chunk-LMZCB7TT.js → chunk-PILE2SH3.js} +2 -2
- package/dist/{chunk-HXLB2XCT.js → chunk-PPT2MXQY.js} +3 -3
- package/dist/{chunk-NMYPZR4W.js → chunk-QHXKELKQ.js} +2 -2
- package/dist/{chunk-Z5IHT6RK.js → chunk-QSQWVQLD.js} +2 -2
- package/dist/chunk-TF5356HW.js +54 -0
- package/dist/{chunk-MGM33GOW.js.map → chunk-TF5356HW.js.map} +1 -1
- package/dist/{chunk-AXHP6TMT.js → chunk-U72JN4YF.js} +2 -2
- package/dist/{chunk-HF5EF335.js → chunk-VE3SSOQX.js} +4 -4
- package/dist/chunk-VE3SSOQX.js.map +1 -0
- package/dist/{chunk-H4NSB54D.js → chunk-XVTD6I34.js} +2 -2
- package/dist/{chunk-JBYLG3CQ.js → chunk-YVRQ7KWB.js} +2 -2
- package/dist/{chunk-U6EJ53DA.js → chunk-YWEGKAE2.js} +2 -2
- package/dist/{chunk-7KAQHNLT.js → chunk-Z46SZWMO.js} +2 -2
- package/dist/{chunk-HGU5B7IY.js → chunk-ZKNSNSV2.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/{client-2KZWK6YT.js → client-B275LQZY.js} +2 -2
- package/dist/config-PYDMLFAM.js +2 -0
- package/dist/{config-mutate-HXWAXWHC.js → config-mutate-BYRF4RJT.js} +2 -2
- package/dist/config-mutator-LZSY6S4H.js +2 -0
- package/dist/{contributions-OSIKL25B.js → contributions-SQVBSENQ.js} +2 -2
- package/dist/{doctor-TOSA3RWP.js → doctor-L6XMYZPY.js} +2 -2
- package/dist/{enrich-R2LIDLPZ.js → enrich-FPK3OW77.js} +2 -2
- package/dist/{event-levels-WOYYEWSZ.js → event-levels-NBOMTB23.js} +2 -2
- package/dist/{follow-up-tool-M2KCLRQQ.js → follow-up-tool-C72BXO4B.js} +2 -2
- package/dist/{hub-XGWCWKMT.js → hub-5OSJMIOO.js} +2 -2
- package/dist/index.d.ts +138 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{program-X6EKIO24.js → program-QJKOF3OJ.js} +24 -24
- package/dist/program-QJKOF3OJ.js.map +1 -0
- package/dist/{promote-YVWRKIAU.js → promote-C65WVH5K.js} +2 -2
- package/dist/{query-X4AFOKZ7.js → query-3LO5OP6O.js} +2 -2
- package/dist/{query-settings-HV2RFQK7.js → query-settings-TWT6XZNV.js} +2 -2
- package/dist/{registry-B276QJ75.js → registry-YI7NLDAL.js} +2 -2
- package/dist/{request-planner-MY6VKTVC.js → request-planner-D6XVDOJN.js} +2 -2
- package/dist/{review-candidates-BRTBB5XV.js → review-candidates-6BQ25RIY.js} +2 -2
- package/dist/{routing-CSVLOGNS.js → routing-KBNYAPTC.js} +2 -2
- package/dist/{rules-UVUJTVOE.js → rules-5ZZQZLQA.js} +2 -2
- package/dist/schedule-tool-GDUREPVN.js +2 -0
- package/dist/{scheduler-OUAVAU4O.js → scheduler-R6N7FT27.js} +2 -2
- package/dist/{seat-H2QR3CRX.js → seat-5AF4CN52.js} +2 -2
- package/dist/{select-EEL5AZ2I.js → select-TJZXA74W.js} +2 -2
- package/dist/send-answer-ZG7UHCVS.js +2 -0
- package/dist/{set-enabled-IBGEH6U6.js → set-enabled-RQPZBVZP.js} +2 -2
- package/dist/{tool-GJ75MWV5.js → tool-2RJYOQE7.js} +2 -2
- package/dist/{tool-DFPOFXGA.js → tool-C7Z56DOT.js} +2 -2
- package/dist/{wiki-ISKHNG3P.js → wiki-3DX53GJ4.js} +2 -2
- package/dist/wiki-enrich-KQNC45FM.js +2 -0
- package/dist/wiki-enrich-KQNC45FM.js.map +1 -0
- package/package.json +3 -2
- package/dist/chunk-2BEZU5OF.js.map +0 -1
- package/dist/chunk-FUJ4KEWS.js.map +0 -1
- package/dist/chunk-HF5EF335.js.map +0 -1
- package/dist/chunk-KO6BU3B3.js.map +0 -1
- package/dist/chunk-MGM33GOW.js +0 -54
- package/dist/chunk-RIYQNADV.js.map +0 -1
- package/dist/config-THFRJMZD.js +0 -2
- package/dist/config-mutator-JZOAWRBH.js +0 -2
- package/dist/program-X6EKIO24.js.map +0 -1
- package/dist/schedule-tool-GUKYTQTY.js +0 -2
- package/dist/wiki-enrich-7SBCPK2E.js +0 -2
- /package/dist/{App-VINKWTYN.js.map → App-RJRJLBCF.js.map} +0 -0
- /package/dist/{ChatApp-RGHQCRBX.js.map → ChatApp-YTGLBN47.js.map} +0 -0
- /package/dist/{absorb-eval-AERHBOQA.js.map → absorb-eval-DZ6SCGT7.js.map} +0 -0
- /package/dist/{agent-PRHZGF6J.js.map → agent-G32GSRUE.js.map} +0 -0
- /package/dist/{board-dashboard-GBEBQUFJ.js.map → board-dashboard-HOZVSYMJ.js.map} +0 -0
- /package/dist/{builtin-YD7MC572.js.map → builtin-U5S2XCNJ.js.map} +0 -0
- /package/dist/{chunk-JJZVFDG6.js.map → chunk-2NTRAVHC.js.map} +0 -0
- /package/dist/{chunk-STIZFWDJ.js.map → chunk-4ZKXEUQP.js.map} +0 -0
- /package/dist/{chunk-O6WW7ATO.js.map → chunk-BGKIKZMZ.js.map} +0 -0
- /package/dist/{chunk-B65EC44C.js.map → chunk-C5ABUPRU.js.map} +0 -0
- /package/dist/{chunk-QN2OTBVC.js.map → chunk-E47LEY5I.js.map} +0 -0
- /package/dist/{chunk-IMUTN7PT.js.map → chunk-EBYG3NS3.js.map} +0 -0
- /package/dist/{chunk-NQGLKWDO.js.map → chunk-EVQQZNZY.js.map} +0 -0
- /package/dist/{chunk-J7U3BASX.js.map → chunk-FUGDD6UW.js.map} +0 -0
- /package/dist/{chunk-ANDEMOYE.js.map → chunk-GG6K5ZL6.js.map} +0 -0
- /package/dist/{chunk-JS2LEURJ.js.map → chunk-GPDVETDE.js.map} +0 -0
- /package/dist/{chunk-XQVL2RYG.js.map → chunk-IOT5REOO.js.map} +0 -0
- /package/dist/{chunk-MUC7MMTT.js.map → chunk-OCTWRSOI.js.map} +0 -0
- /package/dist/{chunk-EJIY45FZ.js.map → chunk-P5G6EYBL.js.map} +0 -0
- /package/dist/{chunk-LMZCB7TT.js.map → chunk-PILE2SH3.js.map} +0 -0
- /package/dist/{chunk-HXLB2XCT.js.map → chunk-PPT2MXQY.js.map} +0 -0
- /package/dist/{chunk-NMYPZR4W.js.map → chunk-QHXKELKQ.js.map} +0 -0
- /package/dist/{chunk-Z5IHT6RK.js.map → chunk-QSQWVQLD.js.map} +0 -0
- /package/dist/{chunk-AXHP6TMT.js.map → chunk-U72JN4YF.js.map} +0 -0
- /package/dist/{chunk-H4NSB54D.js.map → chunk-XVTD6I34.js.map} +0 -0
- /package/dist/{chunk-JBYLG3CQ.js.map → chunk-YVRQ7KWB.js.map} +0 -0
- /package/dist/{chunk-U6EJ53DA.js.map → chunk-YWEGKAE2.js.map} +0 -0
- /package/dist/{chunk-7KAQHNLT.js.map → chunk-Z46SZWMO.js.map} +0 -0
- /package/dist/{chunk-HGU5B7IY.js.map → chunk-ZKNSNSV2.js.map} +0 -0
- /package/dist/{client-2KZWK6YT.js.map → client-B275LQZY.js.map} +0 -0
- /package/dist/{config-THFRJMZD.js.map → config-PYDMLFAM.js.map} +0 -0
- /package/dist/{config-mutate-HXWAXWHC.js.map → config-mutate-BYRF4RJT.js.map} +0 -0
- /package/dist/{config-mutator-JZOAWRBH.js.map → config-mutator-LZSY6S4H.js.map} +0 -0
- /package/dist/{contributions-OSIKL25B.js.map → contributions-SQVBSENQ.js.map} +0 -0
- /package/dist/{doctor-TOSA3RWP.js.map → doctor-L6XMYZPY.js.map} +0 -0
- /package/dist/{enrich-R2LIDLPZ.js.map → enrich-FPK3OW77.js.map} +0 -0
- /package/dist/{event-levels-WOYYEWSZ.js.map → event-levels-NBOMTB23.js.map} +0 -0
- /package/dist/{follow-up-tool-M2KCLRQQ.js.map → follow-up-tool-C72BXO4B.js.map} +0 -0
- /package/dist/{hub-XGWCWKMT.js.map → hub-5OSJMIOO.js.map} +0 -0
- /package/dist/{promote-YVWRKIAU.js.map → promote-C65WVH5K.js.map} +0 -0
- /package/dist/{query-X4AFOKZ7.js.map → query-3LO5OP6O.js.map} +0 -0
- /package/dist/{query-settings-HV2RFQK7.js.map → query-settings-TWT6XZNV.js.map} +0 -0
- /package/dist/{registry-B276QJ75.js.map → registry-YI7NLDAL.js.map} +0 -0
- /package/dist/{request-planner-MY6VKTVC.js.map → request-planner-D6XVDOJN.js.map} +0 -0
- /package/dist/{review-candidates-BRTBB5XV.js.map → review-candidates-6BQ25RIY.js.map} +0 -0
- /package/dist/{routing-CSVLOGNS.js.map → routing-KBNYAPTC.js.map} +0 -0
- /package/dist/{rules-UVUJTVOE.js.map → rules-5ZZQZLQA.js.map} +0 -0
- /package/dist/{schedule-tool-GUKYTQTY.js.map → schedule-tool-GDUREPVN.js.map} +0 -0
- /package/dist/{scheduler-OUAVAU4O.js.map → scheduler-R6N7FT27.js.map} +0 -0
- /package/dist/{seat-H2QR3CRX.js.map → seat-5AF4CN52.js.map} +0 -0
- /package/dist/{select-EEL5AZ2I.js.map → select-TJZXA74W.js.map} +0 -0
- /package/dist/{wiki-ISKHNG3P.js.map → send-answer-ZG7UHCVS.js.map} +0 -0
- /package/dist/{set-enabled-IBGEH6U6.js.map → set-enabled-RQPZBVZP.js.map} +0 -0
- /package/dist/{tool-GJ75MWV5.js.map → tool-2RJYOQE7.js.map} +0 -0
- /package/dist/{tool-DFPOFXGA.js.map → tool-C7Z56DOT.js.map} +0 -0
- /package/dist/{wiki-enrich-7SBCPK2E.js.map → wiki-3DX53GJ4.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agents/tool-set.ts","../src/decisions/seats/tool-set.ts","../src/agents/resume-gate.ts","../src/agents/registry.ts","../src/decisions/seats/session-continuity.ts","../src/agents/runtime.ts","../src/agents/codex-process.ts","../src/agents/error-map.ts","../src/agents/model-usage.ts","../src/agents/process-registry-instance.ts","../src/agents/process-registry.ts","../src/agents/claude-process-factory.ts","../src/guard/audit.ts","../src/guard/autonomy-enforce.ts","../src/guard/person-limits.ts","../src/agents/sessions.ts","../src/daemon/rate-limit.ts","../src/agents/context.ts","../src/agents/resume/origin.ts","../src/agents/message-queue.ts","../src/agents/patterns.ts","../src/events/bus.ts","../src/attach/registry.ts","../src/attach/types.ts","../src/attach/watch.ts","../src/attach/index.ts","../src/agents/overage-status.ts","../src/agents/claude-code-quota.ts","../src/agents/prompt-size-tracker.ts","../src/agents/session-profile.ts","../src/agents/agent-mcp.ts","../src/procedures/store.ts","../src/procedures/types.ts","../src/procedures/candidates.ts","../src/procedures/match.ts","../src/agents/turn-seats.ts","../src/decisions/seats/turn-progress.ts","../src/decisions/seats/turn-handoff.ts","../src/watchers/due.ts","../src/agents/until-aborted.ts","../src/agents/cloud-sessions.ts"],"sourcesContent":["// The tool-set seat on the live path (shadow only). See\n// src/decisions/seats/tool-set.ts for the question and the policy.\n//\n// Asked once per fresh claude-code session, alongside the run: nothing\n// waits for the answer, so shadow mode adds no time before the agent\n// starts (the lesson of the #637 review). When the run ends, the built-in\n// tools it called become the label, so every shadow row grades itself.\n\nimport { askSeat, getSeatMode, labelSeatCall } from \"@/decisions/seat\"\nimport {\n TOOL_SET_SEAT,\n bundleFor,\n isMiss,\n labelForTools,\n toolSetQuestions,\n toolSetState,\n type ToolBundle,\n type ToolSetAnswers,\n type ToolSetInput,\n} from \"@/decisions/seats/tool-set\"\n\nexport interface ToolSetGuess {\n callId: string | null\n /** The bundle the session would have started with. */\n bundle: ToolBundle\n probabilities: Record<string, number>\n}\n\nlet warnedActive = false\n\n/** Ask the seat without blocking. Resolves to null when the seat is off,\n * fails or times out; never rejects. */\nexport function startToolSetShadow(\n input: ToolSetInput & { taskId: string },\n log: (msg: string) => void,\n): Promise<ToolSetGuess | null> | undefined {\n const mode = getSeatMode(TOOL_SET_SEAT)\n if (mode === \"off\") return undefined\n if (mode === \"active\" && !warnedActive) {\n warnedActive = true\n log(`[decisions] seat \"${TOOL_SET_SEAT}\" is set to active, but only shadow is built: it records and changes nothing`)\n }\n return askSeat(TOOL_SET_SEAT, toolSetState(input), toolSetQuestions, {\n incumbent: { bundle: \"full\" },\n links: [{ kind: \"task\", id: input.taskId }],\n features: { agent: input.agent, channel: input.channel ?? \"unknown\" },\n })\n .then((result): ToolSetGuess | null => {\n if (!result) return null\n const answers = result.answers as ToolSetAnswers\n const bundle = bundleFor(answers)\n const probabilities = ((answers.bundle as any)?.probabilities ?? {}) as Record<string, number>\n return { callId: result.callId, bundle, probabilities }\n })\n .catch(() => null)\n}\n\n/** Label the shadow call with the smallest bundle the run actually needed.\n * Skipped when the run errored (its tool list is incomplete) or streamed\n * no events (no tool list was captured at all). */\nexport function labelToolSetRun(\n pending: Promise<ToolSetGuess | null>,\n run: { toolUses: Map<string, number>; eventsSeen: boolean; error?: string; agentId: string },\n log: (msg: string) => void,\n): Promise<void> {\n return pending.then((guess) => {\n if (!guess) return\n if (run.error || !run.eventsSeen) return\n const used = [...run.toolUses.keys()]\n const needed = labelForTools(used)\n labelSeatCall(guess.callId, \"bundle\", needed, { kind: \"outcome\", labeledBy: \"tool-use\" })\n const p = (k: string) => (typeof guess.probabilities[k] === \"number\" ? guess.probabilities[k].toFixed(2) : \"-\")\n log(\n `[${run.agentId}] tool-set shadow: would start with ${guess.bundle} ` +\n `(answer ${p(\"answer\")}, code ${p(\"code\")}, full ${p(\"full\")}); ` +\n `run used ${used.filter((n) => !n.startsWith(\"mcp__\")).join(\", \") || \"no built-in tool\"} → needed ${needed}` +\n (isMiss(guess.bundle, needed) ? \" — MISS\" : \"\"),\n )\n }).catch(() => { /* observability never breaks the run */ })\n}\n","import { choice } from \"../questions\"\nimport type { AnswersFor, ChoiceAnswer, StateValue } from \"../types\"\n\n// Which built-in Claude Code tools a fresh session needs.\n//\n// The built-in tool descriptions are about 14k tokens of a ~27k-token first\n// turn (#615), and every later call of the session re-reads them. A session\n// that only reads and answers does not need Edit, Write or Bash; a lean\n// session can already be started with a shorter list (`--tools`,\n// session.lean.tools). This seat asks, per fresh session, which of three\n// ordered bundles the task needs.\n//\n// Bundles, not single tools, on purpose. A Choice over three ordered sets\n// keeps a stable meaning as Claude Code adds tools, and leaves fewer ways\n// to forget one tool a task turns out to need. Anything outside the first\n// two bundles (web, subagents, notebooks, new tools) is \"full\".\n//\n// Direction of failure: a bundle that is too small makes the agent fail in\n// the middle of its task, which costs far more than the tokens saved. So\n// the policy picks a smaller bundle only when the chance of needing more is\n// below MISS_RISK, and anything uncertain, failed or missing is \"full\".\n//\n// Ground truth is free: every streamed run records which tools it called.\n// After the run, the smallest bundle that covers those calls is the label\n// (labelForTools), so the shadow rows grade themselves.\n//\n// Only shadow is implemented. \"active\" would also need a safety net that\n// restarts a turn with every tool when the agent reaches for a missing one;\n// until that exists the caller treats \"active\" as shadow.\n\nexport const TOOL_SET_SEAT = \"tool-set\"\n\nexport const TOOL_BUNDLES = [\"answer\", \"code\", \"full\"] as const\nexport type ToolBundle = (typeof TOOL_BUNDLES)[number]\n\n/** Built-in tools in each bundle. \"full\" means no `--tools` list at all. */\nexport const BUNDLE_TOOLS: Record<Exclude<ToolBundle, \"full\">, readonly string[]> = {\n answer: [\"Read\", \"Grep\", \"Glob\"],\n code: [\"Read\", \"Grep\", \"Glob\", \"Edit\", \"MultiEdit\", \"Write\", \"Bash\"],\n}\n\n/** A smaller bundle is picked only when the chance that the task needs more\n * than it is below this. */\nexport const MISS_RISK = 0.1\n\nexport interface ToolSetInput {\n /** The task, in the words it arrived in. */\n message: string\n agent: string\n channel?: string | null\n /** The agent's own instructions, clipped: they say what kind of work it\n * does (\"reviews pull requests\", \"answers billing questions\"). */\n agentPrompt?: string | null\n}\n\nexport function toolSetState(input: ToolSetInput): StateValue {\n return {\n task: clip(input.message, 2000),\n agent: input.agent,\n channel: input.channel ?? null,\n ...(input.agentPrompt ? { agentInstructions: clip(input.agentPrompt, 600) } : {}),\n }\n}\n\nexport const toolSetQuestions = {\n bundle: choice(\n {\n answer:\n \"Read-only: the task can be done by reading, searching and listing files and then replying. It changes no file and runs no command (no tests, no git, no scripts).\",\n code:\n \"Change files or run commands in the workspace: edit or write files, run tests, builds, git or shell commands. Still all inside the workspace, with no web access, no sub-agents and no notebooks.\",\n full:\n \"Needs more than that, or it is not clear: fetching or searching the web, starting sub-agents, notebooks, or anything the other two do not clearly cover.\",\n },\n \"Which set of built-in tools does an agent need to do this task completely? Pick the smallest set that is clearly enough.\",\n ),\n}\n\nexport type ToolSetAnswers = AnswersFor<typeof toolSetQuestions>\n\n/** The bundle the policy would start the session with: the smallest one\n * whose chance of being too small is below `risk`. Missing or malformed\n * answers give \"full\". */\nexport function bundleFor(answers: ToolSetAnswers | Record<string, unknown> | null | undefined, risk = MISS_RISK): ToolBundle {\n const a = (answers as Record<string, unknown> | null | undefined)?.bundle as ChoiceAnswer | undefined\n const p = a?.probabilities as Record<string, number> | undefined\n if (!p) return \"full\"\n const pa = num(p.answer), pc = num(p.code), pf = num(p.full)\n if (pa === null || pc === null || pf === null) return \"full\"\n if (pc + pf < risk) return \"answer\"\n if (pf < risk) return \"code\"\n return \"full\"\n}\n\n/** The smallest bundle that covers the built-in tools a run called. MCP\n * tools (`mcp__…`) are not governed by the bundle and are ignored. */\nexport function labelForTools(toolNames: Iterable<string>): ToolBundle {\n const builtIn = [...toolNames].filter((n) => !n.startsWith(\"mcp__\"))\n if (builtIn.every((n) => BUNDLE_TOOLS.answer.includes(n))) return \"answer\"\n if (builtIn.every((n) => BUNDLE_TOOLS.code.includes(n))) return \"code\"\n return \"full\"\n}\n\n/** True when starting with `picked` would have left out a tool the run used. */\nexport function isMiss(picked: ToolBundle, needed: ToolBundle): boolean {\n return TOOL_BUNDLES.indexOf(picked) < TOOL_BUNDLES.indexOf(needed)\n}\n\nfunction num(v: unknown): number | null {\n return typeof v === \"number\" && Number.isFinite(v) ? v : null\n}\n\nfunction clip(s: string, max: number): string {\n return s.length > max ? `${s.slice(0, max)}…` : s\n}\n","// --- Resume or start fresh: a cost gate (#621, step 3) ---\n//\n// `claude --resume` replays the whole transcript on every request of the\n// next turn. While the provider's prompt cache still holds it, that replay\n// is billed at the cache-read price (a tenth of input). Once the cache has\n// expired, the first request writes the whole transcript again at the\n// cache-write price, and every later request in the turn reads it back. A\n// fresh lean start pays the same pattern on a much smaller prompt.\n//\n// So this compares the two for the coming turn, in units of one input\n// token's price:\n//\n// resume = C × (first + read × (N − 1)) first = read if warm, else write\n// fresh = F × (write + read × (N − 1))\n//\n// C the transcript's size: the last request of the previous turn\n// (lastTurnContextTokens)\n// N requests per turn: the previous turn's billed input summed across its\n// requests, divided by C. Each request re-reads roughly the whole\n// context, so the ratio is close to the number of requests.\n// F the size of a fresh lean start (setting)\n//\n// What the coming turn adds is the same either way and drops out. Rotating\n// also costs continuity, which no price shows, so a fresh start must be\n// cheaper by `margin` before the gate picks it. It is the paper's \"compact\n// only when the saving beats the cache rewrite\" rule applied to session\n// reuse. Pure: no clock, no I/O, so it can be tested and backtested as is.\n\nexport type ResumeGateMode = \"off\" | \"shadow\" | \"active\"\n\nexport interface ResumeGateSettings {\n mode: ResumeGateMode\n /** Tokens a fresh lean session starts with. */\n freshTokens: number\n /** How long the cached transcript is assumed to survive. */\n cacheTtlMinutes: number\n /** Cache-write price as a multiple of the input price. */\n cacheWriteFactor: number\n /** A fresh start must be at least this many times cheaper. */\n margin: number\n}\n\n/** Cache-read price as a multiple of the input price. */\nexport const CACHE_READ_FACTOR = 0.1\n/** Requests per turn assumed when the last turn's numbers can't tell. */\nexport const DEFAULT_REQUESTS_PER_TURN = 10\nconst MAX_REQUESTS_PER_TURN = 200\n\nexport interface ResumeGateInput {\n /** End-of-turn context of the previous turn (lastTurnContextTokens). */\n contextTokens: number | null | undefined\n /** Billed input of the previous turn, summed across its requests. */\n turnInputTokens: number | null | undefined\n /** Time since the session last did anything. */\n idleMs: number\n}\n\nexport interface ResumeGateResult {\n /** True: start fresh is cheaper by the margin. */\n rotate: boolean\n /** Null when there is no context reading to judge by. */\n resumeCost: number | null\n freshCost: number\n warm: boolean\n requestsPerTurn: number\n}\n\n/** Requests in the previous turn, from its summed input and its context. */\nexport function requestsPerTurn(contextTokens: number | null | undefined, turnInputTokens: number | null | undefined): number {\n if (!contextTokens || contextTokens <= 0 || !turnInputTokens || turnInputTokens <= 0) return DEFAULT_REQUESTS_PER_TURN\n return Math.min(MAX_REQUESTS_PER_TURN, Math.max(1, Math.round(turnInputTokens / contextTokens)))\n}\n\nexport function decideResume(input: ResumeGateInput, s: ResumeGateSettings): ResumeGateResult {\n const n = requestsPerTurn(input.contextTokens, input.turnInputTokens)\n const warm = input.idleMs < s.cacheTtlMinutes * 60_000\n const later = CACHE_READ_FACTOR * (n - 1)\n const freshCost = s.freshTokens * (s.cacheWriteFactor + later)\n const c = input.contextTokens\n // Without a context reading (a non-streaming turn) there is nothing to\n // compare, and resuming is what the code did before this gate.\n if (!c || c <= 0) return { rotate: false, resumeCost: null, freshCost, warm, requestsPerTurn: n }\n const resumeCost = c * ((warm ? CACHE_READ_FACTOR : s.cacheWriteFactor) + later)\n return { rotate: resumeCost > freshCost * s.margin, resumeCost, freshCost, warm, requestsPerTurn: n }\n}\n\n/** One log line: what the gate saw and what it would do. */\nexport function describeResume(r: ResumeGateResult): string {\n const k = (x: number) => `${Math.round(x / 1000)}k`\n const cache = r.warm ? \"warm cache\" : \"cold cache\"\n if (r.resumeCost === null) return `resume: no context reading (${cache})`\n return `${r.rotate ? \"start fresh\" : \"resume\"}: resume ≈${k(r.resumeCost)}, fresh ≈${k(r.freshCost)} input-token equivalents (${cache}, ${r.requestsPerTurn} requests/turn)`\n}\n","import type { DaemonConfig, AgentDef } from \"@/daemon/config\"\nimport { cheapModelForEngine, triageModelFor } from \"./routing\"\nimport { labelToolSetRun, startToolSetShadow } from \"./tool-set\"\nimport { decideResume, describeResume } from \"./resume-gate\"\nimport { askSeat } from \"@/decisions/seat\"\nimport { PRE_SPAWN_SEAT_TIMEOUT_MS } from \"@/decisions/limits\"\nimport {\n SESSION_CONTINUITY_SEAT,\n continuityState,\n sessionContinuityQuestions,\n shouldRotateEarly,\n type ContinuityInput,\n type SessionContinuityAnswers,\n} from \"@/decisions/seats/session-continuity\"\nimport { executeTask, warmProcessChat, withCallerEnv, type AgentTask, type AgentResponse, type StreamCallback, type ThinkingCallback, type AgentPeer } from \"./runtime\"\nimport { friendlyModelError, renderFriendlyError } from \"./error-map\"\nimport { SessionStore, detectLongMemoryHint, priorUserMessage, priorUserRequests } from \"./sessions\"\nimport { shouldCaptureEntry } from \"@/wiki/capture-filter\"\nimport { WikiHub } from \"@/wiki\"\nimport { RateLimiter } from \"@/daemon/rate-limit\"\nimport { TokenTracker, splitTaskUsageByTier } from \"@/daemon/token-tracker\"\nimport { buildAgentContext, type ContextInput } from \"./context\"\nimport { injectedContextOf } from \"./injected-context\"\nimport { Classifier, GraphStore, type ClassifyResult } from \"@/graph\"\nimport { HandoverStore } from \"@/channels/handover-store\"\nimport { MemoryStore } from \"./memory-store\"\nimport { AgentMemory } from \"./agent-memory\"\nimport { extractMemories } from \"./memory-extract\"\nimport { serializeOrigin, type RunOrigin } from \"./resume/origin\"\nimport { MessageQueue, staleQueueNote, type QueueMode, type QueuedMessage } from \"./message-queue\"\nimport { isQueued, queuedMarker } from \"./queued\"\nimport { loadBootstrapFiles, buildBootstrapContext, detectSoulSwitch, listSoulProfiles } from \"./bootstrap\"\nimport { PatternStore, extractPatterns } from \"./patterns\"\nimport { loadReferences, renderReferences } from \"./references/loader\"\nimport { getDefaultLedger } from \"@/intent/instance\"\nimport { loadRecipes, resolveRecipes, type RecipeIndex } from \"./references/recipes\"\nimport type { ReferenceIndex } from \"./references/types\"\nimport { getEventBus } from \"@/events/bus\"\n\n/** How long the pipeline waits for the intent classification before\n * workflow matching (cache hits only) and before the wiki entry stamp. */\nconst INTENT_WAIT_BEFORE_MATCH_MS = 250\nconst INTENT_WAIT_BEFORE_ENTRY_MS = 5_000\nimport { digestEvents, renderDigest, type SubscriptionInput } from \"@/events/subscriptions\"\nimport { newEventId } from \"@/intent/ulid\"\nimport { currentRoot } from \"@/events/envelope\"\nimport { getAttachRegistry } from \"@/attach\"\nimport { isRestricted } from \"@/guard/autonomy\"\nimport { debug } from \"@/observability/debug\"\nimport type { LandscapeBuilder } from \"./landscape\"\nimport { preflightOverageGate } from \"./overage-status\"\nimport { getProcessRegistry } from \"./process-registry-instance\"\nimport { getMessageRouter } from \"@/channels/router-instance\"\nimport { preflightQuotaGate, recordClaudeCodeDispatch, recordRateLimitEvent, warnIfNearingCap, setDispatchBudget } from \"./claude-code-quota\"\nimport { promptSizeKey, recordPromptSize, warnIfPromptGrowing } from \"./prompt-size-tracker\"\nimport { describeProfile, fullClaudeArgs, leanClaudeArgs, leanConfig, leanLoadsWorkspace, onDemandContextNote, resolveSessionProfile } from \"./session-profile\"\nimport { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { WorkflowStore, matchWorkflow } from \"@/workflows\"\nimport { workflowHintText } from \"@/workflows/follow-up\"\nimport { continuedRun, endedWithoutFailing, finishWrap, isReadOnlyToolUse, liveStep, requiredFor, shouldWrap, startWrap, TASK_WORKFLOW_ID, toolUsesOf, wrapHintText } from \"@/workflows/required\"\nimport type { RunStore as WorkflowRunStore } from \"@/workflows/run-store\"\nimport { randomUUID } from \"crypto\"\nimport { ProcedureStore } from \"@/procedures\"\nimport { matchProcedures, renderProcedureContext } from \"@/procedures/match\"\nimport { onAgentReply, onUserMessage, startTurnWatch } from \"./turn-seats\"\nimport { isHumanFacingTurn } from \"@/a2a/initiator\"\nimport { isPickup, senderOf } from \"@/requests/tracker\"\nimport { isOperatorTurn } from \"@/requests/operator\"\nimport { dueAtOf } from \"@/watchers/due\"\nimport { personLimitsOf, nameMatches, operatorPerson, personOfTurn, refusedPerson } from \"@/people/people\"\nimport { MemberStore } from \"@/members/store\"\nimport { abortReason, untilAborted, withBudget, StepBudgetExceeded } from \"./until-aborted\"\nimport { CLOUD_SESSIONS_DEFAULTS, CloudSessionStore, decideCloudRoute, dispatchCloudSession, launchCloudSession, parseGitHubChatId as parseCloudTarget, type CloudRoute } from \"./cloud-sessions\"\nimport { buildAgentEnv, claudeBillingEnv } from \"@/utils/workspace-env\"\n\n/** Own limit for each preparation step, in ms. The run's pre-spawn\n * deadline is minutes; these are seconds, and a best-effort step that\n * loses its race is skipped rather than holding the run (#340). Steps\n * not listed run under the pre-spawn deadline alone. */\nconst STEP_BUDGET_MS: Record<string, number> = {\n \"request-gate\": 5_000,\n \"seed-history\": 30_000,\n skills: 10_000,\n rotate: 8_000,\n compact: 30_000,\n references: 10_000,\n \"plan-context\": 12_000,\n \"select-context\": 5_000,\n \"route-model\": 5_000,\n}\nimport { appAttachHint } from \"@/utils/artifact-sentinel\"\nimport { prepareOutbox } from \"@/utils/app-outbox\"\n\n// --- Agent Registry: lifecycle management + concurrency control ---\n\n/**\n * Context Surgery — coding-channel detector. A `claude-code` or `codex-cli`\n * tier agent triggered by a github/gitlab webhook is doing focused code work\n * on one issue/PR. The mesh-peer landscape and full session-history aren't\n * useful in that context and bias the agent on noise.\n */\n/** The code-first operating principle (Context Surgery, Fix 4) for coding\n * tiers, empty for the rest so chat/orchestrator agents are unaffected.\n * Lives in the cacheable system-prompt prefix. */\nconst CODE_FIRST_INSTRUCTION = \"[Operating principle]\\nAlways investigate the codebase before relying on issue history or comments. The code is the source of truth — start by reading relevant files (CLAUDE.md, then code), and consult conversation context only to clarify intent. Issue threads may contain wrong hypotheses; verify against the source.\"\n\n/** Claude Code only (Codex has no Edit tool). Without it, about a third of\n * runs of a six-file bug fix edited with a multi-line `sed`, left a\n * duplicate line and spent one to three more model calls repairing it.\n * With it, 0 of 20 runs needed an extra call against 7 of 20, and mean\n * tokens fell 13 percent; output tokens rise, since an edit spells out\n * the old and new text, so cost rose about 4 percent (#455,\n * bench/results/jev-and-edit-followups.md). */\nexport const EDIT_TOOL_INSTRUCTION = \"Change files with the Edit tool, one exact replacement per edit, not with sed, awk or a shell rewrite: a pattern that misses or matches twice leaves the file broken and costs another round. Several edits can go in one turn, and the tests can run in that same turn.\"\n\nexport function codingInstructions(tier: string): string {\n if (tier === \"claude-code\") return `${CODE_FIRST_INSTRUCTION}\\n${EDIT_TOOL_INSTRUCTION}`\n if (tier === \"codex-cli\") return CODE_FIRST_INSTRUCTION\n return \"\"\n}\n\nfunction isCodingChannelContext(channel: string, tier: string): boolean {\n if (tier !== \"claude-code\" && tier !== \"codex-cli\") return false\n return channel === \"github\" || channel === \"gitlab\"\n}\n\n/** Global singleton for sub-agent spawning from tools */\nlet globalRegistry: AgentRegistry | undefined\n\nexport function setGlobalRegistry(registry: AgentRegistry): void {\n globalRegistry = registry\n}\n\nexport function getGlobalRegistry(): AgentRegistry | undefined {\n return globalRegistry\n}\n\nexport interface RunningTask {\n /** Unique id for this execution (timestamp-based). */\n id: string\n /** The id the run's trace is kept under, and the one `task:started`\n * carries: what a request's run link holds. Absent until the trace\n * is allocated. */\n traceId?: string\n /** First 200 chars of the user message, for dense lists. */\n messagePreview: string\n /** The whole request. A task page shows what was actually asked, and the\n * preview cuts mid-sentence — or mid-JSON, which reads as a bug. */\n message: string\n /** Origin channel (telegram, whatsapp, gitlab, api, cron, business, a2a, …). */\n channel: string\n /** Group / chat / issue id from which the task arrived. */\n chatId?: string\n /** Sender id/name (user, agent id, \"cron:<id>\", \"mesh:<peer>\", …). */\n sender?: string\n /** Wall-clock start. */\n startedAt: Date\n /** The step the run is in right now (classify, compact, agent, …). A run\n * that hangs shows here where it stopped. */\n step?: string\n /** Intent-graph path this request was classified under, once known.\n * Classification runs alongside the turn, so it is absent until then. */\n intentPath?: string[]\n /** Step autonomy the turn runs at (report | propose | act). Unset = act. */\n autonomy?: AgentTask[\"autonomy\"]\n /** The workflow run this turn belongs to (#858): a step of a run, or\n * the run a task is wrapped in under workflows.required. `step` and\n * `title` are filled in when the agent list is read. */\n workflow?: { runId: string; workflowId?: string; step?: string; title?: string }\n}\n\ntype TaskOutputSubscriber = (chunk: string) => void\n\ninterface TaskOutput {\n agentId: string\n buffer: string\n subscribers: Set<TaskOutputSubscriber>\n done: boolean\n endedAt?: Date\n}\n\n/** Cap per-task buffer to keep memory bounded; recent tail wins. */\nconst TASK_OUTPUT_BUFFER_MAX = 64 * 1024\n/** Keep finished outputs around briefly so a late opener still gets the tail. */\nconst TASK_OUTPUT_TTL_MS = 5 * 60 * 1000\n/** How long a cancelled run's agent call may take to reap its subprocess\n * before the run lets go of its slot anyway. */\nconst CANCEL_GRACE_MS = 30_000\n/** Default for agents.<id>.preSpawnTimeoutSec when a definition skipped the schema. */\nconst PRE_SPAWN_TIMEOUT_SEC = 300\n\n/** Persisted record of a finished task, written to .agentx/task-history. */\nexport interface TaskRecord {\n id: string\n agentId: string\n channel: string\n chatId?: string\n sender?: string\n message: string\n startedAt: string\n endedAt: string\n durationMs: number\n ok: boolean\n error?: string\n /** Set when a deadline ended the run: `timeout`, with the step it was in;\n * `stopped` when a stop signal ended it (#857). */\n status?: \"timeout\" | \"stopped\"\n step?: string\n /** Final agent text (one-shot, may be empty if streaming captured it). */\n responseText: string\n /** Terminal-style transcript captured from stream-json events. */\n transcript: string\n}\n\n/** Outcome of AgentRegistry.continueFinishedTask. `taskId` is the new run's\n * dashboard id; absent when the message was queued behind a busy slot or\n * answered without taking a slot (`answeredBy`), so there is no run to open. */\nexport type ContinueFinishedTaskResult =\n | { ok: true; agentId: string; channel: string; chatId: string; taskId?: string; queued: boolean; answeredBy?: \"attached\" | \"other\" }\n | { ok: false; status: 404 | 409 | 500; error: string }\n\nconst TASK_HISTORY_DIR = \".agentx/task-history\"\n/** Default retention for persisted task records. Set for business audit trails;\n * bump via the `dashboard.taskHistoryRetentionDays` config field if you need more. */\nconst TASK_HISTORY_RETENTION_DAYS = 30\n\n/** Truncate long values so the buffer doesn't blow up on huge tool inputs/outputs. */\nfunction clip(s: string, max = 800): string {\n if (s.length <= max) return s\n return s.slice(0, max) + ` …[+${s.length - max} chars]`\n}\n\n/**\n * Phase 3 wiki layer — compact pointer to the `agentx wiki query` tool.\n * The wiki is the cross-agent institutional-knowledge source of truth.\n * Agents call it themselves when the question is about \"who / what\n * happened / what we decided / how we do X.\" Empty when the agent has no\n * catalog yet, so new installs see no noise.\n *\n * Uses an absolute CLI path because `agentx` is not on PATH in the agent\n * workspaces. `process.argv[1]` points at the running daemon's cli.js.\n */\n/**\n * The wiki, injected as content on a fresh session.\n *\n * It used to be injected as an invitation: a paragraph telling the agent\n * that an institutional wiki existed and it could shell out to\n * `wiki query` if it wanted to. Four other knowledge layers — skills,\n * patterns, procedures, memory — arrived as content, already in context,\n * free to read. The wiki was the only one that cost a subprocess.\n *\n * Agents behaved exactly as that economy predicts. Over three months of\n * traces the wiki was queried a handful of times out of tens of\n * thousands of steps, while the prompt called it \"the canonical source\n * for who people are, past events, decisions\". Calling something\n * canonical does not make it cheap to read, and agents read what is\n * cheap.\n *\n * So the catalog goes in directly. Titles by type is the map — enough to\n * know what the institution knows and to name an article precisely —\n * and the query command stays for depth. Bodies are still fetched on\n * demand; injecting 500 articles would be the opposite mistake.\n *\n * Fresh sessions only. Under `--resume` the whole transcript is\n * replayed, so this block is still in the agent's context from turn one;\n * re-rendering it every turn is pure bloat, and per-turn bloat is what\n * drove tier-2 rotation and the \"I have no prior context\" failures.\n */\n/**\n * Metadata stamped onto a captured wiki entry.\n *\n * Kept as a named function so the capture site stays readable and so the\n * shape is testable without standing up a registry. Empty objects\n * collapse to undefined — an entry with no metadata should carry no\n * frontmatter keys rather than an empty map that later parses as \"we\n * looked and there was nothing\".\n */\nexport function buildEntryMeta(\n intent: { path?: string[]; pathLabel?: string } | undefined,\n context: {\n sender?: string\n senderId?: string\n senderUsername?: string\n group?: string\n } | undefined,\n): Record<string, unknown> | undefined {\n const meta: Record<string, unknown> = {}\n if (intent?.path?.length) {\n meta.intentPath = intent.path\n meta.intentPathLabel = intent.pathLabel\n }\n if (context?.sender) meta.sender = context.sender\n if (context?.senderId) meta.senderId = context.senderId\n if (context?.senderUsername) meta.senderUsername = context.senderUsername\n // Only worth recording alongside a sender; on its own `sourceContext`\n // already holds it.\n if (context?.group && context?.sender) meta.group = context.group\n return Object.keys(meta).length > 0 ? meta : undefined\n}\n\n/** The `digest` block for a fresh session: events matched by the agent's\n * digest subscriptions since its last finished turn. Undefined when it has\n * none or nothing matched. Callers gate on !resumeSessionId. */\nexport function buildEventDigest(agentId: string, subs: SubscriptionInput[] | undefined): string | undefined {\n if (!subs?.some((s) => s.delivery === \"digest\")) return undefined\n const { shown, more } = digestEvents(agentId, subs, getEventBus().recent())\n return renderDigest(shown, more)\n}\n\n/** Where an agent posts a wiki note (#825), when notes are on. */\nfunction wikiNotesHint(config: DaemonConfig): { inbox: string; daemonUrl: string } | undefined {\n const n = config.wikiNotes\n if (!n?.enabled || !n.inbox) return undefined\n const [host, port] = (config.node?.bind || \"127.0.0.1:18800\").split(\":\")\n return { inbox: n.inbox, daemonUrl: `http://${!host || host === \"0.0.0.0\" ? \"127.0.0.1\" : host}:${port || \"18800\"}` }\n}\n\nexport function buildWikiContext(\n agentWiki: ReturnType<WikiHub[\"getAgentWiki\"]>,\n agentId: string,\n opts: { maxArticles?: number; notesInbox?: { inbox: string; daemonUrl: string } } = {},\n): string {\n let articles: Array<{ meta: { title: string; type?: string }; path: string }>\n try {\n articles = agentWiki.listArticles(agentId)\n } catch {\n return \"\"\n }\n if (articles.length === 0) return \"\"\n\n const byType = new Map<string, string[]>()\n for (const a of articles) {\n const t = a.meta.type || \"untyped\"\n const list = byType.get(t) || []\n list.push(a.meta.title)\n byType.set(t, list)\n }\n\n // A corpus larger than the budget degrades to counts per type rather\n // than a truncated list: knowing 140 people are on file and how to ask\n // beats seeing the first 40 names and assuming that is all of them.\n const max = opts.maxArticles ?? 400\n const listed = articles.length <= max\n\n const catalog = Array.from(byType.entries()).sort()\n .map(([type, titles]) => listed\n ? `**${type}** (${titles.length}) — ${titles.sort().join(\" · \")}`\n : `**${type}** — ${titles.length} article${titles.length === 1 ? \"\" : \"s\"}`)\n .join(\"\\n\")\n\n const cli = process.argv[1] || \"dist/cli.js\"\n const wikiDir = agentWiki.baseDir.replace(new RegExp(`/agents/${agentId}(/[^/]+)?$`), \"\")\n\n return [\n \"[Institutional Wiki — the single source of truth]\",\n `${articles.length} article${articles.length === 1 ? \"\" : \"s\"}. This is long-term memory for the whole fleet: who people are, what systems exist, what happened, what was decided, how we do things.`,\n \"\",\n \"It outranks every other context you are given. Your workspace memory, injected skills, mined procedures and behavioural patterns are all either staged into the wiki or derived from it — where any of them disagrees with the wiki, the wiki is right and the other is stale.\",\n \"\",\n \"Catalog:\",\n catalog,\n \"\",\n `For the contents of an article, or any question spanning several, run:\\n node ${cli} wiki query \"the question\" --dir ${wikiDir} --agent ${agentId}`,\n \"\",\n \"It walks the catalog and the wikilink graph and returns a cited answer. Ask it before you grep the workspace or answer from your own recollection.\",\n \"\",\n `Facts about outside systems (billing, accounts, outages, deploys) carry a source and a check date: \\`node ${cli} wiki facts list --dir ${wikiDir}\\`. Past its time limit a fact must be re-checked at the source before you state it; record what you checked with \\`wiki facts set ... --checked-at now\\`. If you can't check, say it is unverified and ask the owner.`,\n ...(opts.notesInbox && opts.notesInbox.inbox !== agentId ? [\n \"\",\n `When you learn something the wiki should know (a change, a correction, a fact that went stale), leave a short note for the wiki run: \\`node ${cli} wiki notes add --from ${agentId} --change \"what changed\" --source \"where you saw it\" --date YYYY-MM-DD --daemon ${opts.notesInbox.daemonUrl}\\`. The run checks it before it changes anything. Notes stay inside the fleet; never put secrets in one.`,\n ] : []),\n \"[End Institutional Wiki]\",\n ].join(\"\\n\")\n}\n\n/**\n * Format raw Claude Code stream-json events into a terminal-style transcript\n * for the dashboard streaming modal. Returns \"\" for events we don't surface.\n *\n * Stateful so we can emit only the *delta* of assistant text across consecutive\n * `assistant` snapshot events (Claude re-sends the cumulative content each tick).\n */\n/**\n * Cap a string at the configured byte budget, suffixing with \"…[+N]\" when\n * truncated so the trace reader knows there's more upstream. 8KB lets us\n * keep a useful preview of e.g. a Bash tool's stdout while bounding the\n * row size — anything larger almost always exceeds operator attention.\n */\nexport const TRACE_STEP_SUMMARY_BYTES = 8 * 1024\n/** A fresh phone chat: the outbox it names exists and old copies are gone\n * (utils/app-outbox.ts). The hint goes out whatever the disk says. */\nfunction withOutbox(workspace: string | undefined, hint: string): string {\n if (workspace) {\n try { prepareOutbox(workspace) } catch { /* the refusal still explains */ }\n }\n return hint\n}\n\nexport function clipForTrace(s: string): string {\n if (s.length <= TRACE_STEP_SUMMARY_BYTES) return s\n const remaining = s.length - TRACE_STEP_SUMMARY_BYTES\n return s.slice(0, TRACE_STEP_SUMMARY_BYTES) + `…[+${remaining}]`\n}\n\n/**\n * Translate a Claude stream-json event into zero-or-more task:step bus\n * events. Mirrors the block walks in makeStreamEventFormatter but emits\n * structured rows for SQLite persistence rather than terminal text.\n *\n * Why a separate function (and not extending the formatter)? Two\n * concerns. The formatter is for the dashboard's live terminal modal —\n * its output is a string and its job is to read like a transcript. The\n * trace store wants typed rows that survive a daemon restart. Keeping\n * them separate means future refactors of either don't ripple.\n */\n/**\n * Tally a single stream event's tool_use blocks into the running per-task\n * counter. Mirrors the block walks used elsewhere — assistant.tool_use\n * fires once per tool the model invoked. Called from the same onEvent\n * pipeline that drives trace step capture, so there's no extra parse pass.\n */\nexport function tallyToolUses(counter: Map<string, number>, event: any): void {\n if (!event || typeof event !== \"object\") return\n if (event.type !== \"assistant\") return\n const blocks = event.message?.content\n if (!Array.isArray(blocks)) return\n for (const block of blocks) {\n if (block?.type !== \"tool_use\") continue\n const name = typeof block.name === \"string\" ? block.name : \"\"\n if (!name) continue\n counter.set(name, (counter.get(name) ?? 0) + 1)\n }\n}\n\n/**\n * Improvement plan #3 — given an agent's `toolUseRequired` list and the\n * tally of tool_use invocations from this task, return the FIRST tool\n * name that wasn't invoked, or null when the contract was satisfied.\n *\n * Empty / unset toolUseRequired → null (no enforcement). Non-empty list\n * with at least one missing tool → the missing tool name; the caller\n * surfaces this as `tool_required_not_called: <name>` so retry logic\n * can distinguish \"model didn't follow the tool-use contract\" from\n * other error shapes.\n */\nexport function firstMissingRequiredTool(\n required: string[] | undefined,\n invoked: Map<string, number>,\n): string | null {\n if (!required || required.length === 0) return null\n for (const name of required) {\n if ((invoked.get(name) ?? 0) === 0) return name\n }\n return null\n}\n\nexport function emitTraceStepsFromStreamEvent(taskId: string, agentId: string, event: any): void {\n if (!event || typeof event !== \"object\") return\n const t = event.type\n const at = new Date().toISOString()\n const bus = getEventBus()\n\n if (t === \"assistant\" && event.message?.content) {\n for (const block of event.message.content) {\n if (block.type === \"tool_use\") {\n const inputJson = block.input != null ? JSON.stringify(block.input) : \"\"\n bus.emit(\"task:step\", {\n taskId,\n agentId,\n name: \"tool_use\",\n action: typeof block.name === \"string\" ? block.name : null,\n status: \"in-flight\",\n inputSummary: inputJson ? clipForTrace(inputJson) : null,\n at,\n } as any)\n }\n }\n return\n }\n if (t === \"user\" && event.message?.content) {\n for (const block of event.message.content) {\n if (block.type === \"tool_result\") {\n const content = Array.isArray(block.content)\n ? block.content.map((b: any) => (typeof b?.text === \"string\" ? b.text : \"\")).join(\"\")\n : typeof block.content === \"string\" ? block.content : \"\"\n bus.emit(\"task:step\", {\n taskId,\n agentId,\n // Action mirrors the originating tool name when Claude provides\n // tool_use_id. We don't have a quick lookup here without\n // threading more state, so leave action null and let consumers\n // pair via tool_use_id if they care.\n name: \"tool_result\",\n status: block.is_error ? \"error\" : \"ok\",\n outputSummary: content ? clipForTrace(content) : null,\n at,\n } as any)\n }\n }\n return\n }\n}\n\nfunction makeStreamEventFormatter(): (event: any) => string {\n let textSeen = \"\"\n let codexTextSeen = \"\"\n return (event: any): string => {\n if (!event || typeof event !== \"object\") return \"\"\n const t = event.type\n if (t === \"opencode.ready\") return `. opencode ${event.reused ? \"reused\" : \"started\"} ready=${event.startupMs}ms\\n`\n if (t === \"opencode.first_output\") return `. opencode first output=${event.elapsedMs}ms\\n`\n if (t === \"opencode.fallback\") return `. opencode using CLI fallback: ${event.reason}\\n`\n if (t === \"codex.ready\") return `. codex ${event.reused ? \"reused\" : \"started\"} ready=${event.startupMs}ms\\n`\n if (t === \"codex.first_output\") return `. codex first output=${event.elapsedMs}ms\\n`\n if (t === \"codex.fallback\") return `. codex using CLI fallback: ${event.reason}\\n`\n if (t === \"codex.spawned\") {\n const model = event.model ? ` model=${event.model}` : \"\"\n const resume = event.resumeSessionId ? ` resume=${String(event.resumeSessionId).slice(0, 8)}` : \"\"\n return `. codex started${model}${resume}\\n`\n }\n if (t === \"thread.started\") {\n const tid = event.thread_id ? ` thread=${String(event.thread_id).slice(0, 8)}` : \"\"\n return `. codex thread started${tid}\\n`\n }\n if (t === \"turn.started\") return \". codex turn started\\n\"\n if (t === \"turn.completed\") {\n const usage = event.usage || {}\n const input = usage.input_tokens ?? usage.inputTokens\n const output = usage.output_tokens ?? usage.outputTokens\n const tokens = input || output ? ` input=${input || 0} output=${output || 0}` : \"\"\n return `. codex turn completed${tokens}\\n`\n }\n if (t === \"item.started\" || t === \"item.completed\") {\n const item = event.item || {}\n if (item.type === \"agent_message\" && typeof item.text === \"string\") {\n const text = item.text\n const delta = text.startsWith(codexTextSeen) ? text.slice(codexTextSeen.length) : text\n codexTextSeen = text.startsWith(codexTextSeen) ? text : codexTextSeen + text\n return delta\n }\n const label = item.type || \"item\"\n const name = item.name || item.command || item.tool_name || item.id || \"\"\n if (t === \"item.started\") return `\\n> codex ${label}${name ? ` ${name}` : \"\"}\\n`\n const summary = item.output || item.result || item.text || item.error || \"\"\n return `< codex ${label}${name ? ` ${name}` : \"\"}${summary ? `: ${clip(String(summary), 800)}` : \"\"}\\n`\n }\n if (t === \"error\" || t === \"turn.failed\") {\n const msg = event.message || event.error || event.reason || JSON.stringify(event)\n return `[error] ${clip(String(msg), 800)}\\n`\n }\n if (t === \"system\" && event.subtype === \"init\") {\n const m = event.model ? ` model=${event.model}` : \"\"\n const sid = event.session_id ? ` session=${event.session_id.slice(0, 8)}` : \"\"\n return `· init${m}${sid}\\n`\n }\n if (t === \"assistant\" && event.message?.content) {\n let out = \"\"\n for (const block of event.message.content) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n if (block.text.length > textSeen.length) {\n out += block.text.slice(textSeen.length)\n textSeen = block.text\n }\n } else if (block.type === \"thinking\" && typeof block.thinking === \"string\") {\n out += `\\n💭 ${clip(block.thinking, 600)}\\n`\n } else if (block.type === \"tool_use\") {\n const name = block.name || \"tool\"\n const input = block.input ? clip(JSON.stringify(block.input), 400) : \"\"\n out += `\\n→ ${name}(${input})\\n`\n }\n }\n return out\n }\n if (t === \"user\" && event.message?.content) {\n let out = \"\"\n for (const block of event.message.content) {\n if (block.type === \"tool_result\") {\n const c = Array.isArray(block.content)\n ? block.content.map((b: any) => (typeof b?.text === \"string\" ? b.text : \"\")).join(\"\")\n : typeof block.content === \"string\" ? block.content : \"\"\n const flag = block.is_error ? \"← [error] \" : \"← \"\n out += `${flag}${clip(c, 800)}\\n`\n }\n }\n return out\n }\n if (t === \"result\") {\n const dur = event.duration_ms ? ` (${Math.round(event.duration_ms / 1000)}s)` : \"\"\n return `· done${dur}\\n`\n }\n return \"\"\n }\n}\n\ninterface AgentState {\n id: string\n def: AgentDef\n activeTasks: number\n totalTasks: number\n lastActive?: Date\n errors: number\n runningTasks: RunningTask[]\n}\n\nexport class AgentRegistry {\n private agents: Map<string, AgentState> = new Map()\n private config: DaemonConfig\n private providers: Record<string, { apiKey?: string }> = {}\n private sessions: SessionStore\n private wikiHub: WikiHub\n private memoryStore: MemoryStore\n /** Structured per-agent memory (Claude-Code-style: user / feedback /\n * project / reference). Separate from the BM25 fact store above —\n * that's for short-lived facts extracted from conversations; this is\n * for long-lived behavioural memory that inlines into the system\n * prompt on every task. */\n readonly agentMemory: AgentMemory = new AgentMemory()\n private patternStore: PatternStore\n private rateLimiter: RateLimiter\n private tokenTracker: TokenTracker\n private landscape?: LandscapeBuilder\n /** Mesh handle. When set, `execute` falls back to a peer that advertises\n * the requested agent before returning \"Unknown agent\". Duck-typed so\n * we don't create a circular import with src/a2a/mesh.ts. */\n private meshFallback?: {\n findPeerWithSkill: (skillId: string) => { peer: { url: string; token?: string } } | undefined\n sendTask: (peerName: string, text: string, agentId?: string, opts?: { timeoutMs?: number }) => Promise<string>\n directory: () => Array<{ peer: string; healthy: boolean; skills: Array<{ id: string }> }>\n }\n /** Workflow auto-runner — invoked when workflow matching is in `mode: \"auto\"`\n * and a candidate workflow's confidence ≥ autoRunThreshold. Wired by the\n * daemon after the workflow dispatcher is constructed. Returns the runId of\n * the started workflow run; the workflow itself owns posting any reply via\n * its `action.send` / `agent` nodes. Duck-typed to avoid a circular import\n * on WorkflowDispatcher. */\n private workflowAutoRunner?: (input: {\n workflowId: string\n agentId: string\n channel: string\n chatId: string\n message: string\n payload: Record<string, unknown>\n /** Start it as a followed run (#858: workflows.required). */\n follow?: boolean\n }) => Promise<{ runId?: string; title?: string }>\n /** Where wrapped tasks keep their run (#858). Wired by the daemon with\n * the workflow engine's run store; unset means no wrapping. */\n private workflowRuns?: WorkflowRunStore\n private messageQueue: MessageQueue\n /**\n * Voice chats a waiting question has claimed between its wait ending and\n * the chat being marked running (the rate limiter awaits in between).\n * Without the claim, two questions waiting on one chat could both see it\n * free and run at once.\n */\n private voiceClaims = new Set<string>()\n /** Intent Knowledge Graph classifier. Null when graph.enabled=false. */\n private classifier?: Classifier\n private graphStore?: GraphStore\n /** Runtime handover store — shared with MessageRouter via file on disk. */\n private handoverStore: HandoverStore = new HandoverStore()\n /** Active soul profile per agent+chat session: \"agentId:channel:chatId\" → profile name */\n private activeSouls: Map<string, string> = new Map()\n /** Live output captured per running task id — drives the dashboard streaming modal. */\n private taskOutputs: Map<string, TaskOutput> = new Map()\n /** Per-running-task AbortController. Set when execution starts, removed in\n * finally. Cancel paths (operator stop / replace) call .abort() and the\n * runtime kills the underlying claude subprocess.\n *\n * `originalMessage` is the user text recorded in SessionStore at the start\n * of this run. When the operator presses Stop first then Update, we use it\n * to drop the orphan cancelled turn from history so the Update reads as an\n * edit, not a bare follow-up. */\n private taskAborts: Map<string, {\n agentId: string; channel: string; chatId: string; originalMessage: string; controller: AbortController\n /** What a stop signal needs to save and later re-enter the run (#857). */\n traceId?: string; sender?: string; rootId?: string; origin?: RunOrigin\n /** Set when the run is a workflow step (#857: not stoppable with a plan). */\n workflowRunId?: string\n }> = new Map()\n /** Runs a daemon shutdown stopped, with the reason each one reports. */\n private interruptedRuns: Map<string, string> = new Map()\n /** Runs a stop signal ended, with the reason each one reports (#857). */\n private stoppedRuns: Map<string, string> = new Map()\n /** Callers waiting for a run to let go of its slot (whenRunEnds). */\n private runEndWaiters: Map<string, Array<() => void>> = new Map()\n /** Claude cloud sessions this node launched (#622), by issue. Created on\n * first use so a fleet with the setting off never touches the file. */\n private cloudSessions?: CloudSessionStore\n /** The context each running task was started with, by RunningTask id.\n * Kept apart from RunningTask because /agents serialises that, and a\n * context can carry a whole conversation history. Read by A2A\n * delegation to find who started the caller's turn (#277). */\n private runningContexts = new Map<string, NonNullable<AgentTask[\"context\"]>>()\n /** Idempotent cleanup per running task id, for runs that end before their\n * own `finally` can run. */\n private runReleases: Map<string, (response: AgentResponse | undefined) => void> = new Map()\n /** Last completed task summary per agent — single-line blurb for the dashboard card. */\n private lastSummaries: Map<string, { text: string; at: Date; ok: boolean }> = new Map()\n /** 24-hour sparkline cache per agent — recomputed from disk at most once a minute. */\n private sparklineCache: Map<string, { hourly: number[]; at: number }> = new Map()\n /** Per-workspace references registry cache. Loaded lazily on first turn for\n * agents with `contextReferences: true`; reused for the daemon's lifetime\n * (operator-edited registries take effect on next daemon restart — same\n * policy as agentx.json). */\n private referencesCache: Map<string, { refs: ReferenceIndex; recipes: RecipeIndex }> = new Map()\n private log: (...args: unknown[]) => void\n\n constructor(\n config: DaemonConfig,\n log: (...args: unknown[]) => void = console.error.bind(console, \"[agents]\"),\n ) {\n this.log = log\n this.config = config\n this.providers = config.providers\n this.sessions = new SessionStore(process.cwd(), {\n staleMinutes: config.session.staleMinutes,\n maxTurnsPerSession: config.session.maxTurnsPerSession,\n tierTwoThresholdTokens: config.session.tierTwoThresholdTokens,\n tierTwoThresholdTokensByChannel: config.session.tierTwoThresholdTokensByChannel,\n })\n // graph/ is the canonical article folder: the CLI, the MCP wiki tool\n // and `agentx wiki prune` all use it. Reading \"unified\" here gave fresh\n // sessions an empty catalog and the mesh /wiki endpoints no articles\n // (#603).\n this.wikiHub = new WikiHub(undefined, undefined, \"graph\")\n this.memoryStore = new MemoryStore()\n this.patternStore = new PatternStore()\n this.rateLimiter = new RateLimiter()\n this.tokenTracker = new TokenTracker()\n this.messageQueue = new MessageQueue()\n\n // Apply the claude-code fleet dispatch budget. See DaemonConfig.session\n // for the tuning knobs. Pools all claude-code agents under one counter\n // because they share the Max OAuth.\n setDispatchBudget({\n maxPerHour: config.session.maxClaudeCodeDispatchesPerHour,\n maxPer5h: config.session.maxClaudeCodeDispatchesPer5h,\n })\n\n if (config.graph?.enabled) {\n this.graphStore = new GraphStore({\n baseDir: resolve(process.cwd(), config.graph.baseDir),\n log: (...a) => log(\"[graph]\", ...a),\n })\n const draftAgent = config.graph.draftAgent || config.dashboard?.draftAgent\n this.classifier = new Classifier({\n store: this.graphStore,\n daemonUrl: config.dashboard.daemonUrl,\n token: config.dashboard.token,\n draftAgent,\n autoApproveStructure: config.graph.autoApproveStructure,\n autoApproveConfidence: config.graph.autoApproveConfidence,\n classifierModel: config.graph.classifierModel,\n log: (...a) => log(\"[classifier]\", ...a),\n })\n }\n\n for (const [id, def] of Object.entries(config.agents)) {\n this.agents.set(id, {\n id,\n def,\n activeTasks: 0,\n totalTasks: 0,\n errors: 0,\n runningTasks: [],\n })\n }\n }\n\n /**\n * Set landscape builder (called after mesh init).\n */\n setLandscape(builder: LandscapeBuilder): void {\n this.landscape = builder\n }\n\n /** Wire the mesh so `execute` can fall back to a peer that hosts an\n * agent this node doesn't have. Called by the daemon after mesh boot.\n * Pass `undefined` to disable fallback (testing, mesh disabled). */\n setMeshFallback(mesh: NonNullable<AgentRegistry[\"meshFallback\"]>): void {\n this.meshFallback = mesh\n }\n\n /** Wire the workflow auto-runner — see field doc above. Daemon calls this\n * after WorkflowDispatcher boot. Pass undefined to detach (e.g. during\n * reload). */\n setWorkflowRunStore(runs: WorkflowRunStore | undefined): void {\n this.workflowRuns = runs\n }\n\n setWorkflowAutoRunner(runner: NonNullable<AgentRegistry[\"workflowAutoRunner\"]> | undefined): void {\n this.workflowAutoRunner = runner\n }\n\n /** Hot-swap the provider map. Daemon reload calls this after agentx.json\n * changes — the next task that executes will resolve credentials through\n * the fresh table (see executeTask call-site). In-flight tasks keep the\n * old provider reference from their closure, which is the desired behavior\n * (rotating a key mid-task shouldn't fail the task). */\n setProviders(next: Record<string, { apiKey?: string }>): void {\n this.providers = next\n }\n\n /** Hot-swap the live DaemonConfig reference. Registry reads it lazily at\n * execute-time for landscape + session policies, so next-task semantics\n * match setProviders. */\n setConfig(next: DaemonConfig): void {\n this.config = next\n }\n\n /** Swap the workflows block on a config reload (#858): matching,\n * follow-up and required settings are read per turn. */\n setWorkflows(workflows: DaemonConfig[\"workflows\"]): void {\n this.config = { ...this.config, workflows }\n }\n\n /** Swap the people list on a config reload (#384). Turns read it when\n * they start, so the next turn is stamped from the new list. */\n setPeople(people: DaemonConfig[\"people\"]): void {\n this.config = { ...this.config, people }\n }\n\n /** People permissions (#379): the note a limited person is answered with\n * instead of reaching `agentId`, or null when the turn may go ahead. The\n * limit follows them through a delegation (the root's person). Every way\n * to an agent asks here before it starts: a run on this node, and the\n * router and delegation paths to a mesh peer, which cannot check for\n * itself (person ids are per machine). A refusal is kept in the\n * per-person trail. */\n refusalFor(agentId: string, ctx: unknown): string | null {\n const refused = refusedPerson(this.config.people, agentId, ctx as any)\n if (!refused) return null\n this.log(`[${agentId}] refused: ${refused.person.id} may not reach this agent (people[].agents)`)\n this.memberTrail().log({ person: refused.person.id, event: \"agent-refused\", detail: agentId })\n return refused.note\n }\n\n /** A tool or skill call the guard hook denied for a person (#379), kept\n * in their trail beside refused agents. */\n logToolRefusal(personId: string, agentId: string | undefined, detail: string): void {\n this.log(`[${agentId ?? \"?\"}] blocked for ${personId}: ${detail} (people[].deny)`)\n this.memberTrail().log({ person: personId, event: \"tool-refused\", detail })\n }\n\n private memberTrail(): MemberStore {\n const days = this.config.members.logRetentionDays\n if (!this.memberLog || this.memberLog.days !== days) this.memberLog = { days, store: new MemberStore(process.cwd(), Date.now, days) }\n return this.memberLog.store\n }\n private memberLog?: { days: number; store: MemberStore }\n\n /**\n * If there's an active handover routing TO this agent for this (channel,\n * chatId) pair AND the operator's summary hasn't been consumed yet, pull\n * + clear it so the target agent sees the note exactly once.\n */\n private buildHandoverNote(\n agentId: string,\n channel: string,\n chatId: string,\n ): ContextInput[\"handoverNote\"] {\n const o = this.handoverStore.get(channel, chatId)\n if (!o || o.toAgent !== agentId) return undefined\n const summary = this.handoverStore.consumeSummary(channel, chatId)\n // If already consumed on a prior message, skip — route remains active\n // but no repeated briefing in every turn.\n if (!summary && o.summaryConsumedAt) return undefined\n return {\n fromAgent: o.fromAgent,\n summary: summary || o.summary,\n at: o.createdAt,\n }\n }\n\n /**\n * Get agent definition by ID.\n */\n getAgent(id: string): AgentDef | undefined {\n return this.agents.get(id)?.def\n }\n\n /**\n * Find agent by mention pattern (e.g., \"@my_bot\" -> \"my-agent\").\n * Returns the agent with the longest (most specific) mention match.\n *\n * When `atMentionsOnly` is true, only `@`-prefixed mentions are considered.\n * This is used for messages originating from another bot (cross-daemon\n * Telegram cascades, for example) where we don't want bare-word matches\n * like \"marketing\" in prose or \"devops-globex\" quoted in a reply to trigger\n * agents spuriously. Intentional handoffs still work because agents\n * write explicit `@acme_X_bot` handles.\n */\n findByMention(text: string, opts: { atMentionsOnly?: boolean } = {}): string | undefined {\n const lower = text.toLowerCase()\n let bestId: string | undefined\n let bestLen = 0\n\n for (const [id, state] of this.agents) {\n for (const mention of state.def.mentions) {\n if (opts.atMentionsOnly && !mention.startsWith(\"@\")) continue\n const mentionLower = mention.toLowerCase()\n if (lower.includes(mentionLower) && mentionLower.length > bestLen) {\n bestId = id\n bestLen = mentionLower.length\n }\n }\n }\n\n return bestId\n }\n\n /**\n * Find ALL agents mentioned in text (for bot-to-bot detection).\n */\n findAllMentioned(text: string): string[] {\n const lower = text.toLowerCase()\n const found: string[] = []\n\n for (const [id, state] of this.agents) {\n for (const mention of state.def.mentions) {\n if (lower.includes(mention.toLowerCase())) {\n found.push(id)\n break\n }\n }\n }\n\n return found\n }\n\n /** Wiki hub accessor. */\n getWikiHub(): WikiHub { return this.wikiHub }\n getGraphStore(): GraphStore | undefined { return this.graphStore }\n /** Session-store accessor — used by the /recall HTTP endpoint to expose\n * conversation history to agents that need to rebuild context. */\n getSessionStore(): SessionStore { return this.sessions }\n\n /** Fire-and-forget pre-rotation memo capture. Spawns a one-shot Haiku\n * call against the about-to-be-dropped Claude session asking for the\n * facts/decisions worth carrying forward, persists the result into\n * MemoryStore. Runs asynchronously so the calling rotation path\n * doesn't pay the extraction latency — the next user turn dispatches\n * immediately on the fresh session, and the memo lands in time for\n * whichever turn arrives ~30s later (which is when atlas's bloated-\n * session WhatsApp conversations actually need it).\n *\n * Errors are swallowed by design — memory continuity is best-effort,\n * it must never block or break the rotation it's hooked into. */\n /**\n * Ask the session-continuity seat whether to rotate before any\n * mechanical trigger fires. Returns true only for a confident,\n * active-mode early rotation.\n *\n * Never throws: askSeat is fail-open by contract, and everything else\n * here is a guarded read. Off, shadow, backend error and missing\n * history all return false, which is today's behaviour exactly.\n */\n private async maybeRotateForContinuity(\n task: AgentTask,\n state: { def: AgentDef },\n channel: string,\n chatId: string,\n resumeSessionId: string,\n ): Promise<boolean> {\n let previousMessage: string | null = null\n let minutesSinceLastTurn: number | null = null\n let recentRequests: string[] = []\n try {\n const session = this.sessions.getSession(task.agentId, channel, chatId)\n // Not simply the last user message: this turn's request was already\n // appended upstream, so that would compare the request to itself.\n // See priorUserMessage for why the seat stays shut when it does.\n previousMessage = priorUserMessage(session.messages, task.message ?? \"\")\n recentRequests = priorUserRequests(\n session.messages,\n task.message ?? \"\",\n this.config.session.continuityStateTurns,\n )\n if (session.updatedAt) {\n minutesSinceLastTurn = Math.round((Date.now() - Date.parse(session.updatedAt)) / 60_000)\n }\n } catch {\n /* a seat never breaks dispatch over missing history */\n }\n\n const input: ContinuityInput = {\n message: task.message ?? \"\",\n previousMessage,\n recentRequests,\n minutesSinceLastTurn,\n turnCount: this.sessions.getTurnCount(task.agentId, channel, chatId),\n lastTurnContextTokens:\n this.sessions.getLastTurnContextTokens(task.agentId, channel, chatId) || null,\n agentId: task.agentId,\n channel,\n }\n\n const result = await askSeat(\n SESSION_CONTINUITY_SEAT,\n continuityState(input),\n sessionContinuityQuestions,\n {\n // What the code does today: no mechanical trigger fired, so resume.\n incumbent: { continues: \"yes\", needsHistory: \"yes\" },\n links: [{ kind: \"session\", id: `${task.agentId}:${channel}:${chatId}` }],\n features: { agent: task.agentId, channel },\n // Pre-spawn: a slow answer is worth less than the seconds it\n // costs. The backend default (30s) was the cap before this.\n timeoutMs: PRE_SPAWN_SEAT_TIMEOUT_MS,\n },\n )\n if (!result || result.mode !== \"active\") return false\n\n const rotate = shouldRotateEarly(result.answers as SessionContinuityAnswers, {\n mechanicalRotation: false,\n })\n if (!rotate) return false\n\n this.log(`[${task.agentId}] continuity rotation for ${channel}:${chatId} (new subject)`)\n void this.captureRotationMemoAsync(\n task.agentId, state.def, resumeSessionId, channel, chatId, \"continuity\",\n )\n this.sessions.clearClaudeSessionId(task.agentId, channel, chatId)\n getEventBus().emit(\"session:rotated\", {\n agentId: task.agentId, channel, chatId,\n reason: \"continuity\",\n at: new Date().toISOString(),\n })\n return true\n }\n\n private async captureRotationMemoAsync(\n agentId: string,\n def: AgentDef,\n resumeSessionId: string,\n channel: string,\n chatId: string,\n reason: string,\n ): Promise<void> {\n try {\n const { extractRotationMemo } = await import(\"./rotation-memo\")\n const result = await extractRotationMemo(def, resumeSessionId)\n if (!result.memo) {\n this.log(\n `[${agentId}] rotation memo skipped (${reason}, ${result.reason}, ${result.durationMs}ms)`,\n )\n return\n }\n // Claims go to wiki proposals, work state to memory (#273).\n const { storeRotationMemo } = await import(\"./rotation-memo-store\")\n const stored = storeRotationMemo(this.memoryStore, this.sessions, {\n wikiDir: this.wikiHub.getBaseDir(), agentId, channel, chatId, reason, memo: result.memo,\n })\n if (stored.error) this.log(`[${agentId}] memo claims not proposed: ${stored.error}`)\n this.log(\n `[${agentId}] rotation memo captured (${reason}, ${result.durationMs}ms, ${result.memo.length} chars, ` +\n `${stored.proposals} claim(s) proposed to the wiki)`,\n )\n } catch (e: any) {\n this.log(`rotation memo error: ${e?.message || e}`)\n }\n }\n\n /**\n * Build peer list for context engine.\n */\n private buildPeerList(agentId: string, channel?: string): Array<{ name: string; handle?: string; role?: string }> {\n const peers: Array<{ name: string; handle?: string; role?: string }> = []\n for (const [id, state] of this.agents) {\n if (id === agentId) continue\n peers.push({\n name: state.def.name,\n handle: this.getChannelHandle(id, channel),\n role: state.def.systemPrompt?.split(\"\\n\")[0]?.slice(0, 80),\n })\n }\n return peers\n }\n\n /**\n * Get the primary channel handle for an agent (e.g. \"@my_bot\" on telegram).\n */\n private getChannelHandle(agentId: string, channel?: string): string | undefined {\n const agent = this.agents.get(agentId)?.def\n if (!agent) return undefined\n\n // For telegram, find the mention that starts with @ (bot username)\n if (channel === \"telegram\") {\n return agent.mentions.find((m) => m.startsWith(\"@\"))\n }\n\n // Fallback: first mention\n return agent.mentions[0]\n }\n\n /** The store of cloud sessions this node launched (#622). */\n private cloudSessionStore(): CloudSessionStore {\n if (!this.cloudSessions) this.cloudSessions = new CloudSessionStore(resolve(process.cwd(), \".agentx/cloud-sessions.json\"))\n return this.cloudSessions\n }\n\n /** Where this task runs (#622): a Claude cloud session, a follow-up to one\n * already open for its issue, or locally. Decided before the dispatch\n * gates, which only guard a local spawn. Cheap when the setting is off. */\n private planCloudSession(def: AgentDef, task: AgentTask): CloudRoute {\n const settings = { ...CLOUD_SESSIONS_DEFAULTS, ...(def.cloudSessions ?? {}) }\n if (!settings.enabled) return { route: \"local\", reason: \"cloud sessions are off for this agent\" }\n const channel = task.context?.channel || \"api\"\n const channelCfg = (this.config.channels as Record<string, { cloudSessions?: boolean } | undefined>)?.[channel]\n const target = parseCloudTarget(task.context?.chatId)\n const store = this.cloudSessionStore()\n return decideCloudRoute({\n agent: def,\n channelEnabled: channelCfg?.cloudSessions === true,\n context: task.context,\n openSession: target ? store.open(target.repo, target.number, settings.openHours) : undefined,\n launchesToday: store.launchesToday(task.agentId),\n })\n }\n\n /** Launches the cloud session (or forwards to the open one) and records\n * it on the trace. Null means: run locally, the reason is logged and on\n * the trace. */\n private async runCloudSession(def: AgentDef, task: AgentTask, route: CloudRoute, traceId: string): Promise<AgentResponse | null> {\n const settings = { ...CLOUD_SESSIONS_DEFAULTS, ...(def.cloudSessions ?? {}) }\n const started = Date.now()\n const env = claudeBillingEnv(withCallerEnv(buildAgentEnv(def.workspace), task), def.billing)\n const result = await dispatchCloudSession({\n route,\n agentId: task.agentId,\n message: task.message,\n settings,\n store: this.cloudSessionStore(),\n traceId,\n launch: (opts) => launchCloudSession({ ...opts, env, timeoutMs: settings.launchTimeoutSeconds * 1000 }),\n log: (line) => this.log(line),\n trace: (s) => {\n try {\n getEventBus().emit(\"task:step\", {\n taskId: traceId, agentId: task.agentId, name: \"cloud_session\", action: s.action, status: s.status,\n outputSummary: s.status === \"ok\" ? s.summary : undefined, error: s.status === \"error\" ? s.summary : undefined,\n ms: Date.now() - started, at: new Date().toISOString(),\n })\n } catch { /* observability never breaks the run */ }\n },\n })\n if (!result) return null\n return { content: result.content, error: result.error, cloudSession: result.cloudSession, duration: Date.now() - started }\n }\n\n /** The answer for a run whose HTTP caller left before it got a slot\n * (#822). Logged so the dropped request is visible on the node. */\n private callerGone(task: AgentTask, during: string): AgentResponse {\n const reason = abortReason(task.callerSignal!).message\n this.log(`[${task.agentId}] caller gone during ${during} — not starting (${reason})`)\n return { content: \"\", error: `Caller gone before agent \"${task.agentId}\" was free — not started` }\n }\n\n /**\n * Execute a task on an agent. Respects maxConcurrent limit.\n *\n * Wraps `executeInternal` to record an intent-ledger resolution\n * after completion when `task.intentRef` is set. The resolution\n * write clears the dispatched-but-unresolved slot in\n * `intent_decisions` so Inv-ActiveTaskSafety lookups stop blocking\n * subsequent dispatches to the same (project, subject). Without\n * this, every dispatched decision sits in-flight forever and the\n * active-task check becomes vacuously over-aggressive.\n */\n async execute(task: AgentTask, onDelta?: StreamCallback, onThinking?: ThinkingCallback, onEvent?: (event: any) => void): Promise<AgentResponse> {\n const startedAt = Date.now()\n let response: AgentResponse\n try {\n response = await this.executeInternal(task, onDelta, onThinking, onEvent)\n } catch (e: any) {\n response = { content: \"\", error: e?.message ?? String(e) }\n }\n // Cut off by a daemon shutdown, at whatever step: report the restart,\n // not how the step read the kill.\n const interruptedBy = task.runningTaskId ? this.interruptedRuns.get(task.runningTaskId) : undefined\n if (interruptedBy) {\n this.interruptedRuns.delete(task.runningTaskId!)\n response = { ...response, content: \"\", error: interruptedBy, errorKind: \"interrupted\" }\n }\n // Ended by a stop signal (#857): report the stop, not how the step read the kill.\n const stoppedBy = !interruptedBy && task.runningTaskId ? this.stoppedRuns.get(task.runningTaskId) : undefined\n if (stoppedBy) {\n this.stoppedRuns.delete(task.runningTaskId!)\n response = { ...response, content: \"\", error: stoppedBy, errorKind: \"stopped\" }\n }\n // A run that threw or was cancelled before its own cleanup ran.\n if (task.runningTaskId) this.runReleases.get(task.runningTaskId)?.(response)\n // The preparation steps hit the pre-spawn deadline: the message was\n // never handed to a model. With the slot released, run it once more\n // from the start; a second stall is reported in plain words, not with\n // the internal error (#340).\n const preSpawn = !interruptedBy && !stoppedBy && response.error && /^timed out before spawn after/.test(response.error)\n if (preSpawn && !task.preSpawnRetry) {\n this.log(`[${task.agentId}] ${response.error}; retrying the run once`)\n return this.execute({ ...task, preSpawnRetry: 1, runningTaskId: undefined, onStart: undefined }, onDelta, onThinking, onEvent)\n }\n if (preSpawn) {\n const step = response.error!.match(/in step \"([^\"]+)\"/)?.[1] ?? \"start\"\n response = {\n ...response,\n error: `I couldn't get started on this: preparing the run stalled twice (step \"${step}\"). Please send it again.`,\n errorKind: \"interrupted\",\n }\n }\n if (task.intentRef) {\n try {\n // A queued answer is an accepted message, not a failure (#282).\n const status = !response.error\n ? \"completed\"\n : isQueued(response.error)\n ? \"queued\"\n : (/timed out|timeout/i.test(response.error) ? \"timed-out\" : \"failed\")\n getDefaultLedger().recordResolution({\n decisionEventId: task.intentRef.eventId,\n decisionDecidedBy: task.intentRef.decidedBy,\n resolvedAt: Date.now(),\n status,\n durationMs: Date.now() - startedAt,\n resultSummary: response.error\n ? response.error.slice(0, 200)\n : (response.content?.slice(0, 200) ?? null),\n })\n } catch (e: any) {\n // Non-fatal — the ledger may have a unique-constraint hit (the\n // resolution was already recorded by a prior call), or the\n // ledger may have failed entirely. Either way, agent dispatch\n // must succeed regardless.\n this.log(`[ledger] resolution write failed for ${task.intentRef.eventId}/${task.intentRef.decidedBy}: ${e?.message ?? e}`)\n }\n }\n return response\n }\n\n /**\n * Internal dispatcher — the real body. See `execute` for the public\n * wrapper that adds intent-ledger resolution recording.\n */\n private async executeInternal(task: AgentTask, onDelta?: StreamCallback, onThinking?: ThinkingCallback, callerOnEvent?: (event: any) => void): Promise<AgentResponse> {\n // People permissions (#379), before every way to the agent: an attached\n // session, a mesh peer, the local queue.\n const refusal = this.refusalFor(task.agentId, task.context)\n if (refusal) return { content: refusal, duration: 0 }\n\n // Attach mode: a live Claude Code session may have claimed this identity.\n // If so it gets first refusal — but only for a bounded window. When the\n // human doesn't pick the message up we fall through to the normal spawn\n // path below, and the offer is atomically expired so a late drain can't\n // answer it a second time. Attach is a preference, never a black hole.\n // A restricted-autonomy routine never goes to an attached session: that\n // terminal runs with its own (full) permissions, outside the per-task\n // guard hook, so enforcement would be silently skipped.\n const restricted = isRestricted(task.autonomy)\n // People permissions, per tool and per skill (#379): the run carries its\n // person's deny lists to the runtime, which enforces them with a per-run\n // guard hook. Recomputed on every run (a queued or resumed task too), so\n // a stale list on the task is never trusted. Same rule as autonomy: an\n // attached session runs outside the hook, so it is never offered one.\n const limits = personLimitsOf(this.config.people, task.context as any)\n if (limits) task.personLimits = limits\n else delete task.personLimits\n const offered = restricted || limits ? null : getAttachRegistry().offer({\n agentId: task.agentId,\n text: task.message,\n channel: task.context?.channel || \"api\",\n chatId: String(task.context?.chatId || task.context?.group || \"default\"),\n sender: String(task.context?.sender || \"unknown\"),\n })\n if (offered) {\n const outcome = await offered\n if (outcome.kind === \"answered\") {\n this.log(`[${task.agentId}] answered by attached session ${outcome.sessionId}`)\n return { content: outcome.text, viaAttachedSession: outcome.sessionId } as AgentResponse\n }\n this.log(`[${task.agentId}] attached session did not take it (${outcome.reason}) — spawning`)\n }\n\n const state = this.agents.get(task.agentId)\n if (!state) {\n // The autonomy hook is spawned by THIS daemon; a peer would receive a\n // bare message and run it at full power. Refuse instead.\n if (restricted) {\n return { content: \"\", error: `autonomy \"${task.autonomy}\" cannot be enforced on a mesh peer — agent \"${task.agentId}\" is not local`, autonomy: task.autonomy }\n }\n // This forward carries the message only, so the peer would not know\n // whose turn it is and would run it without the person's limits.\n if (limits) {\n return { content: \"\", error: `${limits.name}'s tool and skill limits cannot be enforced on a mesh peer — agent \"${task.agentId}\" is not local` }\n }\n // Mesh fallback: the agent isn't local but a healthy peer may host\n // it. Look it up in the mesh directory and forward via A2A sendTask.\n // Streaming callbacks are dropped — sendTask doesn't stream today.\n if (this.meshFallback) {\n const peerEntry = this.meshFallback.directory().find(\n (p) => p.healthy && p.skills.some((s) => s.id === task.agentId),\n )\n if (peerEntry) {\n this.log(`[${task.agentId}] not local — routing to mesh peer \"${peerEntry.peer}\"`)\n try {\n // Agent tasks can run for minutes. If the caller (workflow\n // agent node) passed a timeoutMinutes, honour it; otherwise\n // mesh.sendTask defaults to 30 minutes which beats Node's\n // 300s fetch default that was silently aborting long runs.\n const timeoutMs = typeof task.timeoutMinutes === \"number\" && task.timeoutMinutes > 0\n ? task.timeoutMinutes * 60 * 1000\n : undefined\n const content = await this.meshFallback.sendTask(peerEntry.peer, task.message, task.agentId, { timeoutMs })\n return { content, viaMesh: peerEntry.peer } as AgentResponse\n } catch (e: any) {\n this.log(`[${task.agentId}] mesh fallback to \"${peerEntry.peer}\" failed: ${e?.message ?? e}`)\n return { content: \"\", error: `mesh fallback failed: ${e?.message ?? e}` }\n }\n }\n }\n return { content: \"\", error: `Unknown agent: ${task.agentId}` }\n }\n\n // Build session key for queue management\n const { channel: qChannel, chatId: qChatId } = warmProcessChat(task.context)\n // Held from a voice wait ending until the chat is marked running.\n let voiceClaim: string | undefined\n\n if (state.activeTasks >= state.def.maxConcurrent) {\n // Synchronous API callers (mesh /task, /ask, direct curl) can't observe\n // a queued result — the queue's flush callback re-routes via channels,\n // not back to the awaiting HTTP caller. Returning `error: __queued__`\n // surfaces as HTTP 500 on the /task endpoint and triggers an \"Error\n // from peer\" comment on whatever channel the upstream is bridged to.\n // For these callers, BLOCK and wait for a slot (up to 25 min — slightly\n // under mesh.sendTask's 30 min default cap) instead of queueing.\n // Restricted routines wait too: a queued message is later re-routed\n // as plain channel text, which would drop its autonomy level. So does\n // the reminders poller: \"reminder\" has no adapter to re-route to, and\n // it would read __queued__ as a refusal and dispatch the reminder again.\n // Event wakes (\"events\") wait so the turn runs inside the dispatch's\n // withRoot: a queued one would be flushed from the earlier run and\n // publish under that run's rootId, or merge with it in collect mode.\n // The phone app (\"app\") streams its answer back over the open request\n // and has no adapter either, so it waits the same way. Agent-to-agent\n // runs (\"a2a\", \"mcp\") are awaited by the delegating agent or by a\n // delegation callback (#277), and have no adapter to flush to.\n // A guest mesh's turn (\"guest\") is answered over its open request\n // too, and has no adapter to flush a queued reply to.\n if (qChannel === \"api\" || qChannel === \"app\" || qChannel === \"a2a\" || qChannel === \"mcp\" || qChannel === \"reminder\" || qChannel === \"events\" || qChannel === \"guest\" || restricted) {\n const start = Date.now()\n const maxWaitMs = 25 * 60_000\n const pollIntervalMs = 500\n while (state.activeTasks >= state.def.maxConcurrent) {\n if (task.callerSignal?.aborted) return this.callerGone(task, \"slot wait\")\n if (Date.now() - start > maxWaitMs) {\n return { content: \"\", error: `Agent \"${task.agentId}\" busy — slot wait timed out after ${Math.round(maxWaitMs / 60000)}m` }\n }\n await new Promise((r) => setTimeout(r, pollIntervalMs))\n }\n // Slot freed — fall through to normal execution path below.\n } else if (qChannel === \"voice\") {\n // /ask waits on the answer to speak it, and \"voice\" has no adapter\n // to re-route a queued reply to: queued, it was answered into\n // nothing. Wait for the same voice chat instead, as the queue\n // would — other work on the agent still runs alongside.\n const start = Date.now()\n const maxWaitMs = 25 * 60_000\n const claim = `${task.agentId}\\u0000${qChatId}`\n while (this.messageQueue.isBusy(task.agentId, qChannel, qChatId) || this.voiceClaims.has(claim)) {\n if (Date.now() - start > maxWaitMs) {\n return { content: \"\", error: `Agent \"${task.agentId}\" busy — voice wait timed out after ${Math.round(maxWaitMs / 60000)}m` }\n }\n await new Promise((r) => setTimeout(r, 500))\n }\n voiceClaim = claim\n this.voiceClaims.add(claim)\n } else {\n // Channel callers (telegram/gitlab/whatsapp/...) already ack'd the\n // user's message, so queueing is the right behavior — the flush\n // callback will reply via the original channel when the slot frees.\n const mode = (state.def.queueMode as QueueMode) || \"collect\"\n const queued = this.messageQueue.enqueue(task.agentId, qChannel, qChatId, {\n text: task.message,\n sender: task.context?.sender || \"User\",\n timestamp: Date.now(),\n channel: qChannel,\n chatId: qChatId,\n originalContext: task.context as Record<string, unknown>,\n // A flushed message queued again keeps its first queue time, so\n // its eventual note reports the whole wait.\n queuedAt: task.queuedAt,\n })\n\n if (queued === \"drop\") {\n this.log(`[${task.agentId}] busy, message dropped (mode: drop)`)\n return { content: \"\", error: `Agent \"${task.agentId}\" is busy — message dropped` }\n }\n\n if (queued) {\n const pending = this.messageQueue.pendingCount(task.agentId, qChannel, qChatId)\n // Who is waiting, so the member page can count the messages ahead\n // of a teammate's (#443). The person is resolved here, not read\n // from the context: a queued message never reached the stamping\n // a run does below.\n getEventBus().emit(\"task:queued\", {\n agentId: task.agentId, channel: qChannel, chatId: qChatId, at: new Date().toISOString(),\n sender: senderOf(task.context), humanRoot: isHumanFacingTurn(task.context as any),\n person: personOfTurn(this.config.people, task.context as any)?.id ?? null,\n messagePreview: task.message.slice(0, 200), queuedAt: Date.now(),\n })\n this.log(`[${task.agentId}] busy, message queued (mode: ${queued}, pending: ${pending}) behind=${state.runningTasks.map((r) => r.id).join(\",\") || \"-\"} chat=${qChannel}:${qChatId} at=${new Date().toISOString()}`)\n return {\n content: \"\",\n error: queuedMarker(queued, pending),\n }\n }\n }\n }\n\n // Rate limit — WAIT for a slot instead of failing. Dropping messages looks\n // like the bot is broken; queueing for ~5 minutes gives chatty channels a\n // smooth experience and still bails if something's genuinely stuck.\n const rateResult = await this.rateLimiter.acquire(task.agentId, {\n maxWaitMs: 5 * 60_000,\n onWait: (reason, waitMs) => {\n this.log(`[${task.agentId}] ${reason} — queued, resumes in ~${Math.max(1, Math.round(waitMs / 1000))}s`)\n },\n })\n // From here to markRunning below nothing awaits, so the chat is\n // marked busy before another waiter can look at it.\n if (voiceClaim) this.voiceClaims.delete(voiceClaim)\n if (!rateResult.ok) {\n this.log(`[${task.agentId}] ${rateResult.reason}`)\n return { content: \"\", error: rateResult.reason }\n }\n // The rate-limit wait above can be minutes long too. The token it\n // took is spent even though this run never starts; that is accepted,\n // since the next caller only waits one token interval longer.\n if (task.callerSignal?.aborted) return this.callerGone(task, \"rate-limit wait\")\n\n // A flushed queued message that waited long enough for its subject to\n // change is told so (#282). Added here, when it really starts, and\n // not in the flush: a message that has to queue again then carries\n // its clean text and first queue time, and gets exactly one note.\n if (task.queuedAt !== undefined) {\n const staleNote = staleQueueNote(task.queuedAt)\n if (staleNote) task.message = `${staleNote}\\n${task.message}`\n task.queuedAt = undefined\n }\n\n state.activeTasks++\n state.totalTasks++\n state.lastActive = new Date()\n\n // Track the running task for live visibility (/agents endpoint surfaces this).\n const runningTask: RunningTask = {\n id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n messagePreview: (task.message || \"\").slice(0, 200),\n message: task.message || \"\",\n channel: task.context?.channel || \"api\",\n chatId: task.context?.chatId || task.context?.group,\n sender: task.context?.sender,\n startedAt: new Date(),\n ...(task.autonomy ? { autonomy: task.autonomy } : {}),\n ...(task.workflowRunId ? { workflow: { runId: task.workflowRunId } } : {}),\n }\n state.runningTasks.push(runningTask)\n task.runningTaskId = runningTask.id\n // Who started this chain (people, #384). Stamped on the context so a\n // delegation from this turn carries it in its root.\n const person = personOfTurn(this.config.people, task.context as any)\n if (task.context) {\n if (person) task.context.person = person.id\n else delete task.context.person\n }\n this.runningContexts.set(runningTask.id, task.context ?? {})\n if (task.onStart) { try { task.onStart(runningTask.id) } catch { /* caller bug must not break the run */ } }\n\n // AbortController for operator stop / replace. Stored under the running\n // task id so /api/tasks/:id/cancel can resolve and abort it.\n const abortController = new AbortController()\n this.taskAborts.set(runningTask.id, {\n agentId: task.agentId,\n channel: runningTask.channel,\n chatId: typeof runningTask.chatId === \"string\" ? runningTask.chatId : \"\",\n originalMessage: task.message || \"\",\n controller: abortController,\n sender: task.context?.sender,\n rootId: currentRoot()?.rootId,\n workflowRunId: task.workflowRunId ?? (typeof runningTask.chatId === \"string\" && runningTask.chatId.startsWith(\"workflow:\") ? runningTask.chatId.slice(9) || undefined : undefined),\n origin: task.origin ?? {\n kind: \"direct\",\n context: task.context as Record<string, unknown> | undefined,\n model: task.model,\n autonomy: task.autonomy,\n },\n })\n\n // Output capture for the dashboard streaming modal. We never *force*\n // streaming on a caller that didn't ask for it — that would change the\n // runtime mode (stream-json vs json) for every task in the system. So:\n // - Caller passed onDelta → wrap it to also fan-out to dashboard subscribers (live).\n // We also install onEvent so the dashboard sees tool calls, tool results,\n // and system events — i.e. everything you'd see in a real terminal.\n // - Caller did NOT pass onDelta → leave runtime in non-streaming mode and\n // post the final response.content as a single chunk after execution.\n const output: TaskOutput = { agentId: task.agentId, buffer: \"\", subscribers: new Set(), done: false }\n this.taskOutputs.set(runningTask.id, output)\n const pushToBuffer = (chunk: string) => {\n if (!chunk) return\n output.buffer += chunk\n if (output.buffer.length > TASK_OUTPUT_BUFFER_MAX) {\n output.buffer = output.buffer.slice(output.buffer.length - TASK_OUTPUT_BUFFER_MAX)\n }\n for (const sub of output.subscribers) {\n try { sub(chunk) } catch { /* subscriber crashed — ignore */ }\n }\n }\n // Allocate the per-execution trace id eagerly so both the streaming\n // parser (per-step capture below) and the bus emit a few lines down\n // can target the same task_traces row. Closures defined here reference\n // it by name; the variable is set before any callback can fire.\n const traceTaskId = newEventId()\n task.taskId = traceTaskId\n runningTask.traceId = traceTaskId\n const abortEntry = this.taskAborts.get(runningTask.id)\n if (abortEntry) abortEntry.traceId = traceTaskId\n\n // Per-task tool-use tally (improvement plan #3). Counts each\n // assistant tool_use block by tool name so we can compare against\n // agent.toolUseRequired after the response returns. The count is\n // populated regardless of toolUseRequired being set so traces\n // and dashboards always have it; the post-task check only fires\n // when the agent has the flag.\n const toolUsesByName = new Map<string, number>()\n\n // Persist each pipeline stage's wall time on the trace as a step named\n // \"pipeline\" with the stage as action. task_trace_steps.ms was NULL\n // for every row before this, so the pre-spawn cost could only be read\n // back from the daemon log.\n const recordPipelineStep = (stage: string, startedAt: number, status: \"ok\" | \"error\") => {\n try {\n getEventBus().emit(\"task:step\", {\n taskId: traceTaskId,\n agentId: task.agentId,\n name: \"pipeline\",\n action: stage,\n status,\n ms: Date.now() - startedAt,\n at: new Date().toISOString(),\n } as any)\n } catch { /* observability never breaks the run */ }\n }\n // Spawn-to-first-event: the one number that separates CLI startup from\n // model latency inside the \"agent\" stage.\n let agentStartedAt = 0\n let firstEventSeen = false\n const noteFirstEvent = () => {\n if (firstEventSeen) return\n firstEventSeen = true\n if (agentStartedAt) recordPipelineStep(\"first-event\", agentStartedAt, \"ok\")\n }\n\n let onEvent: ((event: any) => void) | undefined\n if (onDelta) {\n // Caller's onDelta still fires only for assistant text (unchanged).\n // Dashboard subscribers see the formatted stream-json firehose via onEvent\n // — this is what makes the modal feel like a live terminal.\n const formatter = makeStreamEventFormatter()\n onEvent = (event: any) => {\n noteFirstEvent()\n const formatted = formatter(event)\n if (formatted) pushToBuffer(formatted)\n // Per-step trace capture (improvement plan #2). Fire one\n // task:step event per tool_use / tool_result block. The\n // SQLite subscriber persists it under traceTaskId.\n emitTraceStepsFromStreamEvent(traceTaskId, task.agentId, event)\n tallyToolUses(toolUsesByName, event)\n // Claude Code's own view of the plan window; feeds the dispatch gate.\n recordRateLimitEvent(event, undefined, task.model || state.def.model)\n // Caller-supplied event subscriber (HTTP SSE callers, etc.).\n // Fire after internal capture so a subscriber crash never breaks\n // our own bookkeeping.\n if (callerOnEvent) { try { callerOnEvent(event) } catch { /* */ } }\n }\n } else {\n // Even without a dashboard subscriber, we want trace steps for\n // /traces/:id. Build a minimal onEvent that ONLY emits steps.\n onEvent = (event: any) => {\n noteFirstEvent()\n emitTraceStepsFromStreamEvent(traceTaskId, task.agentId, event)\n tallyToolUses(toolUsesByName, event)\n // Claude Code's own view of the plan window; feeds the dispatch gate.\n recordRateLimitEvent(event, undefined, task.model || state.def.model)\n if (callerOnEvent) { try { callerOnEvent(event) } catch { /* */ } }\n }\n }\n\n // Hard deadline for callers that set one (cron `timeout`, workflow agent\n // nodes, `agentx exec --timeout`). It fires the same abort as an operator\n // stop, so the runtime reaps its subprocess the same way.\n const deadlineMs = task.timeoutMinutes && task.timeoutMinutes > 0 ? task.timeoutMinutes * 60_000 : 0\n const deadline = deadlineMs\n ? setTimeout(() => abortController.abort(new Error(`timed out after ${Math.round(deadlineMs / 1000)}s`)), deadlineMs)\n : undefined\n deadline?.unref?.()\n // Every run, whatever started it, must reach its agent process (or hand\n // off to a workflow) within preSpawnTimeoutSec of taking the slot. A\n // pre-spawn step that never settles otherwise holds the slot forever.\n const preSpawnMs = (state.def.preSpawnTimeoutSec ?? PRE_SPAWN_TIMEOUT_SEC) * 1000\n let preSpawnDeadline: ReturnType<typeof setTimeout> | undefined = setTimeout(() => {\n abortController.abort(new Error(`timed out before spawn after ${Math.round(preSpawnMs / 1000)}s in step \"${runningTask.step ?? \"start\"}\"`))\n }, preSpawnMs)\n preSpawnDeadline.unref?.()\n const clearPreSpawnDeadline = () => {\n if (preSpawnDeadline) clearTimeout(preSpawnDeadline)\n preSpawnDeadline = undefined\n }\n // Suffix for run log lines: which run, which chat, when.\n const runTag = () => `task=${runningTask.id} chat=${qChannel}:${qChatId} at=${new Date().toISOString()}`\n // The step a deadline (pre-spawn or timeoutMinutes) ended the run in.\n let timedOutStep: string | undefined\n abortController.signal.addEventListener(\"abort\", () => {\n const reason = abortReason(abortController.signal).message\n if (/^timed out/.test(reason)) timedOutStep = runningTask.step ?? \"start\"\n this.log(`[${task.agentId}] task ${runningTask.id} aborted in step \"${runningTask.step ?? \"start\"}\" — ${reason} ${runTag()}`)\n }, { once: true })\n // Every await before and around the spawn goes through `step`, so a\n // cancel or deadline ends the run even when the awaited work never\n // settles. The step name is what /agents shows for a run that hangs.\n // `work` is a thunk so an aborted run never starts the next step: several\n // steps sit in best-effort try/catch blocks that swallow the rejection.\n const traceStep = (name: string) => {\n runningTask.step = name\n this.log(`[${task.agentId}] step ${name} ${runTag()}`)\n }\n const step = <T>(name: string, work: () => Promise<T>, graceMs = 0): Promise<T> => {\n if (abortController.signal.aborted) return Promise.reject(abortReason(abortController.signal))\n traceStep(name)\n // Spawning the agent (or handing off to a workflow) ends the pre-spawn phase.\n if (name === \"agent\" || name === \"workflow-auto-run\") {\n clearPreSpawnDeadline()\n agentStartedAt = Date.now()\n }\n const startedAt = Date.now()\n const bounded = withBudget(work(), STEP_BUDGET_MS[name] ?? 0, name)\n return untilAborted(bounded, abortController.signal, graceMs).then(\n (value) => { recordPipelineStep(name, startedAt, \"ok\"); return value },\n (err) => {\n recordPipelineStep(name, startedAt, \"error\")\n if (err instanceof StepBudgetExceeded) {\n this.log(`[${task.agentId}] ${err.message}; continuing without it ${runTag()}`)\n }\n throw err\n },\n )\n }\n /** A step the run can do without: past its budget, `fallback` stands\n * in and the run goes on. Other failures still propagate. */\n const budgeted = async <T>(name: string, work: () => Promise<T>, fallback: () => T, graceMs = 0): Promise<T> => {\n try {\n return await step(name, work, graceMs)\n } catch (err) {\n if (err instanceof StepBudgetExceeded) return fallback()\n throw err\n }\n }\n\n // Give back everything the run holds. Idempotent: the `finally` below\n // calls it, and execute() calls it for a run that threw before reaching\n // that `try`.\n // Set once the run emitted task:started / its own task:completed.\n // closeTrace emits the completion for a run that ended before reaching\n // it: a pre-spawn abort, a cancel or stop signal, an unexpected error.\n // Otherwise its trace stayed \"in-flight\" and the next boot resumed it\n // as cut-off work (#340, #857). A shutdown interruption is emitted\n // flagged `interrupted`, which keeps the trace open on purpose.\n let traceOpened = false\n let completedEmitted = false\n const closeTrace = (finalResponse: AgentResponse | undefined) => {\n if (!traceOpened || completedEmitted) return\n completedEmitted = true\n try {\n getEventBus().emit(\"task:completed\", {\n taskId: traceTaskId,\n agentId: task.agentId,\n channel: qChannel,\n chatId: qChatId,\n durationMs: Date.now() - runningTask.startedAt.getTime(),\n error: finalResponse?.error || (abortController.signal.aborted ? abortReason(abortController.signal).message : \"run ended before completion\"),\n errorKind: finalResponse?.errorKind,\n interrupted: this.interruptedRuns.has(runningTask.id) ? true : undefined,\n stopped: !this.interruptedRuns.has(runningTask.id) && (this.stoppedRuns.has(runningTask.id) || finalResponse?.errorKind === \"stopped\") ? true : undefined,\n at: new Date().toISOString(),\n } as any)\n } catch { /* observability never breaks the run */ }\n }\n\n let released = false\n const releaseRun = (finalResponse: AgentResponse | undefined) => {\n if (released) return\n released = true\n closeTrace(finalResponse)\n this.runReleases.delete(runningTask.id)\n if (deadline) clearTimeout(deadline)\n clearPreSpawnDeadline()\n state.activeTasks--\n // Remove this run from the running-tasks list.\n const idx = state.runningTasks.findIndex((r) => r.id === runningTask.id)\n if (idx !== -1) state.runningTasks.splice(idx, 1)\n this.runningContexts.delete(runningTask.id)\n // Drop the abort entry — the controller is unreachable after this point\n // and a future cancel for the same id should 404, not silently no-op.\n this.taskAborts.delete(runningTask.id)\n const waiters = this.runEndWaiters.get(runningTask.id)\n this.runEndWaiters.delete(runningTask.id)\n for (const w of waiters ?? []) { try { w() } catch { /* */ } }\n\n // Notify any open dashboard streams that this task has finished, then\n // schedule the buffer for cleanup so memory doesn't grow unbounded.\n output.done = true\n output.endedAt = new Date()\n for (const sub of output.subscribers) {\n try { sub(\"\\n[task finished]\\n\") } catch { /* ignore */ }\n }\n output.subscribers.clear()\n setTimeout(() => this.taskOutputs.delete(runningTask.id), TASK_OUTPUT_TTL_MS).unref?.()\n\n // Persist a TaskRecord for the dashboard's \"Recent activities\" panel.\n // Best-effort — disk failures must not affect task semantics.\n try {\n const endedAt = output.endedAt\n const record: TaskRecord = {\n id: runningTask.id,\n agentId: task.agentId,\n channel: runningTask.channel,\n chatId: runningTask.chatId,\n sender: runningTask.sender,\n message: task.message || \"\",\n startedAt: runningTask.startedAt.toISOString(),\n endedAt: endedAt.toISOString(),\n durationMs: endedAt.getTime() - runningTask.startedAt.getTime(),\n ok: !finalResponse?.error,\n error: finalResponse?.error,\n ...(this.stoppedRuns.has(runningTask.id) || finalResponse?.errorKind === \"stopped\"\n ? { status: \"stopped\" as const }\n : timedOutStep ? { status: \"timeout\" as const, step: timedOutStep } : {}),\n responseText: finalResponse?.content || \"\",\n transcript: output.buffer,\n }\n this.persistTaskRecord(record)\n // Stash a one-line summary so the dashboard card can show \"what the\n // agent did last\" without reading from disk on every snapshot.\n const rawSummary = (record.responseText || record.error || \"\").trim()\n if (rawSummary) {\n const firstLine = rawSummary.split(/\\r?\\n/)[0].slice(0, 140)\n this.lastSummaries.set(task.agentId, { text: firstLine, at: endedAt, ok: record.ok })\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] task history persist failed: ${e?.message}`)\n }\n\n // Flush queued messages that arrived while this run was in progress\n this.messageQueue.markDone(task.agentId, qChannel, qChatId)\n .then((queued) => {\n if (queued.length === 0) return\n this.log(`[${task.agentId}] flushing ${queued.length} queued message(s) after task=${runningTask.id} chat=${qChannel}:${qChatId} at=${new Date().toISOString()}`)\n // Re-execute each queued message as a new task. The original\n // inbound message (the one that triggered this run) was posted\n // back to its channel by the router after `registry.execute`\n // returned. Queued messages, however, are re-dispatched here\n // directly — bypassing the router — so we must explicitly post\n // the response back to the channel ourselves, or the user never\n // sees the reply. (Real symptom: operator hits Update on a\n // running task, the follow-up runs and produces a reply, but\n // nothing arrives in Telegram.)\n const flushedAt = Date.now()\n // The line is empty from here: what waited now runs (#443).\n getEventBus().emit(\"task:queue-flushed\", {\n agentId: task.agentId, channel: qChannel, chatId: qChatId, flushedAt, count: queued.length, at: new Date().toISOString(),\n })\n const ended = () => getEventBus().emit(\"task:queue-ended\", {\n agentId: task.agentId, channel: qChannel, chatId: qChatId, flushedAt, at: new Date().toISOString(),\n })\n for (const qm of queued) {\n const ctx = (qm.originalContext as AgentTask[\"context\"]) || {\n channel: qm.channel,\n sender: qm.sender,\n chatId: qm.chatId,\n }\n // queuedAt makes the run add the stale-state note when it\n // starts (#282). Only flushed queued turns carry it, so a\n // normal turn pays nothing for it.\n this.execute({\n message: qm.text,\n agentId: task.agentId,\n context: ctx,\n queuedAt: qm.queuedAt ?? qm.timestamp,\n })\n .then((resp) => {\n // Queued again behind another turn: it still waits.\n if (!isQueued(resp.error)) ended()\n return this.postQueuedResponseToChannel(task.agentId, ctx, resp)\n }, (e) => { ended(); throw e })\n .catch((e) => {\n this.log(`[${task.agentId}] queued message failed: ${e.message}`)\n })\n }\n })\n .catch((e) => {\n getEventBus().emit(\"task:queue-ended\", {\n agentId: task.agentId, channel: qChannel, chatId: qChatId, flushedAt: Date.now(), at: new Date().toISOString(),\n })\n this.log(`[${task.agentId}] queue flush failed: ${e.message}`)\n })\n }\n this.runReleases.set(runningTask.id, releaseRun)\n\n // Mark session as running in the message queue\n this.messageQueue.markRunning(task.agentId, qChannel, qChatId)\n\n this.log(`[${task.agentId}] executing task (${state.activeTasks}/${state.def.maxConcurrent}) ${runTag()}`)\n\n // Build conversation history for session continuity\n const channel = task.context?.channel || \"api\"\n const { evaluateRequest, selectRequestContext } = await import(\"./request-planner\")\n // Asked now, read later: nothing before the context planner needs the\n // gate's answer, so it runs alongside history seeding, skill loading,\n // the continuity seat and compaction instead of ahead of them (#455).\n // The settle handlers also keep a run that returns before the await\n // from leaving a rejection unhandled; the await below still sees it.\n const requestGatePending = budgeted(\n \"request-gate\",\n () => evaluateRequest(task.message, task.agentId, channel),\n (): Awaited<ReturnType<typeof evaluateRequest>> => ({ active: false, preprocess: false }),\n )\n let requestGateSettled = false\n requestGatePending.then(() => { requestGateSettled = true }, () => { requestGateSettled = true })\n const chatId = qChatId\n const senderName = task.context?.sender || \"User\"\n const isCodexCli = state.def.tier === \"codex-cli\"\n\n // Session profile (#615): what this channel's sessions get up front.\n // `lean` (github, a2a, workflow, cron by default) hands the landscape,\n // chat history and cross-chat context over on demand through the\n // agentx MCP tools and starts Claude Code without user-level MCP\n // servers, skills and settings. Chat channels stay `full`.\n const sessionProfile = resolveSessionProfile(this.config.session, channel, state.def.tier)\n const lean = leanConfig(this.config.session)\n const leanOnDemand = sessionProfile === \"lean\" && lean.contextOnDemand\n\n // Improvement plan #8 — caller-driven session reset. Honour it before\n // seeding or recording the current inbound message; otherwise the fresh\n // reset can erase the very message that started the task.\n if (task.freshSession) {\n this.sessions.clearSession(task.agentId, channel, chatId)\n this.log(`[${task.agentId}] freshSession=true → cleared session history for ${channel}:${chatId}`)\n getEventBus().emit(\"session:rotated\", {\n agentId: task.agentId, channel, chatId,\n reason: \"stale\",\n at: new Date().toISOString(),\n })\n }\n\n // How long the session sat idle before this turn. Read it now: seeding,\n // recording the user message and compaction below all bump updatedAt,\n // after which the session always looks idle for ~0 ms. The stale rule,\n // the resume gate and the router all need the pre-turn value.\n const priorIdleMs = this.sessions.sessionIdleMs(task.agentId, channel, chatId)\n\n // Mirror the live channel before recording the new user message — on a\n // cold-create (fresh chatId, new day after rotation), this calls the\n // adapter's seedHistory and back-fills recent messages so the agent\n // doesn't start blind. No-op for warm sessions, non-channel callers\n // (cron/api/a2a), or channels without a seedHistory implementation.\n if (!task.freshSession) {\n await budgeted(\"seed-history\", () => this.sessions.seedIfEmpty(task.agentId, channel, chatId, task.seedHistory), () => undefined)\n }\n\n // Shadow seats read the agent's last reply before this message joins it.\n const previousReply = [...this.sessions.getSession(task.agentId, channel, chatId).messages]\n .reverse().find(m => m.role === \"agent\")?.content\n onUserMessage({ agent: task.agentId, channel, chatId, message: task.message, previousReply, taskId: traceTaskId })\n\n // Record user message in session\n this.sessions.addUserMessage(task.agentId, channel, chatId, senderName, task.message)\n\n const taskStartedAt = Date.now()\n // traceTaskId allocated above (alongside the streaming onEvent\n // callback). Including it in task:started lets the SQLite\n // subscriber open the trace row under the same id the per-step\n // emitter uses.\n getEventBus().emit(\"task:started\", {\n agentId: task.agentId,\n channel,\n chatId,\n messagePreview: (task.message || \"\").slice(0, 200),\n fullMessage: task.message || \"\",\n requestText: task.requestText,\n at: new Date(taskStartedAt).toISOString(),\n taskId: traceTaskId,\n resumeOrigin: serializeOrigin(task.origin ?? {\n kind: \"direct\",\n context: task.context as Record<string, unknown> | undefined,\n model: task.model,\n autonomy: task.autonomy,\n }),\n resumeAttempt: task.resumeAttempt ?? 0,\n resumedFrom: task.resumedFrom,\n sender: senderOf(task.context),\n ...(person ? { person } : {}),\n humanRoot: isHumanFacingTurn(task.context as any),\n pickup: isPickup(task.context),\n operator: isOperatorTurn(task.context),\n dueAt: dueAtOf(task.context as Record<string, unknown> | undefined),\n })\n traceOpened = true\n\n // Classify the message through the intent graph when enabled. Skip for\n // a2a traffic — the classifier itself dispatches through /task, and\n // re-classifying its own prompts would recurse forever. Any classifier\n // failure (bad LLM output, schema rejection, network error) must never\n // propagate — the main task still has to run.\n //\n // The classification runs alongside the rest of the pipeline instead\n // of ahead of it. Measured at ~19s p50 per task (a fresh CLI spawn on\n // every cache miss) while nothing before the spawn needs the answer:\n // workflow matching and the context block take whatever has resolved,\n // and the wiki entry stamp at the end waits for it.\n let intent: ClassifyResult | undefined\n let intentPending: Promise<void> | undefined\n const classifier = this.classifier\n if (classifier && channel !== \"a2a\" && !isCodexCli) {\n traceStep(\"classify\")\n const classifyStartedAt = Date.now()\n intentPending = classifier\n .classify({\n text: task.message,\n channel,\n sender: task.context?.sender,\n chatId,\n agentId: task.agentId,\n })\n .then(\n (r) => {\n intent = r || undefined\n if (intent?.path?.length) runningTask.intentPath = intent.path.slice()\n recordPipelineStep(\"classify\", classifyStartedAt, \"ok\")\n },\n (e: any) => {\n this.log(`[classifier] classify failed for ${task.agentId}: ${e?.message || e}`)\n recordPipelineStep(\"classify\", classifyStartedAt, \"error\")\n },\n )\n }\n /** Wait at most `maxMs` for the classification. Cache hits settle in\n * a tick; an LLM miss keeps running and is picked up later. */\n const awaitIntent = async (maxMs: number): Promise<void> => {\n if (!intentPending) return\n let timer: ReturnType<typeof setTimeout> | undefined\n await Promise.race([\n intentPending,\n new Promise<void>((r) => { timer = setTimeout(r, maxMs); timer.unref?.() }),\n ])\n if (timer) clearTimeout(timer)\n }\n await awaitIntent(INTENT_WAIT_BEFORE_MATCH_MS)\n\n const wfMatching = this.config.workflows?.matching\n // Decision-only here: compute whether auto-run should fire. We don't fire\n // the workflow yet — that happens inside the outer try/finally so\n // runningTask + activeTasks bookkeeping always cleans up.\n let pendingAutoRun: { workflowId: string; confidence: number; follow?: boolean } | undefined\n // Follow-up (#788): a saved workflow that fits this request is named to\n // the agent, which starts it with agentx_workflow instead of running\n // the steps by hand.\n let workflowHint: string | undefined\n const followUp = this.config.workflows?.followUp\n if (this.config.workflows?.enabled && followUp?.enabled && (followUp.agents[task.agentId] ?? true) && !restricted && isHumanFacingTurn(task.context as any)) {\n try {\n const store = new WorkflowStore({ baseDir: resolve(process.cwd(), this.config.workflows.dir) })\n const match = matchWorkflow({ agentId: task.agentId, channel, message: task.message, intentPath: intent?.path }, store.list())\n if (match && match.confidence >= (wfMatching?.suggestThreshold ?? 0.65)) workflowHint = workflowHintText(match.workflow)\n } catch (e: any) {\n this.log(`[${task.agentId}] workflow hint failed (non-fatal): ${e?.message || e}`)\n }\n }\n // Restricted routines never auto-run a workflow: its agent steps would\n // run at their own autonomy, not this task's.\n if (this.config.workflows?.enabled && wfMatching?.enabled && !restricted) {\n try {\n const store = new WorkflowStore({ baseDir: resolve(process.cwd(), this.config.workflows.dir) })\n const match = matchWorkflow({\n agentId: task.agentId,\n channel,\n message: task.message,\n intentPath: intent?.path,\n }, store.list())\n if (match && match.confidence >= wfMatching.suggestThreshold) {\n this.log(\n `[${task.agentId}] workflow match ${match.workflow.id} confidence=${match.confidence.toFixed(2)} ` +\n `mode=${wfMatching.mode} reasons=${match.reasons.join(\",\") || \"n/a\"}`,\n )\n if (wfMatching.mode === \"auto\" && match.confidence >= wfMatching.autoRunThreshold) {\n if (this.workflowAutoRunner) {\n pendingAutoRun = { workflowId: match.workflow.id, confidence: match.confidence }\n } else {\n this.log(`[${task.agentId}] workflow auto-run requested but no runner wired; falling back to agent`)\n }\n }\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] workflow matcher failed (non-fatal): ${e?.message || e}`)\n }\n }\n // workflows.required (#858): every task runs inside a workflow run. A\n // saved workflow that fits a person's request is started and followed\n // (matching need not be on); anything else is wrapped in a run of its\n // own, on a plan the agent writes or the one-step linear template. A\n // workflow step's own turn is never wrapped again.\n const wrapRunStore = requiredFor(this.config.workflows, task.agentId) && shouldWrap(task) ? this.workflowRuns : undefined\n if (wrapRunStore && !pendingAutoRun && !restricted && this.workflowAutoRunner && isHumanFacingTurn(task.context as any)) {\n try {\n const store = new WorkflowStore({ baseDir: resolve(process.cwd(), this.config.workflows.dir) })\n const match = matchWorkflow({ agentId: task.agentId, channel, message: task.message, intentPath: intent?.path }, store.list())\n const threshold = wfMatching?.autoRunThreshold ?? 0.85\n if (match && match.confidence >= threshold) {\n pendingAutoRun = { workflowId: match.workflow.id, confidence: match.confidence, follow: true }\n this.log(`[${task.agentId}] workflows.required: saved workflow ${match.workflow.id} fits (confidence=${match.confidence.toFixed(2)})`)\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] workflows.required matcher failed (non-fatal): ${e?.message || e}`)\n }\n }\n // Chosen now so the agent is told its run before the turn starts; the\n // run itself is created inside the try below, which always closes it.\n const wrapRunId = wrapRunStore ? randomUUID() : undefined\n // Its own context layer, so mined procedures and the follow-up hint\n // never crowd it out of the procedures budget.\n const workflowRunContext = wrapRunId ? wrapHintText(wrapRunId) : undefined\n\n // Wiki context — Phase 3 Farzapedia alignment: instead of preloading BM25\n // hits (the old shallow-RAG path). The catalog itself is injected on\n // fresh sessions (see `wikiContext` below, once rotation has settled);\n // bodies are still fetched on demand by the agent through\n // `agentx wiki query`, so there is no retrieval cost on messages that\n // do not need it.\n const agentWiki = this.wikiHub.getAgentWiki(task.agentId)\n\n // Load persistent agent memory (cross-session facts)\n const relevantMemories = this.memoryStore.findRelevant(task.message, task.agentId, isCodexCli ? 3 : 8)\n // `let`, not `const`, because the planner strategy (below) may overwrite\n // this with its own curated memory bundle when enabled.\n let memoryContext = this.memoryStore.buildContext(relevantMemories)\n // Candidates only; what actually reached the prompt is decided after\n // the context is assembled (see injectedContextOf).\n let memoryFacts = this.memoryStore.contextFacts(relevantMemories)\n\n // Load behavioral patterns (self-improving loop)\n const relevantPatterns = this.patternStore.findRelevant(task.message, task.agentId, 5)\n const patternContext = this.patternStore.buildContext(relevantPatterns)\n\n // Auto-inject skills matched to current message\n let skillInjection = \"\"\n if (!isCodexCli) {\n try {\n const { loadLocalSkills, getAutoInjectSkills } = await import(\"@/agent/skills/loader\")\n const loaded = await step(\"skills\", () => loadLocalSkills(state.def.workspace))\n // A skill the person may not use is not offered to them either\n // (#379); the guard hook still blocks it if the agent reaches for it.\n const denied = task.personLimits?.skills ?? []\n const skills = denied.length ? loaded.filter((s) => !denied.some((p) => nameMatches(p, s.frontmatter.name))) : loaded\n skillInjection = getAutoInjectSkills(skills, task.message)\n } catch {\n // Skill loading is optional\n }\n }\n\n // Decide whether to resume or start fresh\n let resumeSessionId = state.def.tier === \"claude-code\"\n ? this.sessions.getClaudeSessionId(task.agentId, channel, chatId)\n : state.def.tier === \"codex-cli\"\n ? this.sessions.getCodexSessionId(task.agentId, channel, chatId)\n : state.def.tier === \"opencode\"\n ? this.sessions.getOpenCodeSessionId(task.agentId, channel, chatId)\n : undefined\n\n // If session is stale (idle > staleMinutes), start fresh with full context rebuild\n if (resumeSessionId && this.sessions.isSessionStale(task.agentId, channel, chatId, priorIdleMs)) {\n this.log(`[${task.agentId}] session stale for ${channel}:${chatId}, starting fresh`)\n if (state.def.tier === \"claude-code\") {\n void this.captureRotationMemoAsync(task.agentId, state.def, resumeSessionId, channel, chatId, \"stale\")\n }\n this.sessions.clearClaudeSessionId(task.agentId, channel, chatId)\n getEventBus().emit(\"session:rotated\", {\n agentId: task.agentId, channel, chatId,\n reason: \"stale\",\n at: new Date().toISOString(),\n })\n resumeSessionId = undefined\n }\n\n // If the prior turn's END-OF-TURN CONTEXT (per-request input, not the\n // cumulative-with-cache-reads turn total) is near the 200K tier-2\n // boundary, rotate before paying the 1.5× multiplier again. Claude CLI\n // --resume replays every past tool result, so one genuinely bloated\n // context keeps billing tier-2 indefinitely until we drop the session.\n if (resumeSessionId && this.sessions.shouldRotateByTierTwo(task.agentId, channel, chatId)) {\n const lastTokens = this.sessions.getLastTurnContextTokens(task.agentId, channel, chatId)\n this.log(`[${task.agentId}] tier-2 rotation for ${channel}:${chatId} (last turn context: ${lastTokens} tokens ≥ ${this.sessions.getTierTwoThresholdTokens(channel)})`)\n if (state.def.tier === \"claude-code\") {\n void this.captureRotationMemoAsync(task.agentId, state.def, resumeSessionId, channel, chatId, \"tier-2\")\n }\n this.sessions.clearClaudeSessionId(task.agentId, channel, chatId)\n getEventBus().emit(\"session:rotated\", {\n agentId: task.agentId, channel, chatId,\n reason: \"tier-2\",\n lastTurnInputTokens: lastTokens,\n at: new Date().toISOString(),\n })\n resumeSessionId = undefined\n }\n\n // If session has accumulated too many turns, rotate even before it hits\n // tier-2. Claude CLI replays grow linearly with turn count — capping\n // here keeps the per-turn cache-read tax bounded. Compacted summary +\n // recent-messages history seed the next session so nothing is lost.\n if (resumeSessionId && this.sessions.shouldRotateByTurns(task.agentId, channel, chatId)) {\n const turns = this.sessions.getTurnCount(task.agentId, channel, chatId)\n this.log(`[${task.agentId}] max-turns rotation for ${channel}:${chatId} (${turns} turns ≥ ${this.sessions.getMaxTurnsPerSession()})`)\n if (state.def.tier === \"claude-code\") {\n void this.captureRotationMemoAsync(task.agentId, state.def, resumeSessionId, channel, chatId, \"max-turns\")\n }\n this.sessions.clearClaudeSessionId(task.agentId, channel, chatId)\n getEventBus().emit(\"session:rotated\", {\n agentId: task.agentId, channel, chatId,\n reason: \"max-turns\",\n at: new Date().toISOString(),\n })\n resumeSessionId = undefined\n }\n\n // Resume or start fresh (#621, step 3): a price comparison in code, not\n // a model. Like the continuity seat below it can only rotate earlier\n // than the rules above, never keep a session they dropped.\n if (resumeSessionId && state.def.tier === \"claude-code\") {\n // Optional chaining: hand-built configs (tests, older callers) may\n // not carry the block the schema defaults in.\n const gate = this.config.session?.resumeGate\n if (gate && gate.mode !== \"off\") {\n const r = decideResume({\n // Raw reading: the cumulative fallback would make C the turn's\n // summed input, N ≈ 1, and the cold-cache cost about twice it.\n contextTokens: this.sessions.getRawLastTurnContextTokens(task.agentId, channel, chatId),\n turnInputTokens: this.sessions.getLastTurnInputTokens(task.agentId, channel, chatId),\n idleMs: priorIdleMs ?? 0,\n }, gate)\n if (r.rotate && gate.mode === \"active\") {\n this.log(`[${task.agentId}] cost rotation for ${channel}:${chatId}: ${describeResume(r)}`)\n void this.captureRotationMemoAsync(task.agentId, state.def, resumeSessionId, channel, chatId, \"cost\")\n // Read before clearing: clearClaudeSessionId resets the counters.\n const lastTokens = this.sessions.getLastTurnInputTokens(task.agentId, channel, chatId)\n this.sessions.clearClaudeSessionId(task.agentId, channel, chatId)\n getEventBus().emit(\"session:rotated\", {\n agentId: task.agentId, channel, chatId,\n reason: \"cost\",\n lastTurnInputTokens: lastTokens,\n at: new Date().toISOString(),\n })\n resumeSessionId = undefined\n } else if (gate.mode === \"shadow\") {\n this.log(`[${task.agentId}] resume gate (shadow) for ${channel}:${chatId}: would ${describeResume(r)}`)\n }\n }\n }\n\n // --- Session-continuity seat -----------------------------------------\n //\n // Runs only when the mechanical triggers did NOT fire, and can only\n // rotate EARLY. The thresholds above stay authoritative: this may save\n // a transcript replay, never keep alive one the safety rules wanted\n // dead. See src/decisions/seats/session-continuity.ts for why the\n // asymmetry matters — the cost of resuming is exact, the cost of\n // rotating too early is amnesia, and optimising only the measurable\n // side is how the original rotation incident happened.\n if (resumeSessionId && state.def.tier === \"claude-code\") {\n const sessionId = resumeSessionId\n const rotatedEarly = await budgeted(\"rotate\", () => this.maybeRotateForContinuity(\n task, state, channel, chatId, sessionId,\n ), () => false)\n if (rotatedEarly) resumeSessionId = undefined\n }\n\n // Compact session if history is getting too long (summarize older messages).\n //\n // Compaction NO LONGER drops resumeSessionId — Claude's own session has\n // its own history that --resume replays, independent of our stored copy.\n // The summary gets used at the next legitimate rotation (tier-2,\n // max-turns, stale) when we genuinely need a fresh Claude session and\n // have to seed it from the stored messages. Until then, the hot session\n // stays cache-friendly. See sessions.ts compactIfNeeded for the longer\n // explanation.\n try {\n const compactResult = await step(\"compact\", () => this.sessions.compactIfNeeded(\n task.agentId, channel, chatId, this.memoryStore,\n ))\n if (compactResult.compacted) {\n const quality = compactResult.qualityScore ?? \"?\"\n const lost = compactResult.lostEntities?.length ?? 0\n this.log(`[${task.agentId}] session compacted for ${channel}:${chatId} (quality: ${quality}%, ${lost} entities lost)`)\n if (compactResult.lostEntities?.length) {\n this.log(`[${task.agentId}] lost entities: ${compactResult.lostEntities.slice(0, 5).join(\", \")}`)\n }\n if (compactResult.drift) {\n const d = compactResult.drift\n this.log(`[${task.agentId}] DRIFT DETECTED: score=${d.overallScore} (lexicon=${d.lexiconDecay}, tools=${d.toolShift}, semantic=${d.semanticDrift})`)\n if (d.lostWords.length) this.log(`[${task.agentId}] lost domain words: ${d.lostWords.slice(0, 5).join(\", \")}`)\n }\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] compaction failed (non-fatal): ${e.message}`)\n }\n\n // Context Surgery — Fix 1: cap session history for coding-channel\n // contexts. Long github/gitlab issue threads anchor the agent on\n // accumulated assumptions (the diagnosis from initech-v2 review). Tighter\n // cap forces code-first investigation; thread remains accessible via\n // `gh`/`glab` if the agent explicitly fetches it. Gated behind\n // AGENTX_CONTEXT_SURGERY=1 for one-week soak before unconditional rollout.\n const surgeryEnabled = process.env.AGENTX_CONTEXT_SURGERY === \"1\"\n const historyCap = isCodexCli\n ? { maxMessages: 8, maxChars: 2400 }\n : (surgeryEnabled && isCodingChannelContext(channel, state.def.tier))\n ? { maxMessages: 6, maxChars: 1800 }\n : undefined\n\n // Fresh sessions only — `--resume` replays the transcript, so the\n // catalog injected on turn one is still there.\n const wikiContext = !resumeSessionId && !isCodexCli\n ? buildWikiContext(agentWiki, task.agentId, { notesInbox: wikiNotesHint(this.config) })\n : undefined\n\n // Lean sessions fetch earlier turns with agentx_recent instead.\n const sessionHistory = !resumeSessionId && !leanOnDemand\n ? this.sessions.buildHistoryContext(\n task.agentId,\n channel,\n chatId,\n historyCap,\n )\n : undefined\n\n // Continuity memo from the previous (rotated) session — deterministic\n // handover so a fresh session opens knowing the ongoing task instead\n // of amnesiac. Fresh-session-only, same gate as the history rebuild:\n // a resumed session already carries this context natively, and\n // re-injecting per-turn is exactly the bloat that used to force\n // premature tier-2 rotation.\n const rotationMemo = !resumeSessionId\n ? this.sessions.getRotationMemo(task.agentId, channel, chatId) ?? undefined\n : undefined\n\n // Matched procedures — user-perspective SOPs mined from recurring\n // activity. Fresh-session-only, same gate and same reasoning as\n // rotationMemo/references above: a warm session already saw the block,\n // and per-turn re-injection is the bloat that forces tier-2 rotation.\n // Warm sessions can pull one on demand via `agentx procedure match`.\n let procedureContext: string | undefined\n let procedureCandidates: Array<{ id: string; title: string }> = []\n if (this.config.procedures?.injection?.enabled && !resumeSessionId && !isCodexCli) {\n try {\n const procStore = new ProcedureStore({\n baseDir: resolve(process.cwd(), this.config.procedures.dir),\n })\n const procMatches = matchProcedures(task.message, procStore.list(), {\n limit: this.config.procedures.injection.maxProcedures,\n minScore: this.config.procedures.injection.minScore,\n })\n if (procMatches.length > 0) {\n procedureContext = renderProcedureContext(procMatches)\n procedureCandidates = procMatches.map((m) => ({ id: m.procedure.meta.id, title: m.procedure.meta.title }))\n for (const m of procMatches) {\n this.log(`[${task.agentId}] procedure match ${m.procedure.meta.id} score=${m.score.toFixed(2)} (${m.reasons.join(\",\")})`)\n }\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] procedure matcher failed (non-fatal): ${e?.message || e}`)\n }\n }\n\n // Context-rebuild diagnostic. Fires under `--debug context` (or `all`).\n // The amnesia-vs-misreasoning question — \"did the agent see X in its\n // prompt?\" — was unanswerable from session JSONs alone (the rendered\n // prompt prefix is never persisted). This log captures it per-turn:\n // - resume vs fresh (after rotation, this is \"fresh\")\n // - sessionHistory length when rendered (chars + line count)\n // - session.messages snapshot: count, first ts, last ts, first/last\n // name+content preview so we can tell what's actually in the file.\n // Off by default — zero overhead unless the operator opted in.\n if (debug && (debug as any).cat) {\n try {\n const sess = this.sessions.getSession(task.agentId, channel, chatId)\n const msgs = sess.messages\n const first = msgs[0]\n const last = msgs[msgs.length - 1]\n const fingerprint = sessionHistory === undefined\n ? `resume=${resumeSessionId ? resumeSessionId.slice(0, 8) : \"?\"} (no rebuild)`\n : `fresh history=${sessionHistory.length}c/${sessionHistory.split(\"\\n\").length}L`\n const summary = `[${task.agentId}] ${channel}:${chatId} ${fingerprint} | session.messages=${msgs.length}` +\n (msgs.length > 0\n ? ` first=${first.timestamp.slice(11, 19)}(${first.role}:${(first.content || \"\").slice(0, 40).replace(/\\n/g, \" \")}) last=${last.timestamp.slice(11, 19)}(${last.role}:${(last.content || \"\").slice(0, 40).replace(/\\n/g, \" \")})`\n : \"\")\n debug.cat(\"context\", summary)\n } catch (e: any) {\n // Diagnostic logging must never throw into the hot path.\n debug.cat(\"context\", `[${task.agentId}] context-diag error: ${e?.message ?? e}`)\n }\n }\n\n // Verified deterministic references — opt-in per agent via\n // `contextReferences: true`. Only injected when starting a FRESH Claude\n // session: once a session is in progress, the agent already has any\n // facts it learned in earlier turns, and re-rendering them on every\n // resumed turn just bloats the per-turn user-message context. That\n // bloat compounded with --resume's full-history replay was hitting the\n // 180k tier-2 threshold in 5–10 turns and forcing constant rotation,\n // which discarded the conversation Claude session — causing exactly\n // the \"I don't have prior context\" symptom users observed.\n // The resolver itself runs cheaply in-memory; we just gate the rendered\n // block on `!resumeSessionId`.\n let referencesBlock: string | undefined\n if (state.def.contextReferences && !resumeSessionId) {\n try {\n const cacheKey = state.def.workspace\n let cached = this.referencesCache.get(cacheKey)\n if (!cached) {\n const [refs, recipes] = await step(\"references\", () => Promise.all([\n loadReferences(state.def.workspace),\n loadRecipes(state.def.workspace),\n ]))\n if (refs.byId.size === 0 && recipes.recipes.length === 0) {\n const [rootRefs, rootRecipes] = await step(\"references\", () => Promise.all([\n loadReferences(process.cwd()),\n loadRecipes(process.cwd()),\n ]))\n cached = { refs: rootRefs, recipes: rootRecipes }\n } else {\n cached = { refs, recipes }\n }\n this.referencesCache.set(cacheKey, cached)\n }\n const resolved = resolveRecipes(\n {\n agentId: task.agentId,\n intentTags: intent?.path,\n message: task.message,\n },\n cached.recipes,\n cached.refs,\n )\n if (resolved.cards.length > 0) {\n referencesBlock = renderReferences(resolved.cards, 500 * 4)\n }\n if (resolved.unresolvedIds.length > 0) {\n this.log(`[${task.agentId}] references: unresolved ids: ${resolved.unresolvedIds.join(\", \")}`)\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] references resolver failed (non-fatal): ${e?.message || e}`)\n }\n }\n\n // Bridge cross-chat amnesia: inject context from other chats (DM ↔ group).\n // Gated on the current message — we only ship the hint when the user\n // actually refers to another conversation, a peer agent, or earlier\n // activity. Otherwise it's pure waste AND breaks prompt cache every turn.\n // Lean sessions read other chats with agentx_recent (no chatId) instead.\n let crossChatContext = leanOnDemand ? \"\" : this.sessions.getCrossSessionSummary(\n task.agentId, channel, chatId, task.message,\n )\n\n // Long-memory recall pre-fetch — when the current message has an\n // explicit long-memory cue (\"yesterday\", \"last week\", \"remember when…\",\n // \"we discussed\", Arabic equivalents), pull a wider window from the\n // session store and inject as a context block so the agent doesn't have\n // to call /recall itself in obvious cases. Without this, on a fresh\n // claude session the agent would either fabricate context (the\n // observed \"Jordan Ellis\" gmail-search failure) or pester the user.\n let longMemoryRecall: string | undefined\n const lmHint = detectLongMemoryHint(task.message)\n if (lmHint) {\n try {\n const recall = this.sessions.recallTurns({\n agentId: task.agentId,\n channel,\n chatId,\n lookbackDays: lmHint.lookbackDays,\n limit: 12,\n })\n if (recall.turns.length > 0) {\n const lines: string[] = [\n `[Long-memory recall — last ${lmHint.lookbackDays}d on this chat (cue-triggered)]`,\n ]\n // Render oldest-first for natural reading flow\n const ordered = [...recall.turns].sort((a, b) => a.ts.localeCompare(b.ts))\n for (const t of ordered) {\n const stamp = t.ts.slice(11, 16) + \" \" + t.day\n const who = t.role === \"user\" ? (t.senderName || \"User\") : (t.senderName || \"Agent\")\n lines.push(`${stamp} ${who}: ${t.content}`)\n }\n if (recall.hasMore) {\n lines.push(`[…${recall.totalScanned - recall.turns.length}+ older turns available — call /recall with before=${recall.oldestTs} to walk further back]`)\n }\n lines.push(\"[End of long-memory recall — respond to the latest message above]\")\n longMemoryRecall = lines.join(\"\\n\")\n this.log(`[${task.agentId}] long-memory recall fired: lookback=${lmHint.lookbackDays}d, turns=${recall.turns.length}, scanned=${recall.totalScanned}`)\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] long-memory recall failed (non-fatal): ${e.message}`)\n }\n }\n\n // Context strategy: \"layered\" (above) or \"planner\". Planner is a Haiku\n // pre-call that curates just the bits of history/memory/cross-chat the\n // current message needs, replacing the full-blob layered approach.\n // Per-task override (for benchmarks) wins over config default.\n // Fail-open: if the planner errors or times out, we fall through with\n // the layered values already computed above.\n // Resolution order: per-task override (benchmarks) → per-agent\n // override (def.contextStrategy) → global config default.\n const strategy: \"layered\" | \"planner\" = isCodexCli\n ? \"layered\"\n : task.contextStrategy ?? state.def.contextStrategy ?? this.config.session.contextStrategy ?? \"layered\"\n let sessionHistoryOverride: string | undefined\n let planDebug: Record<string, unknown> | undefined\n let plannerSucceeded = false\n // Steps since have moved the run's step label on; a run that stalls here\n // is waiting on the gate, so that is the step it must report.\n if (!requestGateSettled) traceStep(\"request-gate\")\n const requestGate = await requestGatePending\n if (requestGate.arm === \"holdout\") this.log(`[${task.agentId}] request-gate holdout: skipping Jev preprocessing for this turn`)\n // A lean session has nothing for the planner to curate: history and\n // cross-chat are fetched by the agent, not pushed.\n if (strategy === \"planner\" && !requestGate.active && !leanOnDemand && channel !== \"voice\" && channel !== \"desktop\") {\n try {\n const { planContext } = await import(\"./context-planner\")\n const plan = await step(\"plan-context\", () => planContext({\n agentId: task.agentId,\n channel,\n chatId,\n message: task.message,\n sessions: this.sessions,\n memoryStore: this.memoryStore,\n }))\n if (plan) {\n plannerSucceeded = true\n sessionHistoryOverride = plan.sessionHistory\n memoryContext = plan.memoryContext\n memoryFacts = plan.memoryFacts\n crossChatContext = plan.crossChatContext\n planDebug = plan.debug as unknown as Record<string, unknown>\n this.log(`[${task.agentId}] planner: turns=${plan.debug.recentTurns}, mem=${plan.debug.memoryIncluded ? \"yes\" : \"no\"}, xchat=${plan.debug.crossChatIncluded ? \"yes\" : \"no\"} (${plan.debug.planLatencyMs}ms) — ${plan.debug.reasoning ?? \"\"}`)\n } else {\n this.log(`[${task.agentId}] planner returned null — falling back to layered (keeping --resume for continuity)`)\n }\n } catch (e: any) {\n this.log(`[${task.agentId}] planner failed (non-fatal): ${e.message} — falling back to layered (keeping --resume for continuity)`)\n }\n // Force a fresh Claude session ONLY when the planner produced a curated\n // prompt — that's when --resume replay would conflict with the small\n // curated context. If the planner failed, keep the existing session so\n // claude's own --resume continues to provide conversation continuity.\n // Without this guard, every failed planner call dropped the session AND\n // skipped the layered sessionHistory rebuild (which was const-bound on\n // the pre-rotation resumeSessionId state above), leaving the agent\n // context-blind on every turn — the canonical \"I don't have prior\n // context\" symptom on planner-strategy agents.\n if (plannerSucceeded && resumeSessionId) {\n this.sessions.clearClaudeSessionId(task.agentId, channel, chatId)\n resumeSessionId = undefined\n }\n }\n void planDebug // reserved for the bench harness; not injected into context\n\n // Soul switching: detect /soul command and track active profile\n const soulSessionKey = `${task.agentId}:${channel}:${chatId}`\n const soulSwitch = detectSoulSwitch(task.message)\n if (soulSwitch) {\n if (soulSwitch === \"default\") {\n this.activeSouls.delete(soulSessionKey)\n this.log(`[${task.agentId}] Soul reset to default`)\n } else {\n this.activeSouls.set(soulSessionKey, soulSwitch)\n this.log(`[${task.agentId}] Soul switched to: ${soulSwitch}`)\n }\n }\n const activeSoul = this.activeSouls.get(soulSessionKey)\n\n // Load bootstrap identity files (SOUL.md or SOUL.{profile}.md).\n // These are delivered via --append-system-prompt to Claude Code so they\n // live inside the cached system prompt — we no longer append them into\n // the per-turn user-message context (which would pay cache-create on\n // every new session). The context builder still gets a flag-empty\n // bootstrapContext so the `[Identity]` / `[Personality]` block is\n // suppressed in the rendered context (Claude sees them once, cached).\n const bootstrapFiles = loadBootstrapFiles(state.def.workspace, activeSoul)\n const bootstrapContextText = buildBootstrapContext(bootstrapFiles)\n\n // Compose the cacheable system-prompt preamble. Order matters for cache\n // stability: agent.systemPrompt is the most stable (config-defined),\n // bootstrap files follow. Soul-switching mid-session produces a new\n // append-text and a new Claude cache key; that's by design — the rare\n // switch is worth a one-time cache-create cost.\n // AgentMemory — structured per-agent memory (user / feedback /\n // project / reference). Inlined into the cacheable system prompt so\n // it survives --resume and shows up on every turn. Empty when the\n // agent has no memories yet — no prompt bloat for fresh agents.\n const agentMemoryBlock = this.agentMemory.indexMarkdown(task.agentId, this.config.session.memoryIndexMaxChars)\n\n // Context Surgery — Fix 4: code-first operating principle for coding-tier\n // agents. Cacheable (lives in the system-prompt prefix). Empty for non-\n // coding tiers so chat/orchestrator agents are unaffected.\n const codeFirstInstruction = codingInstructions(state.def.tier)\n\n // Rich-reply convention — only on interactive chat channels, and only when\n // the agent hasn't opted out. Rides the cacheable system prompt so it\n // costs nothing per turn. Kept short and conditional (\"when it genuinely\n // helps\") so agents don't spray buttons on every reply.\n const richReplyInstruction = richReplyPrompt(state.def.richMessages, channel)\n\n // Context Surgery — Fix 3: per-workspace CLAUDE.md auto-injection. Read\n // once at task-setup, cap at 4KB to bound prompt size, silent fallback so\n // a missing/unreadable file is a no-op. Lives in the cacheable system\n // prompt prefix; project-specific patterns reach the agent before any\n // task context.\n // A claude-code session gets the workspace CLAUDE.md from Claude Code\n // itself: it runs in the workspace, and its setting sources include the\n // project unless a lean profile leaves it out. Appending it here as well\n // sent it twice (#615 for lean, #455 for full: about 2.5k characters on\n // every call of a session).\n const claudeLoadsWorkspace = state.def.tier === \"claude-code\" && (sessionProfile !== \"lean\" || leanLoadsWorkspace(lean))\n let projectClaudeMd = \"\"\n if (state.def.workspace && !claudeLoadsWorkspace) {\n const claudeMdPath = resolve(state.def.workspace, \"CLAUDE.md\")\n if (existsSync(claudeMdPath)) {\n try {\n const content = readFileSync(claudeMdPath, \"utf-8\")\n const trimmed = content.length > 4000\n ? content.slice(0, 4000) + \"\\n\\n[CLAUDE.md truncated to 4KB]\"\n : content\n projectClaudeMd = `[Project CLAUDE.md]\\n${trimmed}`\n } catch {\n // silent — the agent works fine without it\n }\n }\n }\n\n // Per-channel-event runbook injection — when a project rule resolves a\n // runbook path (gitlab/github adapters stamp it onto IncomingMessage),\n // pull the canonical file set (CLAUDE.md / AGENTS.md / DEPLOY.md /\n // RUNBOOK.md, overridable per-project) and append to the system prefix.\n // This is what carries the *target* project's deploy steps and server\n // mapping into the agent's context — without it the agent falls back to\n // its workspace's CLAUDE.md, which doesn't know about ./cache_clear.sh\n // or which host runs the deploy. Each file capped at 4KB; the whole\n // bundle capped at 16KB to keep the cacheable prefix bounded.\n const runbookPath = task.context?.runbookPath\n const runbookBlocks: string[] = []\n if (runbookPath && existsSync(runbookPath)) {\n const filesToTry = task.context?.runbookFiles && task.context.runbookFiles.length > 0\n ? task.context.runbookFiles\n : [\"CLAUDE.md\", \"AGENTS.md\", \"DEPLOY.md\", \"RUNBOOK.md\"]\n let totalBytes = 0\n const PER_FILE_CAP = 4000\n const TOTAL_CAP = 16000\n for (const filename of filesToTry) {\n if (totalBytes >= TOTAL_CAP) break\n const filePath = resolve(runbookPath, filename)\n if (!existsSync(filePath)) continue\n try {\n const content = readFileSync(filePath, \"utf-8\")\n const trimmed = content.length > PER_FILE_CAP\n ? content.slice(0, PER_FILE_CAP) + `\\n\\n[${filename} truncated to ${PER_FILE_CAP} bytes]`\n : content\n runbookBlocks.push(`[Project Runbook — ${filename} from ${runbookPath}]\\n${trimmed}`)\n totalBytes += trimmed.length\n } catch {\n // silent — missing readability is a no-op\n }\n }\n }\n const projectRunbook = runbookBlocks.length > 0 ? runbookBlocks.join(\"\\n\\n\") : \"\"\n\n const systemPromptAppend = [\n state.def.systemPrompt || \"\",\n codeFirstInstruction,\n richReplyInstruction,\n projectClaudeMd,\n projectRunbook,\n bootstrapContextText || \"\",\n agentMemoryBlock || \"\",\n ].filter((s) => s.trim().length > 0).join(\"\\n\\n\") || undefined\n\n const contextInput: ContextInput = {\n channel,\n chatId,\n channelScope: task.context?.group ? \"group\" : (channel === \"gitlab\" ? \"project\" : \"personal\"),\n groupName: task.context?.group,\n agentId: task.agentId,\n agentName: state.def.name,\n agentHandle: this.getChannelHandle(task.agentId, channel),\n systemPrompt: state.def.systemPrompt,\n sender: senderName,\n senderId: task.context?.senderId,\n senderUsername: task.context?.senderUsername,\n // Context Surgery — Fix 2: skip landscape for coding-tier agents in\n // github/gitlab contexts. The mesh-peer summary (~1-2K tokens of cross-\n // team agents) is irrelevant for focused code work on one issue/PR and\n // anchors the agent on context it shouldn't be using. Chat/orchestrator\n // agents still get the landscape — that's where peer discovery matters.\n // Lean sessions fetch it with agentx_agents (landscape=true) instead.\n landscape: (isCodexCli || leanOnDemand || isCodingChannelContext(channel, state.def.tier))\n ? undefined\n : this.landscape?.getForAgent(task.agentId),\n channelMeta: task.context?.channelMeta,\n mediaPath: task.context?.mediaPath,\n mediaType: task.context?.mediaType,\n replyToText: task.context?.replyToText,\n // bootstrapContext intentionally omitted — delivered via system prompt.\n patternContext: isCodexCli ? undefined : patternContext || undefined,\n procedureContext: [procedureContext, workflowHint].filter(Boolean).join(\"\\n\\n\") || undefined,\n workflowRunContext,\n references: referencesBlock,\n skillInjection: skillInjection || undefined,\n groupHistory: task.context?.group ? undefined : undefined, // group log is injected by router\n // Planner override takes precedence when set — falls back to the\n // layered `sessionHistory` (full buildHistoryContext, scoped by\n // resumeSessionId presence) otherwise.\n sessionHistory: sessionHistoryOverride ?? sessionHistory,\n memoryContext: memoryContext || undefined,\n crossChatContext: crossChatContext || undefined,\n longMemoryRecall: longMemoryRecall || undefined,\n // The one line that stands in for the three layers above on a fresh\n // lean session; a resumed one already saw it.\n contextOnDemand: leanOnDemand && !resumeSessionId ? onDemandContextNote(channel, chatId) : undefined,\n wikiContext,\n handoverNote: this.buildHandoverNote(task.agentId, channel, chatId),\n rotationMemo,\n // Checked here, after the planner may have dropped the session.\n eventDigest: !resumeSessionId ? buildEventDigest(task.agentId, this.config.agents[task.agentId]?.subscriptions ?? state.def.subscriptions) : undefined,\n // How to show a file on the phone: once, when the session starts.\n // Sent only on a fresh app session with rich messages on (#259); the\n // outbox is prepared only when the hint that names it goes out (#258).\n attachHint: (() => {\n const hint = appAttachHint(channel, !resumeSessionId, state.def.richMessages)\n return hint ? withOutbox(state.def.workspace, hint) : undefined\n })(),\n intent: intent\n ? {\n path: intent.path,\n pathLabel: intent.pathLabel,\n pathId: intent.pathId,\n axes: intent.axes,\n leaf: intent.leaf,\n status: intent.status,\n }\n : undefined,\n message: task.message,\n }\n\n // Route mechanical work to a cheaper model, when the seat is active\n // and sure. Everything that is not an explicit confident \"no\" keeps\n // the agent's own model — see agents/routing.ts for why the failure\n // has to land on the expensive side.\n //\n // Started before the context selection and awaited after it: neither\n // reads the other's answer, so the two seat calls overlap instead of\n // queueing (#455). The answer is still in hand before the task runs.\n // A run started only by triage events (a label, a close) goes to the\n // configured triage model first: a fixed rule, no seat asked (#615).\n const triage = !task.model\n ? triageModelFor({\n tier: state.def.tier,\n channel,\n eventActions: task.context?.channelMeta?.eventActions,\n triage: this.config.session.triage,\n isFollowUp: Boolean(resumeSessionId),\n sessionIdleMs: resumeSessionId ? priorIdleMs : null,\n })\n : undefined\n if (triage?.downgraded) this.log(`[${task.agentId}] ${triage.reason}`)\n const cheapModel = cheapModelForEngine(state.def.tier, this.config.decisions.routing)\n const routedModelPending: Promise<string | undefined> =\n triage?.downgraded\n ? Promise.resolve(triage.model)\n : !task.model && cheapModel && (!requestGate.active || requestGate.preprocess)\n ? (async () => {\n try {\n const { routeTaskModel } = await import(\"./routing\")\n const route = await step(\"route-model\", () => routeTaskModel({\n message: task.message,\n agent: task.agentId,\n channel,\n isFollowUp: Boolean(resumeSessionId),\n // Idle time decides whether the cache this would give up still\n // exists. See routing.ts for the arithmetic.\n sessionIdleMs: resumeSessionId ? priorIdleMs : null,\n cheapModel,\n }))\n if (route.downgraded) {\n this.log(`[${task.agentId}] ${route.reason}`)\n return route.model\n }\n } catch {\n /* routing is an optimisation; never let it stop a task */\n }\n return undefined\n })()\n : Promise.resolve(undefined)\n let routedModelSettled = false\n routedModelPending.then(() => { routedModelSettled = true })\n\n const selectedContext = requestGate.active && requestGate.preprocess\n ? await budgeted(\"select-context\", () => selectRequestContext(contextInput), () => ({ input: contextInput, excluded: [] as string[] }))\n : { input: contextInput, excluded: [] }\n if (selectedContext.excluded.length) this.log(`[${task.agentId}] request-context excluded: ${selectedContext.excluded.join(\", \")}`)\n\n const historyContext = buildAgentContext(\n selectedContext.input,\n isCodexCli\n ? {\n totalBudget: 1800,\n layerBudgets: {\n channel: 180,\n scope: 100,\n identity: 120,\n references: 250,\n intent: 80,\n artifacts: 250,\n memory: 250,\n history: 450,\n \"cross-chat\": 200,\n \"long-memory\": 350,\n handover: 120,\n },\n }\n : undefined,\n )\n\n // Lesson impact (#98): the memory, procedure and wiki ids that made it\n // into this prompt, for `agentx trace lessons`. Ids only, capped.\n const injectedContext = injectedContextOf(historyContext, {\n memory: memoryFacts,\n procedures: procedureCandidates,\n wikiContext: selectedContext.input.wikiContext,\n })\n\n // Context-size telemetry. Bytes we control: the layered context string +\n // the cacheable system-prompt append + the current message. Bytes we\n // don't: the Claude CLI --resume replay (tool results from prior turns),\n // which shows up as cacheReadTokens in response.usage. Log a warning\n // when our controllable assembly is unusually large (>16K chars ≈ 4K\n // tokens) — anything that big is usually a runaway layer worth\n // investigating before it snowballs via --resume.\n const agentxContextBytes =\n (historyContext?.length ?? 0) +\n (systemPromptAppend?.length ?? 0) +\n (task.message?.length ?? 0)\n const sizeParts = {\n history: historyContext?.length ?? 0,\n sysPrompt: systemPromptAppend?.length ?? 0,\n message: task.message?.length ?? 0,\n }\n // Record every dispatch for drift detection, warn when growing.\n const sizeKey = promptSizeKey(task.agentId, channel, chatId)\n recordPromptSize(sizeKey, agentxContextBytes, sizeParts)\n const driftWarning = warnIfPromptGrowing(sizeKey)\n if (driftWarning) this.log(`[${task.agentId}] ${driftWarning}`)\n if (agentxContextBytes > 16_000) {\n this.log(`[${task.agentId}] large context for ${channel}:${chatId}: ${agentxContextBytes} bytes (history=${sizeParts.history}, sysPrompt=${sizeParts.sysPrompt}, message=${sizeParts.message})`)\n }\n\n // Attach the cacheable preamble onto the task so runtime.ts can forward\n // it to Claude CLI's --append-system-prompt arg.\n if (!routedModelSettled) traceStep(\"route-model\")\n const routedModel = await routedModelPending\n\n // Lean claude-code sessions also start the CLI with only the agentx MCP\n // server and the project's settings (#615); full ones add the agentx\n // server to what they load anyway (#668). The flags describe the\n // process, so a resumed session carries them too.\n const claudeArgs = state.def.tier !== \"claude-code\"\n ? undefined\n : sessionProfile === \"lean\"\n ? leanClaudeArgs(state.def.workspace, lean, undefined, channel)\n : fullClaudeArgs(state.def.workspace)\n if (sessionProfile === \"lean\" && !resumeSessionId) {\n this.log(`[${task.agentId}] session profile for ${channel}: ${describeProfile(sessionProfile, lean, channel)}`)\n }\n\n // tool-set seat, shadow only (#455): which built-in tools this fresh\n // session would need. Asked alongside the run and never awaited here,\n // so it adds no time before the agent starts; the run's own tool calls\n // label it afterwards.\n const toolSetPending = state.def.tier === \"claude-code\" && !resumeSessionId\n ? startToolSetShadow({\n message: task.message ?? \"\",\n agent: task.agentId,\n channel,\n agentPrompt: state.def.systemPrompt ?? null,\n taskId: traceTaskId,\n }, (m) => this.log(m))\n : undefined\n\n const taskWithSystemPrompt: AgentTask = {\n ...task, systemPromptAppend,\n ...(claudeArgs ? { claudeArgs } : {}),\n ...(routedModel ? { model: routedModel } : {}),\n }\n\n let finalResponse: AgentResponse | undefined\n // turn-progress shadow seat: watches the tool steps of this turn.\n const turnWatch = startTurnWatch({\n agent: task.agentId, request: task.message, taskId: traceTaskId,\n budgetMinutes: state.def.maxExecutionMinutes ?? 20,\n })\n const unwatchedOnEvent = onEvent\n // workflows.required (#858): the turn's wrapping run, once created, and\n // whether every tool it used changed nothing (the plain-question test).\n let wrapStarted = false\n let wrapReadOnly = true\n onEvent = (event: any) => {\n turnWatch.observe(event)\n if (wrapRunId && wrapReadOnly) {\n for (const use of toolUsesOf(event)) if (!isReadOnlyToolUse(use.name, use.input)) wrapReadOnly = false\n }\n unwatchedOnEvent?.(event)\n }\n try {\n // Workflow auto-run short-circuit. When the matcher upstream picked a\n // workflow at >= autoRunThreshold AND `workflows.matching.mode == \"auto\"`,\n // fire that workflow now and return. The workflow owns posting any\n // reply via its action.send / agent nodes; we return an empty content\n // with a metadata marker so the caller knows not to send a redundant\n // agent reply. On runner failure, we log and fall through to normal\n // agent execution — auto-run is best-effort, never a footgun.\n const autoRunner = this.workflowAutoRunner\n if (pendingAutoRun && autoRunner) {\n const autoRun = pendingAutoRun\n try {\n const result = await step(\"workflow-auto-run\", () => autoRunner({\n workflowId: autoRun.workflowId,\n agentId: task.agentId,\n channel,\n chatId,\n message: task.message,\n ...(autoRun.follow ? { follow: true } : {}),\n payload: {\n message: task.message,\n agentId: task.agentId,\n channel,\n chatId,\n senderId: task.context?.senderId,\n senderUsername: task.context?.senderUsername,\n intentPath: intent?.path,\n matchedWorkflowId: autoRun.workflowId,\n matchConfidence: autoRun.confidence,\n },\n }))\n this.log(\n `[${task.agentId}] workflow auto-run started ${pendingAutoRun.workflowId} run=${result.runId || \"?\"}; ` +\n `agent execution skipped — workflow owns the reply`,\n )\n finalResponse = {\n // A required run answers the person with where their request\n // went; the run's own steps and summary do the rest.\n content: autoRun.follow\n ? `Started the workflow \"${result.title ?? autoRun.workflowId}\" for this${result.runId ? ` (run ${result.runId})` : \"\"}. AgentX follows each step and tells the owner when it is done.`\n : \"\",\n duration: 0,\n metadata: {\n handledByWorkflow: pendingAutoRun.workflowId,\n workflowRunId: result.runId,\n workflowMatchConfidence: pendingAutoRun.confidence,\n },\n } as AgentResponse\n return finalResponse\n } catch (e: any) {\n this.log(`[${task.agentId}] workflow auto-run failed; falling back to agent: ${e?.message || e}`)\n }\n }\n\n // workflows.required (#858): no saved workflow took it, so the turn\n // runs inside a run of its own. Best-effort: a store that cannot\n // write never stops the task.\n if (wrapRunId && wrapRunStore) {\n try {\n // A stopped task resumed: its new run names the run the stop closed.\n const continues = continuedRun(wrapRunStore, task.resumedFrom)\n startWrap(wrapRunStore, { runId: wrapRunId, agentId: task.agentId, channel, chatId, message: task.message, taskId: traceTaskId, ...(continues ? { continues } : {}) })\n wrapStarted = true\n runningTask.workflow = { runId: wrapRunId, workflowId: TASK_WORKFLOW_ID }\n } catch (e: any) {\n this.log(`[${task.agentId}] workflows.required: could not create run ${wrapRunId} (non-fatal): ${e?.message || e}`)\n }\n }\n\n // Pre-flight gates (claude-code tier only). Two separate heuristics that\n // short-circuit doomed cold dispatches BEFORE we burn a Claude CLI\n // subprocess to reproduce the same failure:\n // (1) Overage gate — when Anthropic has disabled Max-plan extra usage\n // at the org level. A cold dispatch's fresh cache-create spills\n // past the regular allotment and gets rejected.\n // (2) Quota gate — when Claude Code reports a plan window as\n // rejected (and extra usage is not serving), or when an opt-in\n // local dispatch cap is reached. Warm sessions still pass; cold\n // dispatches are deferred.\n // Warm sessions (resumeSessionId set) bypass both gates — prompt-cache\n // replay keeps them inside the regular allotment.\n //\n // Kept inside the try so the `finally` below still runs — otherwise a\n // short-circuit return leaks runningTask bookkeeping.\n //\n // The gates guard a LOCAL claude spawn, so a task bound for a cloud\n // session (#622) skips them: the session draws on separate credit, and\n // a rejected plan window is exactly when the cloud is wanted. When the\n // launch then fails, the gates run before the local fallback.\n const runDispatchGates = (): AgentResponse | null => {\n if (state.def.tier !== \"claude-code\") return null\n traceStep(\"dispatch-gate\")\n // A persistent-process handle counts as \"warm\" — its subprocess already\n // has the system prompt cached, so no fresh cache-create is needed even\n // when there's no --resume session ID stored for this chatId yet.\n const procReg = state.def.persistentProcess ? getProcessRegistry() : null\n const hasLiveProcess = procReg != null\n && procReg.hasLive({ agentId: task.agentId, channel, chatId })\n const hasWarmSession = Boolean(resumeSessionId) || hasLiveProcess\n const gates = [\n preflightOverageGate(hasWarmSession),\n preflightQuotaGate(hasWarmSession, undefined, task.model || state.def.model),\n ]\n const abort = gates.find((g) => g && g.abort)\n if (abort) {\n this.log(`[${task.agentId}] skipping cold dispatch — ${abort.reason}`)\n const preflightResponse: AgentResponse = {\n content: \"\",\n error: abort.message,\n duration: 0,\n }\n if (!onDelta) {\n output.buffer = `[error] ${abort.message}`\n for (const sub of output.subscribers) {\n try { sub(output.buffer) } catch { /* */ }\n }\n }\n return preflightResponse\n }\n // Past the gates — commit the dispatch to the rolling budget and log\n // a warning if we've crossed warnRatio so the operator sees pressure\n // building before it turns into rejections.\n recordClaudeCodeDispatch()\n const warning = warnIfNearingCap()\n if (warning) this.log(`[${task.agentId}] ${warning}`)\n return null\n }\n const cloudRoute = this.planCloudSession(state.def, task)\n if (cloudRoute.route === \"local\") {\n const gated = runDispatchGates()\n if (gated) {\n // The early return skips the error accounting below; a gated\n // fallback inside the agent step goes through it instead.\n state.errors++\n finalResponse = gated\n return gated\n }\n }\n\n // Tiers other than claude-code don't emit stream-json events into\n // onEvent — they only call onDelta with text chunks. Without\n // wiring those chunks into the modal buffer, /live's task modal\n // came up blank for orchestrator-tier (acme-public) and\n // codex-cli runs even when text was actively streaming back to\n // the caller. Wrap onDelta only for non-claude-code tiers; for\n // claude-code the formatter (onEvent → pushToBuffer) is already\n // the authoritative source — wrapping there would double-print\n // every text chunk in the modal.\n const tierUsesStreamJson = state.def.tier === \"claude-code\"\n const wrappedOnDelta: StreamCallback | undefined = tierUsesStreamJson\n ? onDelta\n : (text, fullText) => {\n if (text) pushToBuffer(text)\n if (onDelta) onDelta(text, fullText)\n }\n // Thinking chunks land on a side channel: we forward to the\n // caller's onThinking (chat endpoint surfaces them as\n // delta.reasoning_content on the SSE wire) AND push to the\n // modal buffer with a `💭 ` prefix only on the first chunk so\n // the dashboard's line-based renderer treats the whole run of\n // reasoning as a single thought event instead of one per token.\n let thinkingOpen = false\n const wrappedOnThinking: ThinkingCallback | undefined = tierUsesStreamJson\n ? onThinking\n : (text) => {\n if (text) {\n if (!thinkingOpen) {\n pushToBuffer(`💭 ${text}`)\n thinkingOpen = true\n } else {\n pushToBuffer(text)\n }\n }\n if (onThinking) onThinking(text)\n }\n // When the agent transitions back to text, close any open\n // thought block in the modal so the next text chunk doesn't\n // get folded into the same line.\n const closeThinkingIfOpen = () => {\n if (thinkingOpen) { pushToBuffer(\"\\n\"); thinkingOpen = false }\n }\n const wrappedOnDeltaWithThinkingClose: StreamCallback | undefined = tierUsesStreamJson\n ? wrappedOnDelta\n : wrappedOnDelta\n ? (text, fullText) => { closeThinkingIfOpen(); wrappedOnDelta(text, fullText) }\n : undefined\n // The runtime reaps its own subprocess on abort; the grace only covers\n // a runtime that never returns.\n const response = await step(\"agent\", async () => {\n // Cloud first (#622): a launched session answers the task; a failed\n // launch runs it locally after the gates a local spawn gets.\n if (cloudRoute.route !== \"local\") {\n const cloud = await this.runCloudSession(state.def, task, cloudRoute, traceTaskId)\n if (cloud) return cloud\n const gated = runDispatchGates()\n if (gated) return gated\n }\n return executeTask(state.def, taskWithSystemPrompt, this.providers, wrappedOnDeltaWithThinkingClose, historyContext, resumeSessionId, onEvent, abortController.signal, wrappedOnThinking)\n }, CANCEL_GRACE_MS)\n\n // Improvement plan #3 — fail with a typed error when the agent\n // declared toolUseRequired and the model didn't invoke at least\n // one of the listed tools. Only fires when the response itself\n // wasn't already an error (an outer error wins). The error\n // message uses the canonical `tool_required_not_called: <name>`\n // shape so retry / fallback logic can pattern-match on it.\n if (!response.error) {\n const missing = firstMissingRequiredTool(state.def.toolUseRequired, toolUsesByName)\n if (missing) {\n const invokedSummary = Array.from(toolUsesByName.entries())\n .map(([n, c]) => `${n}=${c}`)\n .join(\", \") || \"none\"\n response.error = `tool_required_not_called: ${missing} (invoked: ${invokedSummary})`\n response.content = \"\"\n this.log(`[${task.agentId}] tool-use contract violation — required ${missing}, observed ${invokedSummary}`)\n }\n }\n\n if (toolSetPending) {\n void labelToolSetRun(toolSetPending, {\n toolUses: toolUsesByName, eventsSeen: firstEventSeen, error: response.error, agentId: task.agentId,\n }, (m) => this.log(m))\n }\n\n // Stopped by a daemon shutdown: say so, whatever the runtime made of\n // the kill, and leave the trace in flight so the next boot resumes it.\n const interruptedBy = this.interruptedRuns.get(runningTask.id)\n if (interruptedBy) {\n response.content = \"\"\n response.error = interruptedBy\n response.errorKind = \"interrupted\"\n }\n // Ended by a stop signal (#857): stopped, not failed.\n const stoppedBy = interruptedBy ? undefined : this.stoppedRuns.get(runningTask.id)\n if (stoppedBy) {\n response.content = \"\"\n response.error = stoppedBy\n response.errorKind = \"stopped\"\n }\n\n finalResponse = response\n\n // Split this request's tokens into tier1/tier2 buckets so subscribers\n // (sqlite usage_daily, audit, dashboard) record both lanes. The same\n // threshold that TokenTracker.record() applies — extracted as a\n // helper so the two sites can never drift.\n const split = response.usage ? splitTaskUsageByTier(response.usage) : undefined\n completedEmitted = true\n getEventBus().emit(\"task:completed\", {\n taskId: traceTaskId,\n agentId: task.agentId,\n channel,\n chatId,\n durationMs: Date.now() - taskStartedAt,\n error: response.error || undefined,\n errorKind: response.error ? response.errorKind : undefined,\n interrupted: interruptedBy ? true : undefined,\n stopped: stoppedBy ? true : undefined,\n inputTokens: split?.inputTokens,\n outputTokens: split?.outputTokens,\n cacheReadTokens: split?.cacheReadTokens,\n cacheCreateTokens: split?.cacheCreateTokens,\n tier2InputTokens: split?.tier2InputTokens,\n tier2OutputTokens: split?.tier2OutputTokens,\n tier2CacheReadTokens: split?.tier2CacheReadTokens,\n tier2CacheCreateTokens: split?.tier2CacheCreateTokens,\n resumed: Boolean(resumeSessionId),\n resumeSessionId: resumeSessionId || undefined,\n jevArm: requestGate.arm,\n numTurns: response.numTurns,\n injectedContext,\n finalResponse: response.content || undefined,\n // Per-task model attribution: prefer what the runtime actually\n // billed; fall back to the agent's configured model so codex-cli\n // / sdk agents don't get stamped with the daemon-wide opus default.\n billedModel: response.billedModel || state.def.model || undefined,\n at: new Date().toISOString(),\n })\n\n // For non-streaming runs (no caller onDelta) we never captured incremental\n // chunks — surface the final response in one shot so the dashboard modal\n // shows something meaningful when subscribers are attached.\n if (!onDelta && (response.content || response.error)) {\n const finalText = response.error ? `[error] ${response.error}` : response.content\n output.buffer = finalText\n for (const sub of output.subscribers) {\n try { sub(finalText) } catch { /* */ }\n }\n }\n\n if (response.error) {\n state.errors++\n this.log(`[${task.agentId}] error: ${response.error}`)\n } else {\n // Record agent response in session\n this.sessions.addAgentMessage(task.agentId, channel, chatId, response.content)\n onAgentReply({ agent: task.agentId, channel, chatId, request: task.message, reply: response.content, taskId: traceTaskId })\n\n // Store native CLI session IDs for future resume.\n if (response.claudeSessionId) {\n this.sessions.setClaudeSessionId(task.agentId, channel, chatId, response.claudeSessionId)\n }\n if (response.codexSessionId) {\n this.sessions.setCodexSessionId(task.agentId, channel, chatId, response.codexSessionId)\n }\n if (response.opencodeSessionId) {\n this.sessions.setOpenCodeSessionId(task.agentId, channel, chatId, response.opencodeSessionId)\n }\n\n // Record this turn's usage so next task can decide whether to rotate:\n // turnCount + cumulative lastTurnInputTokens (observability) +\n // per-request lastTurnContextTokens (the actual rotation metric).\n // Only meaningful when we kept a claude session — skip otherwise so the\n // counter isn't incremented for tiers that don't use --resume.\n if ((response.claudeSessionId || response.codexSessionId || response.opencodeSessionId) && response.usage) {\n this.sessions.recordTurnUsage(task.agentId, channel, chatId, response.usage, response.contextTokens)\n }\n\n // Tier-2 warning: Claude bills the 1.5× long-context rate when a\n // single REQUEST's input crosses 200K — so judge by the end-of-turn\n // per-request context size, not the cumulative turn total (which\n // sums cache reads across every call and reads 10-20× too high).\n // Next turn will auto-rotate (see shouldRotateByTierTwo); logging\n // THIS turn keeps it observable.\n if (response.usage) {\n const contextSize = response.contextTokens\n const cumulative =\n (response.usage.inputTokens || 0) +\n (response.usage.cacheReadTokens || 0) +\n (response.usage.cacheCreateTokens || 0)\n if ((contextSize ?? cumulative) >= this.sessions.getTierTwoThresholdTokens(channel)) {\n this.log(`[${task.agentId}] TIER-2 HIT on ${channel}:${chatId}: context=${contextSize ?? \"n/a\"} tokens (cumulative turn total=${cumulative}) — next turn will rotate`)\n }\n }\n\n // Wiki: export conversation as raw entry for later absorption.\n //\n // Filtered at the door. The old gate was response length alone, so\n // every task was captured with its own prompt as the \"User:\" half:\n // cron prompts, role briefs, missed-run notices. Of the 10,703\n // entries that produced, 47.5% were machine-origin or prompt-shaped,\n // against 257 articles ever compiled. Sifting that with Sonnet at\n // absorb time costs ~19 minutes a run; rejecting it here costs a\n // regex.\n const wikiCapture = shouldCaptureEntry({\n channel,\n content: `User: ${task.message}`,\n responseLength: response.content.length,\n })\n if (wikiCapture.capture) {\n // The entry carries the classifier's path; by now the turn has\n // run for seconds to minutes, so this rarely waits at all.\n await awaitIntent(INTENT_WAIT_BEFORE_ENTRY_MS)\n try {\n const entryId = `${task.agentId}-${Date.now().toString(36)}`\n this.wikiHub.getSharedStore().addEntry({\n id: entryId,\n date: new Date().toISOString().slice(0, 10),\n agentId: task.agentId,\n source: channel,\n sourceContext: task.context?.group || task.context?.sender,\n content: `User: ${task.message}\\n\\nAgent: ${response.content}`,\n // Who actually spoke.\n //\n // `sourceContext` collapses group and sender into one slot\n // and the group wins, so in a group chat the speaker was\n // dropped on the floor — and group chat is where most\n // people talk. Across a month of entries not one recorded\n // a sender, which is why the wiki could describe someone's\n // billing thread in detail and not know how to reach them:\n // the fact was never captured, so no prompt and no lookup\n // could recover it.\n //\n // senderId is the platform's own identifier — on WhatsApp\n // that is the JID, which is the number itself. Capturing\n // it here means the identifier arrives with the entry\n // instead of being reconstructed later from a directory.\n // Stamp the classifier's path on the entry so `wiki absorb` can\n // propagate it to the article's `graphPath` without\n // reconstructing fingerprints from transformed content. Without\n // this, articles never carry graphPath and the wiki retrieval\n // scorer multiplies the graph weight (default 0.6) by 0.\n meta: buildEntryMeta(intent, task.context),\n })\n } catch {\n // Wiki export is best-effort\n }\n\n // Memory: extract and store memorable facts via Haiku (fire-and-forget)\n extractMemories(\n task.agentId,\n task.message,\n response.content,\n { channel, chatId, sender: senderName },\n this.memoryStore,\n ).catch(() => {})\n\n // Patterns: extract behavioral patterns (fire-and-forget)\n extractPatterns(\n task.agentId,\n task.message,\n response.content,\n { channel, date: new Date().toISOString().slice(0, 10) },\n this.patternStore,\n ).catch(() => {})\n }\n\n // Track token usage (real counts if available, estimate otherwise).\n // Model priority for pricing: what the API ACTUALLY billed (from the\n // CLI init / result event) > per-task override (cron model) > agent\n // config default. Without this, cron-overridden runs get priced at\n // the wrong rate and cache-aware cost reports mislead operators.\n const billedModel = response.billedModel || task.model || state.def.model\n const tChannel = task.context?.channel\n // Session key = \"channel:chatId\" — opaque to the tracker, just needs\n // to be unique per (conversation, day). Lets us derive avg-tasks/\n // session later so retry-heavy traffic becomes visible.\n const tSessionKey = tChannel && (task.context?.chatId || task.context?.group || task.context?.sender)\n ? `${tChannel}:${task.context?.chatId || task.context?.group || task.context?.sender}`\n : undefined\n this.tokenTracker.record(\n task.agentId,\n response.duration || 0,\n response.usage,\n task.message.length,\n response.content.length,\n false,\n billedModel,\n tChannel,\n tSessionKey,\n )\n\n this.log(\n `[${task.agentId}] completed in ${response.duration}ms` +\n (response.tokensUsed ? ` (${response.tokensUsed} tokens)` : \"\") + ` ${runTag()}`,\n )\n }\n\n return response\n } catch (error: any) {\n state.errors++\n this.log(`[${task.agentId}] unexpected error: ${error.message}`)\n const friendly = friendlyModelError(error.message)\n finalResponse = {\n content: \"\",\n error: renderFriendlyError(friendly),\n errorKind: friendly.kind,\n }\n return finalResponse\n } finally {\n turnWatch.stop()\n if (wrapStarted && wrapRunId && wrapRunStore) {\n try {\n const usage = finalResponse?.usage\n const closed = finishWrap(wrapRunStore, wrapRunId, {\n error: finalResponse ? finalResponse.error || undefined : (abortController.signal.aborted ? abortReason(abortController.signal).message : \"run ended before completion\"),\n canceled: endedWithoutFailing(finalResponse?.errorKind) || this.stoppedRuns.has(runningTask.id) || this.interruptedRuns.has(runningTask.id) || (!finalResponse && abortController.signal.aborted),\n durationMs: Date.now() - runningTask.startedAt.getTime(),\n ...(usage ? { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens } : {}),\n readOnly: wrapReadOnly,\n exemptQuestions: this.config.workflows?.required?.exemptQuestions ?? true,\n })\n if (closed && closed !== \"discarded\") this.log(`[${task.agentId}] workflow run ${wrapRunId} ${closed.status} (${closed.meta?.wrap?.mode ?? \"linear\"})`)\n } catch (e: any) {\n this.log(`[${task.agentId}] workflows.required: could not close run ${wrapRunId} (non-fatal): ${e?.message || e}`)\n }\n }\n // A run that threw before its completion event (a pre-spawn abort, a\n // cancel, an unexpected error) still closes its trace; otherwise the\n // row stayed \"in-flight\" and the next boot resumed it as cut-off work\n // (#340). A shutdown interruption keeps it open on purpose.\n closeTrace(finalResponse)\n releaseRun(finalResponse)\n }\n }\n\n /**\n * List all agents and their status.\n */\n /** Total active task count across all agents — used by the daemon's\n * graceful shutdown to drain in-flight runs before exit. */\n getActiveTaskCount(): number {\n let total = 0\n for (const s of this.agents.values()) total += s.activeTasks\n return total\n }\n\n /** drainTimeoutSeconds of each agent with a run in flight (undefined when\n * unset), so a stop can wait as long as the longest of them asks. */\n runningAgentDrainSeconds(): Array<number | undefined> {\n const out: Array<number | undefined> = []\n for (const s of this.agents.values()) if (s.activeTasks > 0) out.push(s.def.drainTimeoutSeconds)\n return out\n }\n\n list(): Array<{\n id: string\n name: string\n tier: string\n model?: string\n workspace: string\n active: number\n total: number\n errors: number\n lastActive?: Date\n runningTasks: RunningTask[]\n lastSummary?: { text: string; at: string; ok: boolean }\n hourlyTasks?: number[]\n }> {\n return Array.from(this.agents.values()).map((s) => {\n const summary = this.lastSummaries.get(s.id)\n return {\n id: s.id,\n name: s.def.name,\n tier: s.def.tier,\n model: s.def.model,\n workspace: s.def.workspace,\n active: s.activeTasks,\n total: s.totalTasks,\n errors: s.errors,\n lastActive: s.lastActive,\n runningTasks: this.withWorkflowSteps(s.runningTasks),\n lastSummary: summary ? { text: summary.text, at: summary.at.toISOString(), ok: summary.ok } : undefined,\n hourlyTasks: this.getHourlySparkline(s.id, 24),\n }\n })\n }\n\n /** Running tasks with the workflow run each belongs to and the step it\n * is on (#858), read from the run store; the stored list is not touched. */\n private withWorkflowSteps(tasks: RunningTask[]): RunningTask[] {\n const runs = this.workflowRuns\n if (!runs || !tasks.some((t) => t.workflow)) return tasks\n return tasks.map((t) => {\n if (!t.workflow) return t\n try {\n const run = runs.get(t.workflow.runId)\n if (!run) return t\n return { ...t, workflow: { ...t.workflow, workflowId: run.workflowId, step: liveStep(run), title: run.meta?.title ?? run.workflowId } }\n } catch { return t }\n })\n }\n\n /**\n * Write a finished task record to disk under .agentx/task-history/<agent>/<yyyy-mm-dd>/.\n * Side-effects only — call sites should treat failure as best-effort.\n */\n private persistTaskRecord(record: TaskRecord): void {\n const day = record.endedAt.slice(0, 10)\n const dir = resolve(process.cwd(), TASK_HISTORY_DIR, this.safe(record.agentId), day)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n const file = resolve(dir, `${this.safe(record.id)}.json`)\n writeFileSync(file, JSON.stringify(record, null, 2), \"utf-8\")\n }\n\n /**\n * Compute a 24-hour hourly task-count sparkline for an agent from persisted\n * history. Returns an array of 24 numbers: buckets[0] = 23-24h ago,\n * buckets[23] = now-hour. Cached per-agent for 60s to keep snapshot load\n * reasonable when the dashboard polls every 2s.\n */\n getHourlySparkline(agentId: string, hours: number = 24): number[] {\n const now = Date.now()\n const cached = this.sparklineCache.get(agentId)\n if (cached && now - cached.at < 60_000 && cached.hourly.length === hours) return cached.hourly\n\n const buckets = new Array<number>(hours).fill(0)\n const windowStart = now - hours * 3600_000\n const root = resolve(process.cwd(), TASK_HISTORY_DIR, this.safe(agentId))\n if (!existsSync(root)) {\n this.sparklineCache.set(agentId, { hourly: buckets, at: now })\n return buckets\n }\n // Only need the last 2 day folders — crossing midnight still fits.\n const today = new Date(now).toISOString().slice(0, 10)\n const yesterday = new Date(now - 86400_000).toISOString().slice(0, 10)\n const dayToday = new Date(now); dayToday.setUTCHours(0, 0, 0, 0)\n for (const day of [yesterday, today]) {\n const dayDir = resolve(root, day)\n if (!existsSync(dayDir)) continue\n try {\n for (const f of readdirSync(dayDir)) {\n if (!f.endsWith(\".json\")) continue\n // Task id is timestamp-prefixed (e.g. 1776447897061-xxx.json) — pull\n // the millis without parsing each file.\n const tsStr = f.split(\"-\")[0]\n const ts = parseInt(tsStr, 10)\n if (!Number.isFinite(ts) || ts < windowStart || ts > now) continue\n const offsetHours = Math.floor((now - ts) / 3600_000)\n const idx = hours - 1 - offsetHours\n if (idx >= 0 && idx < hours) buckets[idx]++\n }\n } catch { /* skip unreadable */ }\n }\n this.sparklineCache.set(agentId, { hourly: buckets, at: now })\n return buckets\n }\n\n /**\n * Rebuild the in-memory lastSummaries cache from disk so dashboard cards\n * show the most recent \"what did they do\" line even right after a restart.\n * Called once on daemon startup — one file read per agent.\n */\n hydrateLastSummariesFromDisk(): number {\n const root = resolve(process.cwd(), TASK_HISTORY_DIR)\n if (!existsSync(root)) return 0\n let loaded = 0\n for (const agentDir of readdirSync(root)) {\n const agentPath = resolve(root, agentDir)\n let days: string[]\n try { days = readdirSync(agentPath).filter((d) => /^\\d{4}-\\d{2}-\\d{2}$/.test(d)).sort().reverse() } catch { continue }\n outer: for (const day of days) {\n const dayDir = resolve(agentPath, day)\n let files: string[]\n try { files = readdirSync(dayDir).filter((f) => f.endsWith(\".json\")).sort().reverse() } catch { continue }\n for (const f of files) {\n try {\n const rec = JSON.parse(readFileSync(resolve(dayDir, f), \"utf-8\")) as TaskRecord\n const raw = (rec.responseText || rec.error || \"\").trim()\n if (!raw) continue\n const firstLine = raw.split(/\\r?\\n/)[0].slice(0, 140)\n this.lastSummaries.set(rec.agentId, { text: firstLine, at: new Date(rec.endedAt), ok: rec.ok })\n loaded++\n break outer\n } catch { /* skip corrupt file */ }\n }\n }\n }\n return loaded\n }\n\n /**\n * Drop history folders older than retention window. Cheap startup-time\n * sweep — once a day is plenty, but doing it on every daemon boot is fine.\n */\n pruneTaskHistory(retentionDays = TASK_HISTORY_RETENTION_DAYS): number {\n const root = resolve(process.cwd(), TASK_HISTORY_DIR)\n if (!existsSync(root)) return 0\n const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000\n let removed = 0\n for (const agentDir of readdirSync(root)) {\n const agentPath = resolve(root, agentDir)\n let dayEntries: string[]\n try { dayEntries = readdirSync(agentPath) } catch { continue }\n for (const day of dayEntries) {\n const ts = Date.parse(day + \"T00:00:00Z\")\n if (Number.isNaN(ts) || ts >= cutoff) continue\n try { rmSync(resolve(agentPath, day), { recursive: true, force: true }); removed++ } catch { /* */ }\n }\n }\n return removed\n }\n\n /**\n * Newest-first list of task records for one agent, capped at `limit`.\n * Walks at most the last few day folders so this is O(limit), not O(history).\n */\n listTaskHistory(agentId: string, limit = 50): Array<Omit<TaskRecord, \"transcript\" | \"responseText\">> {\n const root = resolve(process.cwd(), TASK_HISTORY_DIR, this.safe(agentId))\n if (!existsSync(root)) return []\n const days = readdirSync(root).filter((d) => /^\\d{4}-\\d{2}-\\d{2}$/.test(d)).sort().reverse()\n const out: Array<Omit<TaskRecord, \"transcript\" | \"responseText\">> = []\n for (const day of days) {\n if (out.length >= limit) break\n const dayDir = resolve(root, day)\n let files: string[]\n try { files = readdirSync(dayDir).filter((f) => f.endsWith(\".json\")) } catch { continue }\n // Sort by file mtime desc — task ids are timestamp-prefixed so lexicographic also works.\n files.sort().reverse()\n for (const f of files) {\n if (out.length >= limit) break\n try {\n const rec = JSON.parse(readFileSync(resolve(dayDir, f), \"utf-8\")) as TaskRecord\n const { transcript: _t, responseText: _r, ...summary } = rec\n out.push(summary)\n } catch { /* skip corrupt */ }\n }\n }\n return out\n }\n\n /**\n * Look up one task's full record (transcript included) across the retention window.\n */\n getTaskRecord(agentId: string, taskId: string): TaskRecord | null {\n const root = resolve(process.cwd(), TASK_HISTORY_DIR, this.safe(agentId))\n if (!existsSync(root)) return null\n const safeId = this.safe(taskId)\n const days = readdirSync(root).filter((d) => /^\\d{4}-\\d{2}-\\d{2}$/.test(d)).sort().reverse()\n for (const day of days) {\n const file = resolve(root, day, `${safeId}.json`)\n if (existsSync(file)) {\n try { return JSON.parse(readFileSync(file, \"utf-8\")) as TaskRecord } catch { return null }\n }\n }\n return null\n }\n\n private safe(s: string): string {\n return s.replace(/[^a-zA-Z0-9_.:-]/g, \"_\")\n }\n\n /**\n * Post a queued message's response back to its originating channel.\n * Used by the flush block in `execute()` — channel messages that arrive\n * while the agent is busy are queued and re-dispatched here, bypassing\n * the router; without this helper the agent's reply would never reach\n * the user. Skipped silently for internal channels (`test`, `api`,\n * `a2a`, `cron`, `workflow`, …) where no adapter is registered.\n */\n private postQueuedResponseToChannel(\n agentId: string,\n ctx: AgentTask[\"context\"],\n resp: AgentResponse,\n ): void {\n const router = getMessageRouter()\n if (!router) return\n const channel = ctx?.channel\n const chatId = ctx?.chatId\n if (!channel || !chatId) return\n // Operator-cancelled / queued-marker — nothing to deliver\n if (resp.errorKind === \"cancelled\" || resp.errorKind === \"interrupted\" || resp.errorKind === \"stopped\") return\n if (isQueued(resp.error)) return\n const text = resp.error\n ? `Error: ${resp.error}`\n : (resp.content || \"\").trim()\n if (!text) return\n // Resolve account binding for shared-channel adapters (Telegram is\n // multi-account); sendOutbound auto-fills accountId from agentId.\n router.sendOutbound({ channel, chatId, text, agentId } as any)\n .catch((e: any) => this.log(`[${agentId}] queued response post failed: ${e?.message ?? e}`))\n }\n\n /** The agent running `taskId` right now, or null once it has finished.\n * Lets an HTTP caller that names its task prove which agent it is. */\n runningTaskOwner(taskId: string): { agentId: string; channel: string; chatId?: string } | null {\n for (const s of this.agents.values()) {\n const t = s.runningTasks.find((r) => r.id === taskId)\n if (t) return { agentId: s.id, channel: t.channel, chatId: t.chatId }\n }\n return null\n }\n\n /** A running turn of `agentId` and the context it started with, for\n * A2A delegation (#277). With `taskId` it must be that run; with a\n * channel and chat it must be the only run on that chat. With neither\n * there is no answer: guessing \"the agent's only turn\" could attach a\n * same-host proxy's request to an unrelated conversation. */\n findRunningTurn(\n agentId: string,\n by: { taskId?: string; channel?: string; chatId?: string } = {},\n ): { taskId: string; context: NonNullable<AgentTask[\"context\"]>; autonomy?: AgentTask[\"autonomy\"]; workflowRunId?: string } | null {\n const state = this.agents.get(agentId)\n if (!state) return null\n let run: RunningTask | undefined\n if (by.taskId) {\n run = state.runningTasks.find((r) => r.id === by.taskId)\n } else if (by.channel && by.chatId) {\n // Compared as a warm process names it, so a run with no chat is\n // found by the fallback pair its process sends (#410).\n const matches = state.runningTasks.filter((r) => {\n const chat = warmProcessChat(r)\n return chat.channel === by.channel && chat.chatId === by.chatId\n })\n run = matches.length === 1 ? matches[0] : undefined\n }\n if (!run) return null\n const context = this.runningContexts.get(run.id)\n return context ? { taskId: run.id, context, ...(run.autonomy ? { autonomy: run.autonomy } : {}), ...(run.workflow ? { workflowRunId: run.workflow.runId } : {}) } : null\n }\n\n /** Whether every slot of `agentId` is taken, and by which runs. Lets a\n * synchronous delegation refuse at once when the only slot is held by\n * the turn waiting on it (#277), instead of waiting 25 minutes. */\n slotHolders(agentId: string): { full: boolean; runIds: string[] } | null {\n const state = this.agents.get(agentId)\n if (!state) return null\n return { full: state.activeTasks >= state.def.maxConcurrent, runIds: state.runningTasks.map((r) => r.id) }\n }\n\n /** True while `agentId` has a turn running on this chat. */\n isChatBusy(agentId: string, channel: string, chatId: string): boolean {\n return this.messageQueue.isBusy(agentId, channel, chatId)\n }\n\n /**\n * The intent path of the turn running right now for this conversation,\n * or undefined while it is not classified yet (or nothing is running).\n * Tools launched by that turn ask for it through GET /agents/:id/intent-path;\n * reading the classification log instead would hand them the previous\n * request's path, since this turn's classification may still be in flight.\n */\n runningIntentPath(agentId: string, channel: string, chatId: string): string[] | undefined {\n const state = this.agents.get(agentId)\n if (!state) return undefined\n let latest: RunningTask | undefined\n for (const r of state.runningTasks) {\n if (r.channel !== channel || r.chatId !== chatId) continue\n if (!latest || r.startedAt > latest.startedAt) latest = r\n }\n return latest?.intentPath?.slice()\n }\n\n /**\n * Operator stop. Aborts the in-flight run for `taskId` (kills the\n * underlying claude subprocess via the AbortSignal threaded through\n * runtime.ts). Returns the task's identity for HTTP / audit callers,\n * or null when no live run matches.\n */\n cancelRunningTask(taskId: string, reason = \"operator\"): { agentId: string; channel: string; chatId: string } | null {\n const entry = this.taskAborts.get(taskId)\n if (!entry) return null\n try {\n entry.controller.abort(new Error(reason))\n } catch { /* AbortController.abort never throws on modern Node, but defend */ }\n this.log(`[${entry.agentId}] task ${taskId} cancelled — ${reason}`)\n return { agentId: entry.agentId, channel: entry.channel, chatId: entry.chatId }\n }\n\n /**\n * Stop signal (#857). Aborts the run like an operator stop, but it ends\n * with status `stopped` and errorKind \"stopped\" instead of cancelled, so\n * nothing reports it as a failure. False when no live run matches.\n */\n stopRunningTask(taskId: string, reason: string): boolean {\n const entry = this.taskAborts.get(taskId)\n if (!entry || entry.controller.signal.aborted) return false\n this.stoppedRuns.set(taskId, reason)\n try { entry.controller.abort(new Error(reason)) } catch { /* */ }\n this.log(`[${entry.agentId}] task ${taskId} stopped — ${reason}`)\n return true\n }\n\n /** Resolves once the run `taskId` has let go of its slot (at once when it\n * is not running), or after `timeoutMs`. */\n whenRunEnds(taskId: string, timeoutMs: number): Promise<void> {\n if (!this.taskAborts.has(taskId)) return Promise.resolve()\n return new Promise((resolveEnd) => {\n const timer = setTimeout(resolveEnd, timeoutMs)\n timer.unref?.()\n const list = this.runEndWaiters.get(taskId) ?? []\n list.push(() => { clearTimeout(timer); resolveEnd() })\n this.runEndWaiters.set(taskId, list)\n })\n }\n\n /** A running task a stop signal can address: by its id, or the only run\n * of `agentId` on a chat. Null when none (or several) match. */\n signalTarget(by: { taskId?: string; agentId?: string; channel?: string; chatId?: string }): {\n taskId: string; traceId?: string; agentId: string; channel: string; chatId: string\n sender?: string; rootId?: string; originalMessage: string; origin: RunOrigin | null; workflowRunId?: string\n } | null {\n let id: string | undefined\n if (by.taskId) {\n id = this.taskAborts.has(by.taskId) ? by.taskId : undefined\n } else if (by.agentId && by.channel && by.chatId) {\n const ids = [...this.taskAborts].filter(([, e]) => e.agentId === by.agentId && e.channel === by.channel && e.chatId === by.chatId).map(([k]) => k)\n id = ids.length === 1 ? ids[0] : undefined\n }\n const e = id ? this.taskAborts.get(id) : undefined\n if (!id || !e) return null\n return {\n taskId: id, traceId: e.traceId, agentId: e.agentId, channel: e.channel, chatId: e.chatId,\n sender: e.sender, rootId: e.rootId, originalMessage: e.originalMessage, origin: e.origin ?? null,\n ...(e.workflowRunId ? { workflowRunId: e.workflowRunId } : {}),\n }\n }\n\n /**\n * Daemon shutdown: stop every run still going once the drain limit is\n * spent. Each one ends as `interrupted` with `reason` rather than as a\n * failure of its own, and its trace stays in flight for resume on boot.\n * Returns how many runs it stopped.\n */\n interruptRunning(reason: string): number {\n let n = 0\n for (const [taskId, e] of this.taskAborts) {\n this.interruptedRuns.set(taskId, reason)\n try { e.controller.abort(new Error(reason)) } catch { /* */ }\n this.log(`[${e.agentId}] task ${taskId} interrupted — ${reason}`)\n n++\n }\n return n\n }\n\n /** Stop every in-flight run of `agentId` in one chat. Used when a streaming\n * caller disconnects (the phone app's Stop). Returns how many it aborted. */\n cancelChatTasks(agentId: string, channel: string, chatId: string, reason = \"operator\"): number {\n let n = 0\n for (const [taskId, e] of this.taskAborts) {\n if (e.agentId === agentId && e.channel === channel && e.chatId === chatId && this.cancelRunningTask(taskId, reason)) n++\n }\n return n\n }\n\n /**\n * Queue a follow-up correction/update for an in-flight task. The message\n * lands in the per-session MessageQueue and is dispatched after the\n * current run finishes — same path channel messages take while the agent\n * is busy. With `replace=true` we cancel the current run first so the\n * follow-up runs immediately.\n *\n * Returns the resolved identity + queue position, or null when no live\n * run matches taskId.\n */\n queueFollowUp(\n taskId: string,\n message: string,\n sender: string,\n opts: { replace?: boolean; reason?: string } = {},\n ): { agentId: string; channel: string; chatId: string; replaced: boolean; edited: boolean; pending: number } | null {\n const entry = this.taskAborts.get(taskId)\n if (!entry) return null\n const { agentId, channel, chatId, originalMessage } = entry\n\n // Inject model: Update appends a message to the same chat session;\n // the running task keeps going and the new message dispatches at the\n // first chance — same as sending a message to an ongoing Claude\n // session. Update never *initiates* a cancel (use Stop for that).\n //\n // BUT — if the controller is already aborted (operator pressed Stop\n // just before Update), the cancelled user turn is sitting in session\n // history with no agent reply. Leaving it there makes the Update\n // read as a bare orphan ask (\"87\" after a stranded \"count up to 75\"\n // → agent had no idea what \"87\" meant). So when we detect the\n // aborted state, we treat the Update as editing the cancelled\n // message in place: pop the orphan from session history before\n // enqueueing the new message.\n const isStopped = entry.controller.signal.aborted\n let edited = false\n if (isStopped && originalMessage) {\n edited = this.sessions.removeLastUserMessageIfMatches(agentId, channel, chatId, originalMessage)\n }\n\n const queuedAt = Date.now()\n this.messageQueue.enqueue(agentId, channel, chatId, {\n text: message,\n sender,\n timestamp: queuedAt,\n channel,\n chatId,\n originalContext: { channel, chatId, sender },\n })\n const pending = this.messageQueue.pendingCount(agentId, channel, chatId)\n // It waits in the same line as a channel message, so the member page\n // counts it among the messages ahead of a teammate's (#443). Sent from\n // the owner's dashboard, it is the owner's; not a human-facing root,\n // so request status leaves it alone.\n getEventBus().emit(\"task:queued\", {\n agentId, channel, chatId, at: new Date(queuedAt).toISOString(),\n sender: { name: sender }, humanRoot: false,\n person: operatorPerson(this.config.people)?.id ?? null,\n messagePreview: message.slice(0, 200), queuedAt,\n })\n\n let replaced = false\n if (opts.replace) {\n const reason = opts.reason || `replaced by follow-up from ${sender}`\n try { entry.controller.abort(new Error(reason)) } catch { /* */ }\n this.log(`[${agentId}] task ${taskId} replaced by follow-up — ${reason}`)\n replaced = true\n } else {\n this.log(`[${agentId}] follow-up queued for task ${taskId} (pending=${pending}${edited ? \", edited cancelled turn\" : \"\"})`)\n }\n return { agentId, channel, chatId, replaced, edited, pending }\n }\n\n /**\n * Operator follow-up on a task that has already finished: dispatch the\n * message as a new turn on the same (channel, chatId), so the session\n * resumes with the finished run's context.\n *\n * Scheduled runs only. A cron chat has no person on the other end, so the\n * Task page is the only place its reply is read. Resuming a Telegram or\n * GitLab conversation from here would run a turn whose reply never reaches\n * the person in that chat, which is worse than refusing.\n *\n * Resolves once the new run starts (with its task id, for the Task page)\n * or, if the message was queued or answered without a slot, once execute\n * returns without one.\n */\n async continueFinishedTask(\n agentId: string,\n taskId: string,\n message: string,\n sender: string,\n opts: { model?: (chatId: string) => string | undefined } = {},\n ): Promise<ContinueFinishedTaskResult> {\n const record = this.getTaskRecord(agentId, taskId)\n if (!record) return { ok: false, status: 404, error: `no task ${taskId} for agent ${agentId}` }\n if (record.channel !== \"cron\" || !record.chatId) {\n return { ok: false, status: 409, error: `task ${taskId} is finished; only scheduled (cron) runs can be resumed from the dashboard` }\n }\n const channel = record.channel\n const chatId = record.chatId\n return new Promise<ContinueFinishedTaskResult>((resolveStart) => {\n let settled = false\n const settle = (result: ContinueFinishedTaskResult) => {\n if (settled) return\n settled = true\n resolveStart(result)\n }\n this.log(`[${agentId}] operator resumed ${channel}:${chatId} from finished task ${taskId}`)\n this.execute({\n message,\n agentId,\n // The job's model override, so the resumed turn runs on the same\n // model as the scheduled one did.\n model: opts.model?.(chatId),\n context: { channel, chatId, sender },\n onStart: (id) => settle({ ok: true, agentId, channel, chatId, taskId: id, queued: false }),\n })\n .then((resp) => {\n // Only reached first when the run never took a slot: it was queued\n // behind a busy slot (the flush dispatches it later), answered\n // without one (attached session, mesh forward), or refused.\n if (isQueued(resp.error)) {\n settle({ ok: true, agentId, channel, chatId, queued: true })\n } else if (!resp.error) {\n // Delivered and answered. Reporting a failure here would invite a\n // duplicate Send.\n settle({ ok: true, agentId, channel, chatId, queued: false, answeredBy: resp.viaAttachedSession ? \"attached\" : \"other\" })\n } else {\n settle({ ok: false, status: 500, error: resp.error || \"run did not start\" })\n }\n })\n .catch((e) => settle({ ok: false, status: 500, error: e?.message ?? String(e) }))\n })\n }\n\n /**\n * Subscribe to live output for a running task. Returns the buffer that has\n * accumulated so far plus an unsubscribe handle. If the task is unknown,\n * returns null. If the task already finished, the subscriber is given the\n * tail buffer and immediately ended.\n */\n subscribeToTaskOutput(taskId: string, sub: TaskOutputSubscriber): { initial: string; done: boolean; unsubscribe: () => void } | null {\n const out = this.taskOutputs.get(taskId)\n if (!out) return null\n if (out.done) return { initial: out.buffer, done: true, unsubscribe: () => {} }\n out.subscribers.add(sub)\n return {\n initial: out.buffer,\n done: false,\n unsubscribe: () => { out.subscribers.delete(sub) },\n }\n }\n\n /**\n * Get token usage summary.\n */\n getUsage(days: number = 7) {\n return this.tokenTracker.summary(days)\n }\n\n /**\n * Get today's usage.\n */\n getTodayUsage() {\n return this.tokenTracker.today()\n }\n\n /**\n * Get the token tracker instance (for daemon hooks).\n */\n getTokenTracker(): TokenTracker {\n return this.tokenTracker\n }\n}\n\n/** The rich-reply convention for an agent's system prompt: only on the\n * interactive chat channels, and only when the agent hasn't opted out.\n * Quick replies are offered in the phone app alone, which renders them. */\nexport function richReplyPrompt(richMessages: boolean | undefined, channel: string | undefined): string {\n if (richMessages === false || (channel !== \"telegram\" && channel !== \"whatsapp\" && channel !== \"app\")) return \"\"\n return [\n \"[Rich replies]\",\n \"On this chat channel you may add buttons, a poll, or media to a reply by appending ONE fenced block at the very end:\",\n \"```agentx:ui\",\n '{ \"buttons\": [{\"label\": \"Open docs\", \"url\": \"https://...\"}], \"poll\": {\"question\": \"Ship it?\", \"options\": [\"Yes\", \"No\"]}, \"media\": {\"type\": \"image\", \"url\": \"https://...\"} }',\n \"```\",\n \"All fields are optional; include only what helps. Buttons must be https URLs (tappable callback actions aren't supported yet). Use this sparingly — only when a link, choice, or image genuinely improves the reply. The block is stripped from the visible text.\",\n ...(channel === \"app\"\n ? ['In this phone app you may also offer up to 4 short answers the user can tap instead of typing: \"quickReplies\": [\"Yes\", \"Not now\"] (at most 40 characters each), or a button with a reply instead of a url: {\"label\": \"Run the tests\", \"reply\": \"Please run the tests\"}. A tap sends that text as the user\\'s next message, nothing more. Offer them only when the user is likely to answer with one of them.']\n : []),\n ].join(\"\\n\")\n}\n","import { noul } from \"../questions\"\nimport type { AnswersFor, NoulAnswer, StateValue } from \"../types\"\n\n// The session-continuity seat.\n//\n// Every production agent is claude-code tier, which means `claude --resume`\n// replays its own transcript on every turn and agentx never owns the\n// messages array. The one lever agentx does own is WHEN to stop resuming.\n// That is what drives the cache-read bill: devops-agent reads 186M cached\n// tokens a week at a 60:1 read-to-write ratio, and every one of those reads\n// is a turn re-reading a transcript agentx chose to keep alive.\n//\n// Today three hand-tuned mechanical triggers decide it: turn count, last\n// turn's context size, and 12 hours of silence. None of them knows whether\n// the conversation is actually continuous. A new task that changed the\n// subject entirely still resumes a 150K-token transcript, because it\n// arrived 40 minutes after the last one.\n//\n// Those thresholds were not derived, they were tuned after an incident —\n// sessions rotating on cumulative usage (3.3M vs 195K) read to users as\n// the agent developing amnesia mid-task. That is the signature of a number\n// picked under pressure and never measured since.\n//\n// A WARNING ABOUT THIS SEAT SPECIFICALLY.\n//\n// Its two sides are not equally measurable. The cost of keeping a session\n// alive is exact — it is in lastTurnContextTokens. The cost of killing one\n// too early is amnesia, which shows up as a confused user, not a metric.\n// Optimising only the side you can measure is how the original incident\n// happened. So:\n//\n// - the seat may only recommend rotating EARLY, never skipping a\n// mechanical rotation. The safety triggers stay authoritative.\n// - `needsHistory` is the labelled question, not `continues`. Whether\n// the reply actually depended on earlier context is the thing that\n// hurts when wrong, and it is the thing a human can judge.\n//\n// Ground truth here is weaker than the monitor pre-filter's, which derives\n// its labels for free. Expect to label these by hand, lowest-confidence\n// first, and do not promote this seat on the cost number alone.\n\nexport const SESSION_CONTINUITY_SEAT = \"session-continuity\"\n\n// One clause each, in the state's own vocabulary — see the note on phrasing\n// in monitor-prefilter.ts. These two are deliberately close but not\n// identical: a request can change subject while still depending on earlier\n// context (\"forget the deploy, what was that token you mentioned?\"), and\n// rotating on that one is precisely the amnesia case.\nexport const sessionContinuityQuestions = {\n // `continues` states the boundary in criteria rather than leaving it to\n // the phrase \"same piece of work\".\n //\n // It used to ask \"Is the new request part of the same piece of work as\n // the previous request?\" with no criteria — the vague-definition shape\n // TypeSafe's Noul page warns about by example (\"Is the candidate strong\n // in Python?\"). With no stated boundary the probability had nothing\n // stable to mean, and it showed: across four resampled runs of the 27\n // labelled bench turns, the two populations sat the WRONG way round\n // (worst-case margin about -0.12) while `needsHistory` — which asks a\n // referential question with a definite answer — separated.\n //\n // Supplying the boundary through criteria, which the docs call for\n // exactly when \"the boundary between yes and no is nuanced\", caught 6 of\n // 7 subject changes against 4 of 7, in every run, with no rotation on a\n // turn that needed earlier context.\n //\n // It did NOT make `continues` cleanly separable — the margin moves to\n // roughly zero, not comfortably positive. This is a better question, not\n // a solved one, and it is still the weaker of the two.\n continues: noul(\n \"The new request continues the task the user was already working on in the previous request, rather than starting a separate one.\",\n {\n true:\n 'The new request advances, narrows, corrects or follows up on the previous request\\'s task — including terse replies such as \"and the cost?\" or \"why?\" that mean nothing on their own.',\n false:\n \"The new request opens a subject that could be answered without having seen the previous request, even if it arrived seconds later or belongs to the same broad field.\",\n },\n ),\n // Left alone deliberately. It already separates, and changing both\n // questions at once would have produced a number nothing could be\n // attributed to.\n needsHistory: noul(\"Does the new request refer to something stated earlier in the conversation?\"),\n}\n\nexport type SessionContinuityAnswers = AnswersFor<typeof sessionContinuityQuestions>\n\nexport interface ContinuityInput {\n /** The request about to be dispatched. */\n message: string\n /** The previous user message in this chat, if any. */\n previousMessage?: string | null\n /** Requests BEFORE `previousMessage`, oldest first.\n *\n * Empty by default. The benchmark showed why the option exists: on\n * subject changes with no lexical signpost, two messages alone left the\n * model genuinely unsure (`continues` 0.58 where a clean change scored\n * 0.22), and no policy threshold recovers a number that never separated.\n * A subject boundary is visible against a run of requests in a way it is\n * not against one predecessor.\n *\n * Bounded and off unless asked for, because this runs before every\n * resumed turn and the whole point of the seat is to be cheaper than\n * what it guards. */\n recentRequests?: string[]\n minutesSinceLastTurn: number | null\n turnCount: number\n lastTurnContextTokens: number | null\n agentId: string\n channel: string\n}\n\n/** Small on purpose. Deciding whether two requests are the same piece of\n * work needs the two requests, not the transcript between them — and this\n * runs before a task that has not started yet, so it must be cheap.\n *\n * `recentRequests` widens that deliberately narrow window, and stays\n * absent from the state entirely when empty so the default call is\n * byte-identical to what it always was. */\nexport function continuityState(input: ContinuityInput): StateValue {\n const earlier = (input.recentRequests ?? []).filter(Boolean)\n return {\n newRequest: clip(input.message, 1200),\n previousRequest: input.previousMessage ? clip(input.previousMessage, 800) : null,\n ...(earlier.length\n ? { earlierRequests: earlier.map((r) => clip(r, 400)) }\n : {}),\n minutesSinceLastTurn: input.minutesSinceLastTurn,\n turnsSoFar: input.turnCount,\n currentContextTokens: input.lastTurnContextTokens,\n agent: input.agentId,\n channel: input.channel,\n }\n}\n\nexport interface RotatePolicy {\n /** Both nouls must sit at or below this to rotate early. */\n maxContinuity?: number\n /** True when a mechanical trigger already fired. The seat must not\n * override those — it can only act when they did NOT fire. */\n mechanicalRotation: boolean\n}\n\n/**\n * Should this session rotate EARLY, before any mechanical trigger?\n *\n * Deliberately one-directional. When a threshold has already decided to\n * rotate, this returns false and the threshold wins: the seat can save\n * money by rotating sooner, never by keeping a session alive that the\n * safety rules wanted dead.\n *\n * Both questions must agree. They are correlated but not identical — a\n * request can change subject while still needing earlier context (\"ignore\n * the deploy, what was that token you mentioned?\"), and rotating on that\n * one is exactly the amnesia case.\n */\nexport function shouldRotateEarly(\n answers: SessionContinuityAnswers,\n policy: RotatePolicy,\n): boolean {\n if (policy.mechanicalRotation) return false\n\n const maxContinuity = policy.maxContinuity ?? 0.2\n\n const continues = (answers.continues as NoulAnswer).noul\n const needsHistory = (answers.needsHistory as NoulAnswer).noul\n\n // Threshold the probabilities directly. There is no second gate, and\n // there was never anything for one to read.\n //\n // This used to also require a \"confidence\" derived as |noul - 0.5| * 2.\n // A Noul has no confidence: TypeSafe's own primitive documentation says\n // it \"does not return a separate confidence value\" and warns that a\n // value near 0.5 does not mean medium anything — the probability IS the\n // answer, and the documented way to act on one is to threshold it.\n // types.ts has said the same thing about NoulAnswer since it was\n // written; the gate here contradicted it.\n //\n // It was not merely redundant, it silently halved the threshold. Both\n // nouls at or below 0.2 already forces the derived value to 0.6 or\n // more, so a floor of 0.8 was really the demand that both sit at or\n // below 0.1 — the seat enforced 0.1 while its own docstring, its\n // config and every discussion of it said 0.2. A benchmark sweep found\n // the floor binding on every cell and read it as a tuning knob. It was\n // a misuse of the primitive wearing a knob's clothes.\n return continues <= maxContinuity && needsHistory <= maxContinuity\n}\n\nfunction clip(value: string, max: number): string {\n const text = String(value ?? \"\")\n return text.length > max ? text.slice(0, max) : text\n}\n","import { openCodeProcessPool, OpenCodeServerUnavailable } from \"./opencode-process\"\nimport { execa } from \"execa\"\nimport { codexProcessPool, CodexUnavailable } from \"./codex-process\"\nimport { execFile, spawn } from \"child_process\"\nimport { StringDecoder } from \"string_decoder\"\nimport { mkdtempSync, readFileSync, rmSync } from \"fs\"\nimport { join } from \"path\"\nimport { tmpdir } from \"os\"\nimport { friendlyModelError, renderFriendlyError, type FriendlyError } from \"./error-map\"\nimport { primaryModelFromUsage } from \"./model-usage\"\n\n/** An exit 143 this close to the time limit is the limit's own SIGTERM. */\nconst SIGTERM_TIMEOUT_SLACK_MS = 5_000\n\n/** Small helper: render a raw error string into both the operator-facing\n * one-line message and the typed kind discriminator. Call sites that hit\n * `renderFriendlyError(friendlyModelError(...))` should use this so the\n * kind survives onto the response (see AgentResponse.errorKind). */\nfunction buildErrorEnvelope(raw: string | undefined | null): { error: string; errorKind: FriendlyError[\"kind\"] } {\n const friendly = friendlyModelError(raw)\n if (friendly.kind === \"invalid_request\" || friendly.kind === \"unknown\") {\n const sample = (raw || \"\").replace(/\\s+/g, \" \").slice(0, 800)\n if (sample) process.stderr.write(`[agentx][model-error][${friendly.kind}] raw=${sample}\\n`)\n }\n return { error: renderFriendlyError(friendly), errorKind: friendly.kind }\n}\nimport { buildAgentEnv, claudeBillingEnv } from \"@/utils/workspace-env\"\nimport type { AgentDef } from \"@/daemon/config\"\nimport type { SeededMessage } from \"@/channels/types\"\nimport { getProcessRegistry } from \"./process-registry-instance\"\nimport { RegistryCapExceeded, type ProcessKey } from \"./process-registry\"\nimport { TurnDeadlineExceeded, TurnInterrupted } from \"./claude-process-factory\"\nimport { effectiveMcpConfig } from \"./codegraph-bootstrap\"\nimport { autonomyBrief, isRestricted, type AutonomyLevel } from \"@/guard/autonomy\"\nimport { autonomyClaudeArgs, autonomyUnsupported, takeAutonomyBlocks, type AutonomyBlock } from \"@/guard/autonomy-enforce\"\nimport { clearPersonLimits, personLimitsUnsupported, registerPersonLimits, withPersonLimitHook } from \"@/guard/person-limits\"\nimport type { PersonLimits } from \"@/people/people\"\n\n// --- Agent execution runtime ---\n// Routes agent tasks to the correct execution tier:\n// - claude-code: spawns claude CLI (subscription, full features)\n// - codex-cli: spawns codex CLI (OpenAI Codex, full CLI agent)\n// - opencode: spawns OpenCode CLI (uses the operator's configured providers)\n// - sdk: uses Claude Agent SDK (API key, programmatic)\n// - orchestrator: uses agentx's own agentic loop (any provider)\n\n/** Message shown to operators when the `claude` CLI isn't on PATH. Hits both\n * the streaming and non-streaming paths — without this we used to surface\n * \"Claude Code exited with code unknown\" / \"code ENOENT\", neither of which\n * hints at the actual fix. */\nfunction claudeMissingMessage(): string {\n return (\n `Claude Code CLI not found on PATH. Install it before starting an agent: ` +\n `https://docs.claude.com/en/docs/claude-code . On Linux/macOS: npm i -g @anthropic-ai/claude-code ` +\n `then verify with claude --version. If you don't intend to use the claude-code engine, set the agent's tier to \"sdk\" or \"orchestrator\" in agentx.json.`\n )\n}\n\nfunction codexMissingMessage(): string {\n return (\n `Codex CLI not found on PATH. Install it before starting an agent: ` +\n `npm i -g @openai/codex then verify with codex --version. ` +\n `If you don't intend to use the codex-cli engine, set the agent's tier to \"claude-code\", \"sdk\", or \"orchestrator\" in agentx.json.`\n )\n}\n\nfunction openCodeMissingMessage(): string {\n return (\n `OpenCode CLI not found on PATH. Install it from https://opencode.ai/docs/ and verify with opencode --version. ` +\n `If you don't intend to use the opencode engine, set the agent's tier to \"claude-code\", \"codex-cli\", \"sdk\", or \"orchestrator\" in agentx.json.`\n )\n}\n\nexport interface AgentPeer {\n id: string\n name: string\n handle?: string // e.g. \"@my_bot\"\n role?: string // from systemPrompt, first line\n}\n\nexport interface AgentTask {\n message: string\n agentId: string\n /** Set by the queue flush: when this message was first queued. The run\n * prepends the stale-state note from it when it starts, and a flushed\n * message that has to queue again keeps it, so the note is added once\n * and reports the whole wait (#282). */\n queuedAt?: number\n /** Correlator threaded by the workflow engine. When the engine dispatches\n * an agent as part of a state transition, this carries the run id so\n * post:response can re-enter the engine with an agentResult condition. */\n workflowRunId?: string\n /** Per-invocation model override (e.g. cron model). Falls back to agent.model. */\n model?: string\n /** What the person typed, when `message` wraps it in a prompt of the\n * caller's own. The requests record keeps this text (#401). */\n requestText?: string\n /** Cacheable text delivered to Claude via --append-system-prompt. Typically\n * agent.systemPrompt + SOUL/IDENTITY/AGENTS.md. Passing stable content\n * here (instead of in the user-message body) keeps it inside Claude's\n * cached system prompt across session resumes. */\n systemPromptAppend?: string\n /** Per-invocation override for the context assembly strategy. When unset,\n * the registry falls back to config.session.contextStrategy. Set this\n * from benchmark harnesses that want to A/B the same request under\n * \"layered\" and \"planner\" without reloading daemon config. */\n contextStrategy?: \"layered\" | \"planner\"\n /** Upper bound on how long this task may run. Applied to mesh\n * forwarding (as fetch timeout) and local execution when the runtime\n * supports it. Workflow `agent` nodes pass this through from their\n * config.timeoutMinutes. */\n timeoutMinutes?: number\n /** Phase 1 / 6 — when set, identifies the intent-ledger decision row\n * this task corresponds to. The registry records a resolution row\n * on this decision when the task completes (success / error / mesh-\n * fallback). Active-task safety in `decideAndCommit` reads\n * intent_resolutions to clear in-flight slots; without resolution\n * writes, every dispatched decision sits in-flight forever and\n * Inv-ActiveTaskSafety becomes vacuously over-aggressive. Channel\n * adapters (gitlab, router) set this when their record*Dispatch\n * helper returns a dispatched decision. */\n intentRef?: {\n eventId: string\n decidedBy: string\n }\n /** ULID for the per-execution trace row in task_traces (improvement\n * plan #2). Allocated by registry.execute right before emitting the\n * task:started bus event so per-step capture sites in the streaming\n * parser can append rows under the same id. Optional — when unset,\n * step capture is a no-op. */\n taskId?: string\n /** The id the dashboard knows this run by (RunningTask.id — live stream,\n * cancel, follow-up, persisted TaskRecord). Set by registry.execute once\n * the run holds a slot; distinct from `taskId`, which is the trace id.\n * Callers that dispatch without an HTTP stream (cron) read it back to\n * link the run to the Task page. */\n runningTaskId?: string\n /** Called with `runningTaskId` as soon as the run starts. Never fires for\n * a message that was queued or dropped instead of run. */\n onStart?: (runningTaskId: string) => void\n /** Aborted when the HTTP caller waiting on this run has gone (timeout,\n * disconnect). A run still waiting for a busy agent's slot is then\n * dropped instead of started: nobody is left to read its answer, and a\n * retrying caller would otherwise have every attempt run once the slot\n * frees (#822). A run that already started is not affected. */\n callerSignal?: AbortSignal\n /** How to re-enter this run if a restart cuts it off (agents/resume).\n * Absent: recorded as a direct run with this task's context. */\n origin?: import(\"./resume/origin\").RunOrigin\n /** Set on a run that resumes one cut off by a restart. */\n resumeAttempt?: number\n resumedFrom?: string\n /** Set on the one automatic retry of a run whose preparation steps hit\n * the pre-spawn deadline (#340). A retry that hits it again is reported. */\n preSpawnRetry?: number\n /** Improvement plan #8 — when true, the dispatcher discards any\n * cached session for this (agent, channel, chatId) before\n * executing: the claudeSessionId is cleared (no --resume) and\n * any persistent-process handle is killed so the next dispatch\n * spawns fresh. Use from triage→sub-agent delegation paths where\n * the caller knows this is a NEW visitor on a chatId that may\n * collide with prior conversations.\n *\n * Caught in benchmark Run 3 (2026-05-04): a triage agent\n * delegated to \"lead\" for a brand-new visitor; lead's warm\n * persistent process replied with the previous visitor's\n * confirmation message. freshSession: true defends against\n * that pattern without requiring callers to invent unique\n * chatIds. */\n freshSession?: boolean\n /** Transcript held by a client that owns the conversation (OpenCode).\n * Seeds an empty AgentX session the same way a channel adapter's\n * seedHistory does, so a new or rotated session starts with it. */\n seedHistory?: SeededMessage[]\n /** Extra flags for the `claude` CLI, set by the registry from the\n * channel's session profile (lean: strict MCP config, setting sources;\n * see agents/session-profile.ts). Spawn-per-task and persistent\n * processes carry them alike. Ignored by other tiers. */\n claudeArgs?: string[]\n /** Routine autonomy level (cron job / workflow agent step). `report` and\n * `propose` are enforced for THIS task only via a per-spawn guard hook\n * (see guard/autonomy-enforce.ts); unset or `act` = the agent's normal\n * permissions. Tiers that cannot enforce it refuse the task. */\n autonomy?: AutonomyLevel\n /** The tools and skills the person behind this turn may not use (people,\n * #379). Set by the registry; enforced for THIS task only by a per-spawn\n * guard hook (guard/person-limits.ts), the same way as `autonomy`. */\n personLimits?: PersonLimits\n context?: {\n channel?: string\n sender?: string\n /** Platform user ID (e.g. Telegram user ID) */\n senderId?: string\n /** Platform username (e.g. @username) */\n senderUsername?: string\n /** The known person who started this chain (people, #384). Set by the\n * registry when the turn starts; a value sent by a caller is replaced. */\n person?: string\n group?: string\n /** Stable chat ID for session keying (e.g. \"project:issue:123\" for GitLab) */\n chatId?: string\n /** Attached media file (image, audio, video, document) */\n mediaPath?: string\n mediaType?: string\n /** Text of the message being replied to */\n replyToText?: string\n conversationHistory?: Array<{ role: string; content: string }>\n /** This agent's own handle on the current channel */\n myHandle?: string\n /** Other available agents and their handles */\n peers?: AgentPeer[]\n /** Verified channel metadata (from adapter) */\n channelMeta?: {\n agents?: Array<{ id: string; name: string; handle?: string }>\n project?: string\n issue?: { type: string; iid: string; title: string }\n facts?: string[]\n eventActions?: string[]\n }\n /** Path to the project root whose runbook (CLAUDE.md / AGENTS.md / etc.)\n * should be injected into the agent's system prefix for this task.\n * Set by the gitlab/github adapters when a project rule resolves a\n * `runbook:` path. Optional. */\n runbookPath?: string\n /** Override list of files to read from runbookPath. Optional. */\n runbookFiles?: string[]\n /** Root of an agent-to-agent chain (a2a/initiator.ts). Set on every\n * delegated hop, forwarded across mesh peers with the context. */\n initiator?: import(\"@/a2a/initiator\").RootInitiator\n /** Set on a callback turn: the delegation whose result it carries. */\n delegation?: { taskId: string; from: string; peer?: string; status: string }\n }\n}\n\nexport interface AgentResponse {\n content: string\n error?: string\n /** Typed discriminator on `error`. Lets dispatchers and workflow nodes\n * branch on the failure cause without string-matching the friendly\n * message (which is operator-facing prose, not machine input). The\n * full FriendlyError taxonomy is in error-map.ts; common ones to\n * branch on: `out_of_credits`, `rate_limit`, `auth`, `timeout`. */\n errorKind?: FriendlyError[\"kind\"]\n tokensUsed?: number\n duration?: number\n claudeSessionId?: string\n codexSessionId?: string\n opencodeSessionId?: string\n usage?: TokenUsage // Real token counts from Claude's JSON output\n /** End-of-turn context size: the LAST API call's (input + cacheRead +\n * cacheCreate) inside this turn, captured from the final assistant\n * stream event. `usage` above is CUMULATIVE across every call in the\n * agentic loop (a tool-heavy turn easily sums to millions via cache\n * reads) — only this field reflects how full the conversation context\n * actually is, so rotation decisions must use it, never `usage`.\n * Undefined on non-streaming paths (result JSON has no per-call data). */\n contextTokens?: number\n /** Agentic-loop iterations the provider took inside THIS user turn, from\n * the CLI result event's `num_turns`. Distinct from a conversation turn:\n * one user message can cost many of these when the agent chains tools.\n * Benchmarks need it to tell \"JEV made the agent cheaper\" apart from\n * \"JEV made the agent do less work\". Undefined when the provider does\n * not report it. */\n numTurns?: number\n /** What the Claude Code CLI itself reported the turn cost, in USD at\n * list price (`total_cost_usd` on its result). Undefined on other tiers\n * and on the warm-process path. The benchmark that compares a task run\n * through AgentX with the same run on the bare CLI (#455) reads it, so\n * both sides are priced by the same meter. */\n costUsd?: number\n /** The model Claude actually billed for (from the CLI's init event). When\n * absent, cost reporting should fall back to the model override / agent\n * config. Knowing the billed model is what makes cache-aware pricing\n * trustworthy — a task that ran sonnet but was logged as opus would\n * overstate cost by ~5×. */\n billedModel?: string\n /** Set when the task was forwarded to a mesh peer that hosted the\n * requested agent (the local registry didn't have it). Name of the\n * peer that actually ran it. */\n viaMesh?: string\n /** Set when a live Claude Code session attached to this identity answered\n * the task instead of a spawned provider (attach mode). Claude Code\n * session id of the terminal that wrote the reply. */\n viaAttachedSession?: string\n /** Set when the task went to a Claude cloud session instead of a local\n * run (#622): the session's id and claude.ai/code URL. `followUp` marks\n * a comment forwarded to a session that was already open. The result of\n * the session is a pull request, so `content` is only the notice posted\n * on the issue. */\n cloudSession?: { id: string; url: string; followUp?: boolean }\n /** Set on tasks that ran under a restricted autonomy level. */\n autonomy?: AutonomyLevel\n /** Tool calls the autonomy guard blocked during this task — what the\n * routine would have done with more power. */\n autonomyBlocks?: AutonomyBlock[]\n}\n\n/** Callback for streaming text deltas */\nexport type StreamCallback = (delta: string, fullText: string) => void\n\n/**\n * Build the prompt from agent config + task context + conversation history.\n */\nfunction buildPrompt(agent: AgentDef, task: AgentTask, historyContext?: string): string {\n const parts: string[] = []\n\n // NOTE: agent.systemPrompt is no longer injected into the user-message body —\n // it's now delivered via `--append-system-prompt` so Claude's prompt cache\n // retains it across turns instead of us paying cache-create for it on every\n // new session. If a per-task override is needed, use task.context.systemPrompt\n // (which buildClaudeArgs also forwards to --append-system-prompt).\n\n // Inject environment context — skip if historyContext is provided (context engine handles it)\n if (task.context && !historyContext) {\n const ctx = task.context\n const envLines: string[] = [\"\", \"[Environment]\"]\n const isGitLab = ctx.channel === \"gitlab\" || ctx.channel?.startsWith(\"webhook:\")\n const isTelegram = ctx.channel === \"telegram\"\n\n if (ctx.channel) envLines.push(`Channel: ${ctx.channel}`)\n if (ctx.group) envLines.push(`Group: ${ctx.group}`)\n if (ctx.sender) envLines.push(`Message from: ${ctx.sender}`)\n if (ctx.myHandle) envLines.push(`Your handle on this channel: ${ctx.myHandle}`)\n\n if (isGitLab) {\n envLines.push(\"\")\n envLines.push(\"[IMPORTANT: You are responding to a GitLab comment/event]\")\n envLines.push(\"- Keep it short — 3-5 lines for the main message. Humans scan, not read\")\n envLines.push(\"- Lead with the result or action, not a narration of what you plan to do\")\n envLines.push(\"- Use <details><summary>Details</summary>\\\\n\\\\ncontent\\\\n</details> for verbose output (logs, full commands, step-by-step)\")\n envLines.push(\"- Do NOT mention Telegram handles — they don't work on GitLab\")\n envLines.push(\"- Do NOT try to delegate to other agents — reply directly\")\n envLines.push(\"- Reference issues with #IID and MRs with !IID\")\n }\n\n if (isTelegram && ctx.peers?.length) {\n envLines.push(\"\")\n envLines.push(\"[Team — other agents you can mention to delegate or collaborate]\")\n for (const peer of ctx.peers) {\n const handle = peer.handle ? ` (mention: ${peer.handle})` : \"\"\n const role = peer.role ? ` — ${peer.role}` : \"\"\n envLines.push(`• ${peer.name}${handle}${role}`)\n }\n envLines.push(\"\")\n envLines.push(\"To involve another agent, mention their handle in your response and they will automatically see it and reply.\")\n }\n\n parts.push(envLines.join(\"\\n\"))\n }\n\n // Reply-to context (when user replies to a specific message)\n if (task.context?.replyToText) {\n parts.push(\"\")\n parts.push(`[Replying to]: ${task.context.replyToText}`)\n }\n\n // Attached media\n if (task.context?.mediaPath) {\n parts.push(\"\")\n parts.push(`[Attached file: ${task.context.mediaPath}]`)\n parts.push(`[File type: ${task.context.mediaType || \"unknown\"}]`)\n if (task.context.mediaType?.startsWith(\"image/\")) {\n parts.push(\"Please read/view this image file and describe or respond to it.\")\n } else if (task.context.mediaType?.startsWith(\"audio/\")) {\n parts.push(\"Please transcribe this audio file and respond to its content.\")\n } else if (task.context.mediaType?.startsWith(\"video/\")) {\n parts.push(\"A video file is attached. Describe what you can determine about it.\")\n } else {\n parts.push(\"Please read this file and respond based on its content.\")\n }\n }\n\n // Inject conversation history for session continuity\n if (historyContext) {\n parts.push(\"\")\n parts.push(historyContext)\n }\n\n parts.push(\"\")\n parts.push(task.message)\n\n return parts.join(\"\\n\")\n}\n\n/**\n * Build CLI args for claude command.\n */\nfunction buildClaudeArgs(\n agent: AgentDef,\n prompt: string,\n streaming: boolean,\n resumeSessionId?: string,\n modelOverride?: string,\n /** Static per-agent preamble delivered via --append-system-prompt. Content\n * here is Claude-cached across turns as part of the system prompt, so\n * moving stable content (agent.systemPrompt + SOUL/IDENTITY/AGENTS.md) out\n * of the user-message body and into this arg avoids paying cache-create\n * for it on every new session. */\n systemPromptAppend?: string,\n /** Per-task flags (autonomy enforcement). Appended last. */\n extraArgs: string[] = [],\n): string[] {\n const args: string[] = [\n \"-p\", prompt,\n \"--output-format\", streaming ? \"stream-json\" : \"json\",\n ]\n\n // stream-json requires --verbose in recent Claude Code versions\n if (streaming) {\n args.push(\"--verbose\")\n }\n\n // Resume existing Claude session for conversation continuity\n if (resumeSessionId) {\n args.push(\"--resume\", resumeSessionId)\n }\n\n const model = modelOverride || agent.model\n if (model) {\n args.push(\"--model\", model)\n }\n\n if (agent.permissionMode === \"bypassPermissions\") {\n args.push(\"--dangerously-skip-permissions\")\n }\n\n if (systemPromptAppend && systemPromptAppend.trim().length > 0) {\n args.push(\"--append-system-prompt\", systemPromptAppend)\n }\n\n args.push(...extraArgs)\n return args\n}\n\nfunction buildCodexPrompt(prompt: string, systemPromptAppend?: string): string {\n if (!systemPromptAppend || systemPromptAppend.trim().length === 0) return prompt\n return `[System]\\n${systemPromptAppend.trim()}\\n\\n[User]\\n${prompt}`\n}\n\nfunction buildOpenCodeArgs(\n agent: AgentDef,\n prompt: string,\n modelOverride?: string,\n systemPromptAppend?: string,\n resumeSessionId?: string,\n): string[] {\n const args = [\"run\", \"--format\", \"json\", \"--thinking\"]\n const model = modelOverride || agent.model\n if (model) args.push(\"--model\", model)\n if (resumeSessionId) args.push(\"--session\", resumeSessionId)\n if (agent.permissionMode === \"bypassPermissions\") args.push(\"--auto\")\n args.push(buildCodexPrompt(prompt, systemPromptAppend))\n return args\n}\n\nfunction extractOpenCodeError(event: any): string | undefined {\n if (!event || typeof event !== \"object\" || event.type !== \"error\") return undefined\n const value = event.error?.message ?? event.error ?? event.message ?? event.part?.error?.message\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined\n}\n\nfunction tomlString(value: string): string {\n return JSON.stringify(value)\n}\n\nfunction buildCodexAgentxMcpArgs(): string[] {\n const cli = process.argv[1] || \"dist/cli.js\"\n return [\n \"-c\", `mcp_servers.agentx.command=${tomlString(process.execPath)}`,\n \"-c\", `mcp_servers.agentx.args=[${[cli, \"serve\", \"--stdio\", \"--cwd\", process.cwd()].map(tomlString).join(\",\")}]`,\n ]\n}\n\nfunction buildCodexArgs(\n agent: AgentDef,\n prompt: string,\n streaming: boolean,\n modelOverride?: string,\n systemPromptAppend?: string,\n outputFile?: string,\n resumeSessionId?: string,\n): string[] {\n const args: string[] = resumeSessionId\n ? [\"exec\", \"resume\"]\n : [\"exec\"]\n\n args.push(\n \"--ignore-user-config\",\n \"--skip-git-repo-check\",\n ...buildCodexAgentxMcpArgs(),\n )\n\n if (!resumeSessionId) args.push(\"--color\", \"never\")\n\n const model = modelOverride || agent.model\n if (model) args.push(\"--model\", model)\n\n if (agent.permissionMode === \"bypassPermissions\") {\n args.push(\"--dangerously-bypass-approvals-and-sandbox\")\n } else if (!resumeSessionId) {\n args.push(\"--sandbox\", \"workspace-write\")\n }\n\n if (streaming || resumeSessionId || outputFile) args.push(\"--json\")\n if (outputFile) args.push(\"--output-last-message\", outputFile)\n\n if (resumeSessionId) args.push(resumeSessionId)\n args.push(buildCodexPrompt(prompt, systemPromptAppend))\n return args\n}\n\nfunction buildRuntimeEnv(agent: AgentDef, task: AgentTask): NodeJS.ProcessEnv {\n const env = buildAgentEnv(agent.workspace)\n const home = env.HOME || process.env.HOME\n const pathParts = [\n env.PATH || \"\",\n home ? `${home}/.bun/bin` : \"\",\n home ? `${home}/.local/bin` : \"\",\n home ? `${home}/.opencode/bin` : \"\",\n \"/opt/homebrew/bin\",\n \"/usr/local/bin\",\n ].filter(Boolean)\n env.PATH = Array.from(new Set(pathParts.flatMap((p) => p.split(\":\")).filter(Boolean))).join(\":\")\n return withCallerEnv(env, task)\n}\n\n/** Export who is running and for which chat, so tools the agent launches\n * (the agentx MCP server in particular) can identify the caller without\n * trusting model-supplied arguments. This is the per-spawn version, with\n * the running task id. A warm persistent process is keyed by (agent,\n * channel, chatId) and gets the same agent and chat through\n * persistentCallerEnv (claude-process-factory.ts), but no task id, which\n * changes every turn. */\nexport function withCallerEnv(env: NodeJS.ProcessEnv, task: AgentTask): NodeJS.ProcessEnv {\n env.AGENTX_AGENT_ID = task.agentId\n // The running task, so a daemon endpoint the agent calls (agent memory)\n // can check the caller really is this agent, mid-run.\n if (task.runningTaskId) env.AGENTX_TASK_ID = task.runningTaskId\n if (task.context?.channel) env.AGENTX_CHANNEL = task.context.channel\n if (task.context?.chatId) env.AGENTX_CHAT_ID = task.context.chatId\n return env\n}\n\n/** The channel and chat a warm process is keyed by, and so the pair it\n * sends as proof of its running turn. A run with no chat falls back to\n * its sender, then to \"default\". The registry matches a running turn\n * against this same pair (#410): one derivation, or the two drift. */\nexport function warmProcessChat(context: { channel?: string; chatId?: string; group?: string; sender?: string } = {}): { channel: string; chatId: string } {\n return {\n channel: context.channel || \"api\",\n chatId: context.chatId || context.group || context.sender || \"default\",\n }\n}\n\nfunction buildOpenCodeEnv(agent: AgentDef, task: AgentTask): NodeJS.ProcessEnv {\n const env = buildRuntimeEnv(agent, task)\n let config: Record<string, any> = {}\n if (env.OPENCODE_CONFIG_CONTENT) {\n try { config = JSON.parse(env.OPENCODE_CONFIG_CONTENT) } catch { /* replace invalid inherited content */ }\n }\n\n const mcp: Record<string, any> = { ...(config.mcp || {}) }\n for (const [name, server] of Object.entries(agent.mcp || {}) as Array<[string, any]>) {\n mcp[name] = server.type === \"http\"\n ? { type: \"remote\", url: server.url, headers: server.headers }\n : { type: \"local\", command: [server.command, ...(server.args || [])], environment: server.env }\n }\n\n const cli = process.argv[1] || \"dist/cli.js\"\n mcp.agentx = {\n type: \"local\",\n command: [process.execPath, cli, \"serve\", \"--stdio\", \"--cwd\", process.cwd()],\n }\n env.OPENCODE_CONFIG_CONTENT = JSON.stringify({ ...config, mcp })\n return env\n}\n\nfunction extractCodexTextFromEvent(event: any): string | undefined {\n if (!event || typeof event !== \"object\") return undefined\n if (typeof event.delta === \"string\") return event.delta\n if (typeof event.text_delta === \"string\") return event.text_delta\n if (typeof event.message === \"string\" && /message|delta/i.test(String(event.type || \"\"))) return event.message\n if (typeof event.text === \"string\" && /message|delta|response/i.test(String(event.type || \"\"))) return event.text\n\n const item = event.item || event.msg || event.message\n if (item?.type === \"agent_message\" && typeof item.text === \"string\") return item.text\n if (typeof item?.text === \"string\" && /message|delta|response|item/i.test(String(event.type || \"\"))) return item.text\n const content = item?.content\n if (Array.isArray(content)) {\n const text = content\n .map((block: any) => typeof block?.text === \"string\" ? block.text : \"\")\n .join(\"\")\n if (text) return text\n }\n return undefined\n}\n\nfunction firstNumber(...values: unknown[]): number {\n for (const value of values) {\n if (typeof value === \"number\" && Number.isFinite(value)) return value\n }\n return 0\n}\n\nfunction extractCodexUsage(value: any, depth = 0): TokenUsage | undefined {\n if (!value || typeof value !== \"object\" || depth > 4) return undefined\n const candidate = value.usage || value.token_usage || value.tokenUsage || value.metrics?.usage\n if (candidate && typeof candidate === \"object\") {\n return {\n inputTokens: firstNumber(candidate.input_tokens, candidate.inputTokens, candidate.prompt_tokens, candidate.promptTokens),\n outputTokens: firstNumber(candidate.output_tokens, candidate.outputTokens, candidate.completion_tokens, candidate.completionTokens),\n cacheReadTokens: firstNumber(candidate.cache_read_input_tokens, candidate.cached_input_tokens, candidate.cacheReadTokens, candidate.cachedInputTokens),\n cacheCreateTokens: firstNumber(candidate.cache_creation_input_tokens, candidate.cacheCreateTokens),\n }\n }\n for (const nested of [value.msg, value.message, value.response, value.result, value.item]) {\n const usage = extractCodexUsage(nested, depth + 1)\n if (usage) return usage\n }\n return undefined\n}\n\nfunction extractCodexBilledModel(event: any): string | undefined {\n if (!event || typeof event !== \"object\") return undefined\n return (\n (typeof event.model === \"string\" && event.model) ||\n (typeof event.msg?.model === \"string\" && event.msg.model) ||\n (typeof event.message?.model === \"string\" && event.message.model) ||\n (typeof event.response?.model === \"string\" && event.response.model) ||\n undefined\n )\n}\n\nfunction extractCodexSessionId(value: any, depth = 0): string | undefined {\n if (!value || typeof value !== \"object\" || depth > 4) return undefined\n for (const key of [\"thread_id\", \"threadId\", \"session_id\", \"sessionId\", \"conversation_id\", \"conversationId\"]) {\n const candidate = value[key]\n if (typeof candidate === \"string\" && candidate.trim()) return candidate.trim()\n }\n for (const nested of [value.msg, value.message, value.response, value.result, value.item]) {\n const sessionId = extractCodexSessionId(nested, depth + 1)\n if (sessionId) return sessionId\n }\n return undefined\n}\n\nfunction collectCodexRunMetadata(stdout: string | Buffer | undefined): {\n usage?: TokenUsage\n billedModel?: string\n sessionId?: string\n apiError?: string\n} {\n const text = typeof stdout === \"string\" ? stdout : stdout?.toString(\"utf8\") || \"\"\n let usage: TokenUsage | undefined\n let billedModel: string | undefined\n let sessionId: string | undefined\n let apiError: string | undefined\n for (const line of text.split(\"\\n\")) {\n if (!line.trim()) continue\n try {\n const event = JSON.parse(line)\n usage = extractCodexUsage(event) || usage\n billedModel = extractCodexBilledModel(event) || billedModel\n sessionId = extractCodexSessionId(event) || sessionId\n apiError = extractCodexErrorMessage(event) || apiError\n } catch {\n // Non-JSON output is handled by the caller as content fallback.\n }\n }\n return { usage, billedModel, sessionId, apiError }\n}\n\n/** Extract the human-readable failure message from a codex event.\n * Codex emits failures on stdout as JSON, NOT on stderr — so when our\n * runtime fell back to stderr.trim() it was returning the benign\n * \"Reading additional input from stdin...\" log line as if it were the\n * error. This walks the two known shapes:\n * {\"type\":\"error\",\"message\":\"…\"}\n * {\"type\":\"turn.failed\",\"error\":{\"message\":\"…\"}}\n * …and unwraps any nested provider JSON inside the message so the\n * surfaced text is the actual reason (\"model X not supported when\n * using Codex with a ChatGPT account\") instead of envelope noise. */\nfunction extractCodexErrorMessage(event: any): string | undefined {\n if (!event || typeof event !== \"object\") return undefined\n if (event.type === \"error\" && typeof event.message === \"string\") {\n return unwrapCodexErrorMessage(event.message)\n }\n if (event.type === \"turn.failed\") {\n const msg = event.error?.message ?? event.error\n if (typeof msg === \"string\") return unwrapCodexErrorMessage(msg)\n }\n return undefined\n}\n\n/** Codex sometimes nests a provider JSON envelope inside the message\n * string: `{\"type\":\"error\",\"status\":400,\"error\":{\"message\":\"…\"}}`.\n * Try to peel one layer; fall back to the raw string. */\nfunction unwrapCodexErrorMessage(raw: string): string {\n const trimmed = raw.trim()\n if (trimmed.startsWith(\"{\")) {\n try {\n const inner = JSON.parse(trimmed)\n const nested = inner?.error?.message ?? inner?.message\n if (typeof nested === \"string\" && nested.trim()) return nested.trim()\n } catch { /* leave as-is */ }\n }\n return trimmed\n}\n\n/**\n * Improvement plan #4 — return the resolved permission posture for an\n * agent, including the actual CLI flag that gets passed (or doesn't).\n * Lets operators answer \"what permissions does my agent actually run\n * with?\" without reading the runtime source. Surfaced on\n * `GET /agents/:id` and logged at every claude-code spawn so the\n * operator can grep journalctl.\n */\nexport interface ResolvedPermission {\n /** Raw value from agentx.json, e.g. \"default\" / \"bypassPermissions\" / \"plan\" / \"auto\" / etc. */\n configured: string\n /** Whether the spawn includes --dangerously-skip-permissions. */\n skipPermissions: boolean\n /** Best-effort one-line summary suitable for journalctl. */\n summary: string\n}\n\nexport function resolvePermission(agent: AgentDef): ResolvedPermission {\n const configured = agent.permissionMode || \"default\"\n const skipPermissions = configured === \"bypassPermissions\"\n const summary = skipPermissions\n ? `bypassPermissions (--dangerously-skip-permissions)`\n : `${configured} (no skip-permissions flag)`\n return { configured, skipPermissions, summary }\n}\n\n/**\n * Single-line spawn audit that lands in stderr per dispatch. Lets\n * operators verify what every claude subprocess was actually invoked\n * with — specifically the permission flag, which is the load-bearing\n * security knob. Quiet on info paths (no flooding journalctl) — just\n * one line per spawn, with the agentId so `journalctl | grep <id>` is\n * useful.\n */\nexport function logClaudeSpawn(\n agentId: string,\n agent: AgentDef,\n modelOverride: string | undefined,\n resumeSessionId: string | undefined,\n via: \"spawn\" | \"stream\" | \"persistent\",\n): void {\n const perm = resolvePermission(agent)\n const model = modelOverride || agent.model || \"<default>\"\n const resume = resumeSessionId ? ` resume=${resumeSessionId.slice(0, 8)}…` : \"\"\n // Use process.stderr directly so this surfaces under journalctl even\n // when an agent's logger is detached. Single line, prefixed for grep.\n process.stderr.write(`[claude-spawn] agent=${agentId} via=${via} model=${model} perm=${perm.summary}${resume}\\n`)\n}\n\n/**\n * Parse JSON output from `claude -p --output-format json`.\n * Returns the text result and session_id.\n */\nexport interface TokenUsage {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreateTokens: number\n}\n\n/**\n * When the Claude Code CLI returns exit=0 but the run itself errored (e.g.\n * out-of-credits mid-session), it emits a top-level JSON object with\n * `is_error: true` and the raw API error in `result`. Detect + extract so we\n * can run the same friendly-error translator we use for stderr.\n * Returns null when the output is a normal success.\n */\nfunction extractClaudeIsError(stdout: string): string | null {\n try {\n const data = JSON.parse(stdout)\n if (data && data.is_error && typeof data.result === \"string\") return data.result\n if (data && data.error && typeof data.error === \"string\") return data.error\n } catch { /* not JSON — fall through */ }\n return null\n}\n\nfunction parseClaudeJsonOutput(stdout: string): { text: string; sessionId?: string; usage?: TokenUsage; billedModel?: string; numTurns?: number; costUsd?: number } {\n try {\n const data = JSON.parse(stdout)\n const usage = data.usage ? {\n inputTokens: data.usage.input_tokens || 0,\n outputTokens: data.usage.output_tokens || 0,\n cacheReadTokens: data.usage.cache_read_input_tokens || 0,\n cacheCreateTokens: data.usage.cache_creation_input_tokens || 0,\n } : undefined\n\n // Claude Code's --output-format json response carries the actual billed\n // model at `model` (or nested under `message.model` depending on CLI ver).\n // Recent CLIs put it under `modelUsage` keyed by model id instead, next\n // to any side model the CLI called, so take the one that cost the most.\n const billedModel: string | undefined =\n (typeof data.model === \"string\" && data.model) ||\n (typeof data.message?.model === \"string\" && data.message.model) ||\n primaryModelFromUsage(data.modelUsage) ||\n undefined\n\n return {\n text: data.result || data.content || \"\",\n sessionId: data.session_id,\n usage,\n billedModel,\n numTurns: typeof data.num_turns === \"number\" ? data.num_turns : undefined,\n costUsd: typeof data.total_cost_usd === \"number\" ? data.total_cost_usd : undefined,\n }\n } catch {\n return { text: stdout }\n }\n}\n\n/**\n * Execute a task using the Claude Code CLI (tier: \"claude-code\").\n * Non-streaming — waits for full response.\n */\nexport async function executeClaudeCode(\n agent: AgentDef,\n task: AgentTask,\n historyContext?: string,\n resumeSessionId?: string,\n abortSignal?: AbortSignal,\n): Promise<AgentResponse> {\n const start = Date.now()\n // Always inject context (landscape, rules, channel info) even on resume.\n // Claude CLI --resume carries its own conversation history, but the\n // landscape + rules must be fresh so the agent sees capability updates.\n const prompt = buildPrompt(agent, task, historyContext)\n const restricted = restrictedClaudeArgs(task)\n if (\"error\" in restricted) return { content: \"\", error: restricted.error, duration: Date.now() - start }\n const args = buildClaudeArgs(agent, prompt, false, resumeSessionId, task.model, task.systemPromptAppend, [...(task.claudeArgs ?? []), ...restricted.args])\n logClaudeSpawn(task.agentId, agent, task.model, resumeSessionId, \"spawn\")\n\n // If the caller already aborted before we spawned, short-circuit so we\n // don't spend tokens on a doomed run.\n if (abortSignal?.aborted) {\n return { content: \"\", error: \"task cancelled by operator\", duration: Date.now() - start }\n }\n\n let abortReason: string | undefined\n try {\n const timeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const { stdout, stderr, exitCode, killed } = await new Promise<{ stdout: string; stderr: string; exitCode: number | string; killed: boolean }>((resolve) => {\n const childEnv = claudeBillingEnv(withCallerEnv(buildAgentEnv(agent.workspace), task), agent.billing)\n let killed = false\n const proc = execFile(\"claude\", args, {\n cwd: agent.workspace,\n timeout: timeoutMs,\n maxBuffer: 10 * 1024 * 1024,\n env: { ...childEnv, HOME: childEnv.HOME || \"/home/\" + (childEnv.USER || \"user\") },\n }, (error, stdout, stderr) => {\n // Always resolve — we handle errors ourselves based on stdout/stderr\n resolve({\n stdout: stdout || \"\",\n stderr: stderr || \"\",\n exitCode: error ? (error as any).code ?? 1 : 0,\n killed,\n })\n })\n // Close stdin immediately. Claude CLI otherwise waits 3s for input then\n // proceeds with a warning; in cron retries that cascade invalidates the\n // prompt cache between attempts (5× input cost on cache-create).\n proc.stdin?.end()\n // Operator-initiated cancellation. SIGTERM first; if the child is still\n // alive after 3s, escalate to SIGKILL. The execFile callback fires when\n // the child exits, which resolves the outer promise.\n if (abortSignal) {\n const onAbort = () => {\n killed = true\n abortReason = (abortSignal.reason as any)?.message || (typeof abortSignal.reason === \"string\" ? abortSignal.reason : \"task cancelled by operator\")\n try { proc.kill(\"SIGTERM\") } catch { /* */ }\n setTimeout(() => { try { if (!proc.killed) proc.kill(\"SIGKILL\") } catch { /* */ } }, 3000).unref?.()\n }\n if (abortSignal.aborted) onAbort()\n else abortSignal.addEventListener(\"abort\", onAbort, { once: true })\n }\n })\n\n if (killed) {\n return {\n content: \"\",\n error: abortReason || \"task cancelled by operator\",\n errorKind: \"cancelled\",\n duration: Date.now() - start,\n }\n }\n\n if (!stdout && exitCode !== 0) {\n // exit 143 = 128+SIGTERM. It is our own `timeout` only when the run\n // actually reached it; earlier, something else stopped the process\n // (a daemon shutdown), and raising maxExecutionMinutes would not help.\n let errMsg: string\n const ranMs = Date.now() - start\n if (exitCode === 143 && ranMs < timeoutMs - SIGTERM_TIMEOUT_SLACK_MS) {\n return {\n content: \"\",\n error: `Claude Code was stopped (SIGTERM) after ${Math.round(ranMs / 1000)}s, before its ${Math.round(timeoutMs / 60_000)}m time limit`,\n errorKind: \"interrupted\",\n duration: ranMs,\n }\n }\n if (exitCode === 143) {\n errMsg = `Claude Code timed out after ${Math.round(timeoutMs / 60_000)}m (SIGTERM). Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`\n } else if (exitCode === \"ENOENT\" || /ENOENT|spawn claude/i.test(stderr || \"\")) {\n errMsg = claudeMissingMessage()\n } else {\n errMsg = stderr?.trim() || `Claude Code exited with code ${exitCode}`\n }\n return {\n content: \"\",\n ...buildErrorEnvelope(errMsg),\n duration: Date.now() - start,\n }\n }\n\n // Claude Code sometimes exits 0 but embeds the API error in stdout's\n // \"result\" field when `is_error` is set. Translate that too.\n const apiErrorInStdout = extractClaudeIsError(stdout)\n if (apiErrorInStdout) {\n return {\n content: \"\",\n ...buildErrorEnvelope(apiErrorInStdout),\n duration: Date.now() - start,\n }\n }\n\n const parsed = parseClaudeJsonOutput(stdout)\n\n return {\n content: parsed.text,\n duration: Date.now() - start,\n claudeSessionId: parsed.sessionId,\n usage: parsed.usage,\n numTurns: parsed.numTurns,\n costUsd: parsed.costUsd,\n billedModel: parsed.billedModel,\n }\n } catch (error: any) {\n console.error(`[runtime] execFile threw: ${error.message}`)\n return {\n content: \"\",\n error: error.message || \"Claude Code failed\",\n duration: Date.now() - start,\n }\n }\n}\n\n/**\n * Execute with streaming — calls onDelta with text chunks as they arrive.\n * Uses claude --output-format stream-json to get real-time output.\n */\nexport async function executeClaudeCodeStreaming(\n agent: AgentDef,\n task: AgentTask,\n onDelta: StreamCallback,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n abortSignal?: AbortSignal,\n): Promise<AgentResponse> {\n const start = Date.now()\n if (abortSignal?.aborted) {\n return { content: \"\", error: \"task cancelled by operator\", duration: Date.now() - start }\n }\n // Always inject context (landscape, rules, channel info) even on resume.\n // Claude CLI --resume carries its own conversation history, but the\n // landscape + rules must be fresh so the agent sees capability updates.\n const prompt = buildPrompt(agent, task, historyContext)\n const restricted = restrictedClaudeArgs(task)\n if (\"error\" in restricted) return { content: \"\", error: restricted.error, duration: Date.now() - start }\n const args = buildClaudeArgs(agent, prompt, true, resumeSessionId, task.model, task.systemPromptAppend, [...(task.claudeArgs ?? []), ...restricted.args])\n logClaudeSpawn(task.agentId, agent, task.model, resumeSessionId, \"stream\")\n\n let fullText = \"\"\n // A run can span several turns when the agent leaves background work\n // (#892). `turnText` is the current turn's text; `earlierText` holds the\n // turns before it, so fullText = earlierText + turnText.\n let earlierText = \"\"\n let turnText = \"\"\n const emitTurnText = (delta: string) => {\n const sep = earlierText && !turnText ? \"\\n\\n\" : \"\"\n turnText += delta\n fullText = earlierText + (earlierText ? \"\\n\\n\" : \"\") + turnText\n onDelta(sep + delta, fullText)\n }\n const background = new BackgroundTasks()\n let heldForBackground = false\n // Capture model + usage + session id from the stream events — Claude Code\n // emits a system-init event up front (with the billed model) and a terminal\n // result event with the full usage accounting. Without these we can't\n // attribute cost correctly for streamed tasks.\n let streamBilledModel: string | undefined\n let streamUsage: TokenUsage | undefined\n let streamSessionId: string | undefined\n /** Last assistant event's per-call context size — see AgentResponse.contextTokens. */\n let streamContextTokens: number | undefined\n let streamNumTurns: number | undefined\n let streamCostUsd: number | undefined\n /** If the terminal `result` event carries is_error, we stash it here and\n * surface the translated message instead of treating `result` as agent text. */\n let streamApiError: string | undefined\n\n try {\n const streamTimeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const spawnEnv = claudeBillingEnv(withCallerEnv(buildAgentEnv(agent.workspace), task), agent.billing)\n const proc = execa(\"claude\", args, {\n cwd: agent.workspace,\n timeout: streamTimeoutMs,\n reject: false,\n env: spawnEnv,\n extendEnv: false,\n // Don't buffer — we'll read stdout line by line\n buffer: false,\n // Close stdin so Claude CLI doesn't wait 3s for input (see executeClaudeCode).\n stdin: \"ignore\",\n })\n // Settled by the terminal `result` event: the first one with no\n // background task left (#892). The answer is complete at that point;\n // what follows is the CLI's own teardown (SessionEnd hook, MCP\n // servers), which the persistent path never waited for either.\n let markResultSeen: () => void = () => {}\n const resultSeen = new Promise<void>((r) => { markResultSeen = r })\n\n // Operator cancellation. SIGTERM → SIGKILL escalation after 3s; we mark\n // the run as cancelled so the post-await result-mapping below surfaces a\n // clean \"task cancelled\" error instead of a garbled \"killed by signal\".\n let cancelled = false\n let cancelReason: string | undefined\n if (abortSignal) {\n const onAbort = () => {\n cancelled = true\n cancelReason = (abortSignal.reason as any)?.message || (typeof abortSignal.reason === \"string\" ? abortSignal.reason : \"task cancelled by operator\")\n try { proc.kill(\"SIGTERM\") } catch { /* */ }\n setTimeout(() => { try { proc.kill(\"SIGKILL\") } catch { /* */ } }, 3000).unref?.()\n }\n if (abortSignal.aborted) onAbort()\n else abortSignal.addEventListener(\"abort\", onAbort, { once: true })\n }\n\n // Parse stream-json output line by line.\n // StringDecoder buffers partial UTF-8 sequences across chunk\n // boundaries; plain Buffer.toString() does not, so a chunk that\n // ends mid-character (Arabic = 2 bytes, common emoji = 4 bytes)\n // produced replacement chars or dropped letters in the agent's\n // outbound text. The user reported \"letters missing from words\"\n // on WhatsApp specifically — Arabic + emoji-heavy traffic is\n // exactly the workload that exposes this. Hoisted out of the if\n // block so the post-`await proc` flush can call decoder.end().\n let lineBuffer = \"\"\n const decoder = new StringDecoder(\"utf8\")\n if (proc.stdout) {\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n lineBuffer += decoder.write(chunk as Buffer)\n const lines = lineBuffer.split(\"\\n\")\n lineBuffer = lines.pop() || \"\"\n\n for (const line of lines) {\n if (!line.trim()) continue\n try {\n const event = JSON.parse(line)\n\n // Surface every parsed event for observability (dashboard streaming).\n // Best-effort — never let a subscriber crash the runtime.\n if (onEvent) {\n try { onEvent(event) } catch { /* */ }\n }\n\n // Every \"assistant\" event is a full API message envelope whose\n // usage covers THAT call only — input + cache tokens ≈ the\n // context size of the request that produced it. The last one\n // seen is the end-of-turn context size (rotation metric).\n if (event.type === \"assistant\" && event.message?.usage) {\n const u = event.message.usage\n const callContext =\n (u.input_tokens || 0) +\n (u.cache_read_input_tokens || 0) +\n (u.cache_creation_input_tokens || 0)\n if (callContext > 0) streamContextTokens = callContext\n }\n\n // Claude stream-json emits different event types\n // \"assistant\" messages with content contain the text\n if (event.type === \"assistant\" && event.message?.content) {\n for (const block of event.message.content) {\n if (block.type === \"text\" && block.text) {\n const delta = block.text.slice(turnText.length)\n if (delta) emitTurnText(delta)\n }\n }\n }\n\n // \"content_block_delta\" for streaming text\n if (event.type === \"content_block_delta\" && event.delta?.text) {\n emitTurnText(event.delta.text)\n }\n\n background.observe(event)\n\n // \"result\" event contains final text (or an API error, when is_error).\n if (event.type === \"result\" && event.result) {\n if (event.is_error && typeof event.result === \"string\") {\n streamApiError = event.result\n } else {\n const resultText = typeof event.result === \"string\"\n ? event.result\n : event.result\n if (typeof resultText === \"string\" && resultText.length > turnText.length) {\n const delta = resultText.slice(turnText.length)\n if (delta) emitTurnText(delta)\n }\n }\n // Final event also carries the authoritative usage + model + session.\n if (event.usage) {\n streamUsage = {\n inputTokens: event.usage.input_tokens || 0,\n outputTokens: event.usage.output_tokens || 0,\n cacheReadTokens: event.usage.cache_read_input_tokens || 0,\n cacheCreateTokens: event.usage.cache_creation_input_tokens || 0,\n }\n }\n if (typeof event.model === \"string\") streamBilledModel = event.model\n else streamBilledModel = primaryModelFromUsage(event.modelUsage) || streamBilledModel\n if (typeof event.session_id === \"string\") streamSessionId = event.session_id\n if (typeof event.num_turns === \"number\") streamNumTurns = event.num_turns\n if (typeof event.total_cost_usd === \"number\") streamCostUsd = event.total_cost_usd\n }\n\n // A result ends the run only when no background work is left.\n // Otherwise the CLI stays up and runs another turn once that\n // work ends, so the task stays open (and stoppable) until then.\n if (event.type === \"result\") {\n if (background.pending === 0) {\n markResultSeen()\n } else {\n if (!heldForBackground) {\n process.stderr.write(`[claude-stream] agent=${task.agentId} task=${task.taskId ?? \"?\"} turn ended with ${background.pending} background task(s) running; the task stays open until they end\\n`)\n }\n heldForBackground = true\n if (turnText) {\n earlierText = fullText\n turnText = \"\"\n }\n }\n }\n\n // System-init event (first thing the CLI emits) carries the model\n // it's about to invoke — capture early so we have it even if the\n // run errors before the terminal result.\n //\n // Do NOT capture session_id from init: when --resume <X> is\n // passed, init's session_id echoes X back (the resumed-from ID),\n // not the new ID Claude rolls into for THIS turn. Persisting X\n // means the next turn re-resumes from before this turn's progress\n // — exactly the \"wrong-message resume\" symptom. Only `result`\n // carries the authoritative new session_id (line above).\n if (event.type === \"system\" && event.subtype === \"init\") {\n if (typeof event.model === \"string\") streamBilledModel = event.model\n if (process.env.AGENTX_DEBUG_CLAUDE_ENV === \"1\") {\n process.stderr.write(`[claude-init] agent=${task.agentId} apiKeySource=${event.apiKeySource} version=${event.claude_code_version} model=${event.model} permMode=${event.permissionMode}\\n`)\n }\n }\n } catch {\n // Not JSON — could be raw text output, append it\n if (line.trim() && !line.startsWith(\"{\")) {\n emitTurnText(line + \"\\n\")\n }\n }\n }\n })\n }\n\n const result = await settleAfterResult(proc, resultSeen)\n\n // Flush any partial UTF-8 sequence still buffered in the decoder.\n // The stream-json output normally ends with a newline so this is a\n // no-op, but it keeps the channel honest if the producer ever\n // exits mid-character.\n const tail = decoder.end()\n if (tail) lineBuffer += tail\n\n // Operator cancellation wins over any partial stream — surface the\n // friendly error so callers (KPI, dashboard, channel ack) record it\n // distinctly from a timeout or model failure.\n if (cancelled) {\n return {\n content: \"\",\n error: cancelReason || \"task cancelled by operator\",\n errorKind: \"cancelled\",\n duration: Date.now() - start,\n }\n }\n\n // If we got no streaming content, fall back to stdout\n if (!fullText && result.stdout) {\n fullText = typeof result.stdout === \"string\" ? result.stdout : \"\"\n }\n\n if (result.exitCode !== 0 && !fullText) {\n const stderr = typeof result.stderr === \"string\" ? result.stderr : \"\"\n // execa sets exitCode=undefined when the child is killed by signal —\n // surface that as a recognizable timeout message instead of leaking\n // \"exited with code undefined\" into the user's chat. Mirrors the\n // non-streaming path's exit-143 handling.\n const r = result as { exitCode?: number; signal?: string; timedOut?: boolean; failed?: boolean; code?: string; shortMessage?: string }\n let errMsg: string\n if (r.timedOut) {\n errMsg = `Claude Code timed out after ${Math.round(streamTimeoutMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`\n } else if (r.signal) {\n errMsg = stderr.trim() || `Claude Code killed by signal ${r.signal}`\n } else if (r.code === \"ENOENT\" || /ENOENT|spawn claude/i.test((r.shortMessage || \"\") + \" \" + stderr)) {\n // execa surfaces \"binary not on PATH\" as { failed: true, code: 'ENOENT',\n // exitCode: undefined } — without an explicit branch this used to leak\n // through as \"Claude Code exited with code unknown\", leaving operators\n // with no clue that the issue is a missing CLI.\n errMsg = claudeMissingMessage()\n } else {\n errMsg = stderr.trim() || `Claude Code exited with code ${r.exitCode ?? \"unknown\"}`\n }\n return {\n content: \"\",\n ...buildErrorEnvelope(errMsg),\n duration: Date.now() - start,\n }\n }\n\n if (streamApiError) {\n return {\n content: \"\",\n ...buildErrorEnvelope(streamApiError),\n duration: Date.now() - start,\n usage: streamUsage,\n contextTokens: streamContextTokens,\n numTurns: streamNumTurns,\n billedModel: streamBilledModel,\n claudeSessionId: streamSessionId,\n }\n }\n\n return {\n content: fullText,\n duration: Date.now() - start,\n usage: streamUsage,\n contextTokens: streamContextTokens,\n numTurns: streamNumTurns,\n costUsd: streamCostUsd,\n billedModel: streamBilledModel,\n claudeSessionId: streamSessionId,\n }\n } catch (error: any) {\n return {\n content: fullText || \"\",\n ...buildErrorEnvelope(error.message),\n duration: Date.now() - start,\n usage: streamUsage,\n contextTokens: streamContextTokens,\n numTurns: streamNumTurns,\n billedModel: streamBilledModel,\n claudeSessionId: streamSessionId,\n }\n }\n}\n\nexport async function executeCodexCli(\n agent: AgentDef,\n task: AgentTask,\n historyContext?: string,\n resumeSessionId?: string,\n): Promise<AgentResponse> {\n const start = Date.now()\n const prompt = buildPrompt(agent, task, historyContext)\n const tmp = mkdtempSync(join(tmpdir(), \"agentx-codex-\"))\n const outputFile = join(tmp, \"last-message.txt\")\n const args = buildCodexArgs(agent, prompt, false, task.model, task.systemPromptAppend, outputFile, resumeSessionId)\n\n try {\n const timeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const result = await execa(\"codex\", args, {\n cwd: agent.workspace,\n timeout: timeoutMs,\n reject: false,\n env: buildRuntimeEnv(agent, task),\n extendEnv: false,\n stdin: \"ignore\",\n })\n\n // Parse the JSON event stream first so we can prefer the real API\n // error (carried on stdout) over codex's benign stderr log lines.\n const metadata = collectCodexRunMetadata(result.stdout)\n\n // Treat a recorded API error as a failure even when codex itself\n // exits 0 — auth/model/quota errors come back as \"turn.failed\"\n // events but don't always set a non-zero exit.\n if (result.exitCode !== 0 || metadata.apiError) {\n const r = result as { exitCode?: number; signal?: string; timedOut?: boolean; code?: string; shortMessage?: string }\n let errMsg: string\n if (r.timedOut) {\n errMsg = `Codex CLI timed out after ${Math.round(timeoutMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`\n } else if (metadata.apiError) {\n // Real API failure surfaced from the JSON event stream — beats\n // stderr noise like \"Reading additional input from stdin...\".\n errMsg = metadata.apiError\n } else if (r.signal) {\n errMsg = result.stderr.trim() || `Codex CLI killed by signal ${r.signal}`\n } else if (r.code === \"ENOENT\" || /ENOENT|spawn codex/i.test((r.shortMessage || \"\") + \" \" + result.stderr)) {\n errMsg = codexMissingMessage()\n } else {\n errMsg = result.stderr.trim() || result.stdout.trim() || `Codex CLI exited with code ${r.exitCode ?? \"unknown\"}`\n }\n return {\n content: \"\",\n ...buildErrorEnvelope(errMsg),\n duration: Date.now() - start,\n usage: metadata.usage,\n billedModel: metadata.billedModel,\n codexSessionId: metadata.sessionId || resumeSessionId,\n }\n }\n\n let content = \"\"\n try { content = readFileSync(outputFile, \"utf8\").trim() } catch { /* fall back below */ }\n if (!content) content = result.stdout.trim()\n return {\n content,\n duration: Date.now() - start,\n usage: metadata.usage,\n billedModel: metadata.billedModel,\n codexSessionId: metadata.sessionId || resumeSessionId,\n }\n } catch (error: any) {\n const raw = /ENOENT|spawn codex/i.test(error?.message || \"\") ? codexMissingMessage() : error?.message\n return { content: \"\", ...buildErrorEnvelope(raw || \"Codex CLI failed\"), duration: Date.now() - start }\n } finally {\n rmSync(tmp, { recursive: true, force: true })\n }\n}\n\nexport async function executeCodexCliStreaming(\n agent: AgentDef,\n task: AgentTask,\n onDelta: StreamCallback,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n): Promise<AgentResponse> {\n const start = Date.now()\n const prompt = buildPrompt(agent, task, historyContext)\n const tmp = mkdtempSync(join(tmpdir(), \"agentx-codex-\"))\n const outputFile = join(tmp, \"last-message.txt\")\n const args = buildCodexArgs(agent, prompt, true, task.model, task.systemPromptAppend, outputFile, resumeSessionId)\n let fullText = \"\"\n let streamUsage: TokenUsage | undefined\n let streamBilledModel: string | undefined\n let streamSessionId: string | undefined\n let streamApiError: string | undefined\n\n try {\n const timeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const proc = execa(\"codex\", args, {\n cwd: agent.workspace,\n timeout: timeoutMs,\n reject: false,\n env: buildRuntimeEnv(agent, task),\n extendEnv: false,\n buffer: false,\n stdin: \"ignore\",\n })\n if (onEvent) {\n try {\n onEvent({\n type: \"codex.spawned\",\n model: task.model || agent.model,\n resumeSessionId,\n })\n } catch { /* */ }\n }\n\n let lineBuffer = \"\"\n let stderrText = \"\"\n let idleTimedOut = false\n const decoder = new StringDecoder(\"utf8\")\n const stderrDecoder = new StringDecoder(\"utf8\")\n const idleTimeoutMs = Math.min(\n timeoutMs,\n Math.max(30_000, Number(process.env.AGENTX_CODEX_IDLE_TIMEOUT_MS || 180_000)),\n )\n let idleTimer: ReturnType<typeof setTimeout> | undefined\n const resetIdleTimer = () => {\n if (idleTimer) clearTimeout(idleTimer)\n idleTimer = setTimeout(() => {\n idleTimedOut = true\n ;(proc as any).kill(\"SIGTERM\", { forceKillAfterTimeout: 5_000 })\n }, idleTimeoutMs)\n }\n resetIdleTimer()\n proc.stderr?.on(\"data\", (chunk: Buffer) => {\n resetIdleTimer()\n stderrText += stderrDecoder.write(chunk)\n if (stderrText.length > 16_000) stderrText = stderrText.slice(-16_000)\n })\n proc.stdout?.on(\"data\", (chunk: Buffer) => {\n resetIdleTimer()\n lineBuffer += decoder.write(chunk)\n const lines = lineBuffer.split(\"\\n\")\n lineBuffer = lines.pop() || \"\"\n for (const line of lines) {\n if (!line.trim()) continue\n try {\n const event = JSON.parse(line)\n if (onEvent) {\n try { onEvent(event) } catch { /* */ }\n }\n streamUsage = extractCodexUsage(event) || streamUsage\n streamBilledModel = extractCodexBilledModel(event) || streamBilledModel\n streamSessionId = extractCodexSessionId(event) || streamSessionId\n streamApiError = extractCodexErrorMessage(event) || streamApiError\n const text = extractCodexTextFromEvent(event)\n if (text && text !== fullText) {\n const delta = text.startsWith(fullText) ? text.slice(fullText.length) : text\n fullText = text.startsWith(fullText) ? text : fullText + text\n if (delta) onDelta(delta, fullText)\n }\n } catch {\n fullText += line + \"\\n\"\n onDelta(line + \"\\n\", fullText)\n }\n }\n })\n\n const result = await proc\n if (idleTimer) clearTimeout(idleTimer)\n const tail = decoder.end()\n if (tail) lineBuffer += tail\n const stderrTail = stderrDecoder.end()\n if (stderrTail) stderrText += stderrTail\n\n // A recorded API error from the JSON event stream wins even when\n // the run produced some incidental text or codex exited 0 — the\n // turn failed and the user wants to know why, not see partial output.\n if ((result.exitCode !== 0 && !fullText) || streamApiError) {\n const stderr = stderrText || (typeof result.stderr === \"string\" ? result.stderr : \"\")\n const r = result as { exitCode?: number; signal?: string; timedOut?: boolean; code?: string; shortMessage?: string }\n let errMsg: string\n if (idleTimedOut) errMsg = `Codex CLI produced no output for ${Math.round(idleTimeoutMs / 1000)}s and was stopped. AgentX sent a smaller prompt; retry the task or set AGENTX_CODEX_IDLE_TIMEOUT_MS if this turn needs longer.`\n else if (r.timedOut) errMsg = `Codex CLI timed out after ${Math.round(timeoutMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`\n else if (streamApiError) errMsg = streamApiError\n else if (r.signal) errMsg = stderr.trim() || `Codex CLI killed by signal ${r.signal}`\n else if (r.code === \"ENOENT\" || /ENOENT|spawn codex/i.test((r.shortMessage || \"\") + \" \" + stderr)) errMsg = codexMissingMessage()\n else errMsg = stderr.trim() || `Codex CLI exited with code ${r.exitCode ?? \"unknown\"}`\n return {\n content: \"\",\n ...buildErrorEnvelope(errMsg),\n duration: Date.now() - start,\n usage: streamUsage,\n billedModel: streamBilledModel,\n codexSessionId: streamSessionId || resumeSessionId,\n }\n }\n\n let finalText = \"\"\n try { finalText = readFileSync(outputFile, \"utf8\").trim() } catch { /* */ }\n if (finalText && finalText !== fullText) {\n const delta = finalText.startsWith(fullText) ? finalText.slice(fullText.length) : finalText\n fullText = finalText\n if (delta) onDelta(delta, fullText)\n }\n return {\n content: fullText,\n duration: Date.now() - start,\n usage: streamUsage,\n billedModel: streamBilledModel,\n codexSessionId: streamSessionId || resumeSessionId,\n }\n } catch (error: any) {\n const raw = /ENOENT|spawn codex/i.test(error?.message || \"\") ? codexMissingMessage() : error?.message\n return { content: fullText, ...buildErrorEnvelope(raw || \"Codex CLI failed\"), duration: Date.now() - start, usage: streamUsage, billedModel: streamBilledModel, codexSessionId: streamSessionId || resumeSessionId }\n } finally {\n rmSync(tmp, { recursive: true, force: true })\n }\n}\n\nexport async function executeOpenCodeCli(\n agent: AgentDef,\n task: AgentTask,\n onDelta?: StreamCallback,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n abortSignal?: AbortSignal,\n onThinking?: ThinkingCallback,\n): Promise<AgentResponse> {\n const start = Date.now()\n if (abortSignal?.aborted) {\n return { content: \"\", error: \"task cancelled by operator\", errorKind: \"cancelled\", duration: 0 }\n }\n\n const prompt = buildPrompt(agent, task, historyContext)\n const args = buildOpenCodeArgs(agent, prompt, task.model, task.systemPromptAppend, task.freshSession ? undefined : resumeSessionId)\n let fullText = \"\"\n let usage: TokenUsage | undefined\n let sessionId = task.freshSession ? undefined : resumeSessionId\n let apiError: string | undefined\n let server: Awaited<ReturnType<typeof openCodeProcessPool.acquire>> | undefined\n let failed = true\n let cleanupAbort: (() => void) | undefined\n\n try {\n const timeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n let env = buildOpenCodeEnv(agent, task)\n if (agent.persistentProcess) {\n try {\n server = await openCodeProcessPool.acquire({\n key: JSON.stringify([task.agentId, task.context?.channel, task.context?.chatId]),\n cwd: agent.workspace, env, model: task.model || agent.model,\n permission: agent.permissionMode, fresh: task.freshSession, signal: abortSignal,\n })\n args.splice(1, 0, \"--server\", server.url)\n env = server.env\n onEvent?.({ type: \"opencode.ready\", reused: server.reused, startupMs: Date.now() - start })\n } catch (error) {\n if (!(error instanceof OpenCodeServerUnavailable)) throw error\n if (error.standalone) args.splice(1, 0, \"--standalone\")\n onEvent?.({ type: \"opencode.fallback\", reason: error.message })\n }\n }\n if (abortSignal?.aborted) throw new Error(\"task cancelled by operator\")\n const proc = execa(\"opencode\", args, {\n cwd: agent.workspace,\n timeout: timeoutMs,\n reject: false,\n env,\n extendEnv: false,\n buffer: false,\n stdin: \"ignore\",\n })\n try { onEvent?.({ type: \"opencode.spawned\", model: task.model || agent.model, resumeSessionId }) } catch { /* */ }\n\n let cancelled = false\n let cancelReason: string | undefined\n const onAbort = () => {\n server?.invalidate()\n cancelled = true\n cancelReason = (abortSignal?.reason as any)?.message || (typeof abortSignal?.reason === \"string\" ? abortSignal.reason : \"task cancelled by operator\")\n try { proc.kill(\"SIGTERM\", { forceKillAfterTimeout: 3_000 }) } catch { /* */ }\n }\n if (abortSignal) abortSignal.addEventListener(\"abort\", onAbort, { once: true })\n cleanupAbort = () => abortSignal?.removeEventListener(\"abort\", onAbort)\n if (abortSignal?.aborted) onAbort()\n\n let lineBuffer = \"\"\n let stderrText = \"\"\n const decoder = new StringDecoder(\"utf8\")\n const stderrDecoder = new StringDecoder(\"utf8\")\n const consumeLine = (line: string) => {\n if (!line.trim()) return\n try {\n const event = JSON.parse(line)\n try { onEvent?.(event) } catch { /* */ }\n if (typeof event.sessionID === \"string\") sessionId = event.sessionID\n const text = event.type === \"text\" && typeof event.part?.text === \"string\" ? event.part.text : undefined\n if (text) {\n if (!fullText) onEvent?.({ type: \"opencode.first_output\", elapsedMs: Date.now() - start, reused: server?.reused ?? false })\n fullText += text\n onDelta?.(text, fullText)\n }\n const reasoning = event.type === \"reasoning\" && typeof event.part?.text === \"string\" ? event.part.text : undefined\n if (reasoning) onThinking?.(reasoning)\n const tokens = event.type === \"step_finish\" ? event.part?.tokens : undefined\n if (tokens) {\n usage = {\n inputTokens: firstNumber(tokens.input),\n outputTokens: firstNumber(tokens.output),\n cacheReadTokens: firstNumber(tokens.cache?.read),\n cacheCreateTokens: firstNumber(tokens.cache?.write),\n }\n }\n apiError = extractOpenCodeError(event) || apiError\n } catch {\n fullText += line + \"\\n\"\n onDelta?.(line + \"\\n\", fullText)\n }\n }\n proc.stdout?.on(\"data\", (chunk: Buffer) => {\n lineBuffer += decoder.write(chunk)\n const lines = lineBuffer.split(\"\\n\")\n lineBuffer = lines.pop() || \"\"\n for (const line of lines) consumeLine(line)\n })\n proc.stderr?.on(\"data\", (chunk: Buffer) => { stderrText += stderrDecoder.write(chunk) })\n\n const result = await proc\n if (abortSignal) abortSignal.removeEventListener(\"abort\", onAbort)\n lineBuffer += decoder.end()\n if (lineBuffer.trim()) consumeLine(lineBuffer)\n stderrText += stderrDecoder.end()\n\n if (cancelled) {\n return { content: \"\", error: cancelReason || \"task cancelled by operator\", errorKind: \"cancelled\", duration: Date.now() - start, opencodeSessionId: sessionId }\n }\n if (result.exitCode !== 0 || apiError) {\n const r = result as { exitCode?: number; signal?: string; timedOut?: boolean; code?: string; shortMessage?: string }\n let errMsg: string\n if (r.timedOut) errMsg = `OpenCode timed out after ${Math.round(timeoutMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`\n else if (apiError) errMsg = apiError\n else if (r.code === \"ENOENT\" || /ENOENT|spawn opencode/i.test((r.shortMessage || \"\") + \" \" + stderrText)) errMsg = openCodeMissingMessage()\n else errMsg = stderrText.trim() || `OpenCode exited with code ${r.exitCode ?? \"unknown\"}`\n return { content: \"\", ...buildErrorEnvelope(errMsg), duration: Date.now() - start, usage, opencodeSessionId: sessionId }\n }\n failed = false\n return { content: fullText.trim(), duration: Date.now() - start, usage, billedModel: task.model || agent.model, opencodeSessionId: sessionId }\n } catch (error: any) {\n if (abortSignal?.aborted) return { content: \"\", error: \"task cancelled by operator\", errorKind: \"cancelled\", duration: Date.now() - start }\n const raw = /ENOENT|spawn opencode/i.test(error?.message || \"\") ? openCodeMissingMessage() : error?.message\n return { content: fullText, ...buildErrorEnvelope(raw || \"OpenCode failed\"), duration: Date.now() - start, usage, opencodeSessionId: sessionId }\n } finally {\n cleanupAbort?.()\n server?.release(failed)\n }\n}\n/**\n * Execute a task using the Claude Agent SDK (tier: \"sdk\").\n */\nexport async function executeSdk(\n agent: AgentDef,\n task: AgentTask,\n apiKey: string,\n historyContext?: string,\n): Promise<AgentResponse> {\n const start = Date.now()\n\n try {\n // @ts-ignore — SDK may not be installed\n const sdk = await import(\"@anthropic-ai/claude-agent-sdk\")\n const { query } = sdk\n\n // Build prompt with full context (landscape, memory, patterns, etc.)\n const prompt = buildPrompt(agent, task, historyContext)\n\n let content = \"\"\n\n const q = query({\n prompt,\n options: {\n model: task.model || agent.model,\n cwd: agent.workspace,\n permissionMode: \"bypassPermissions\" as any,\n },\n })\n\n for await (const message of q) {\n if (message.type === \"result\" && message.subtype === \"success\") {\n content = message.result || \"\"\n }\n }\n\n return {\n content,\n duration: Date.now() - start,\n }\n } catch (error: any) {\n return {\n content: \"\",\n ...buildErrorEnvelope(`SDK error: ${error.message}`),\n duration: Date.now() - start,\n }\n }\n}\n\n/**\n * Execute a task using agentx's own orchestrator (tier: \"orchestrator\").\n *\n * When `onDelta` is supplied, switches from `generate()` (single-shot) to\n * `generateStream()` and forwards text deltas as they arrive. This is what\n * lets the daemon's POST /chat?stream=true actually stream Claude/DeepSeek\n * tokens — without it, the orchestrator returned only after the agentic\n * loop finished and SSE clients saw `role` then `stop` with no content\n * between (the failure mode that surfaced on the acme-public smoke test\n * after the tier switch).\n */\n/** Side-channel for reasoning/thinking chunks — distinct from\n * StreamCallback so callers can route them to a different UI lane\n * (e.g. a collapsible thinking block on example.com, a dimmed terminal\n * block in the dashboard) without polluting the visible response. */\nexport type ThinkingCallback = (text: string) => void\n\nexport async function executeOrchestrator(\n agent: AgentDef,\n task: AgentTask,\n apiKey?: string,\n historyContext?: string,\n onDelta?: StreamCallback,\n providerOpts?: { thinking?: boolean },\n onThinking?: ThinkingCallback,\n abortSignal?: AbortSignal,\n onEvent?: (event: any) => void,\n): Promise<AgentResponse> {\n const start = Date.now()\n\n // Wall-clock cap. Every other tier (claude-code, codex-cli) already\n // honours agent.maxExecutionMinutes; without it on the orchestrator\n // path a hung provider stream or stuck tool keeps the runningTask\n // alive forever — the registry's finally-block only fires when this\n // function returns, and operator Stop relies on the same return.\n // Combine the operator's abortSignal with a timer so EITHER source\n // forces the loop to bail and the registry to clean up.\n const timeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const combined = new AbortController()\n let timedOut = false\n const onOperatorAbort = () => {\n const reason = (abortSignal!.reason as any)?.message\n || (typeof abortSignal!.reason === \"string\" ? abortSignal!.reason : \"task cancelled by operator\")\n try { combined.abort(new Error(reason)) } catch { /* */ }\n }\n if (abortSignal) {\n if (abortSignal.aborted) onOperatorAbort()\n else abortSignal.addEventListener(\"abort\", onOperatorAbort, { once: true })\n }\n const wallClockTimer = setTimeout(() => {\n timedOut = true\n try { combined.abort(new Error(`orchestrator timed out after ${Math.round(timeoutMs / 60_000)}m`)) } catch { /* */ }\n }, timeoutMs)\n ;(wallClockTimer as any).unref?.()\n\n const timeoutEnvelope = (): AgentResponse => ({\n content: \"\",\n ...buildErrorEnvelope(`Orchestrator timed out after ${Math.round(timeoutMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`),\n duration: Date.now() - start,\n })\n\n try {\n const providerName = agent.provider || \"claude-code\"\n\n // Build full prompt with context (landscape, memory, patterns, etc.)\n const fullTask = historyContext\n ? `${historyContext}\\n\\n${task.message}`\n : task.message\n\n // Enable acme workspace tools (list_projects, create_task, etc.)\n // when the channel is web-chat AND the sender is an acme user_id\n // UUID. The ToolExecutor reads the bearer from env at dispatch\n // time — it never enters the agent's prompt context. Non-acme\n // callers (telegram numeric ids, mesh, crons) get no acmeContext\n // so the tools are absent from their catalog entirely.\n const sender = task.context?.sender\n const looksUuid =\n typeof sender === \"string\" &&\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sender)\n const acmeContext =\n task.context?.channel === \"web-chat\" && looksUuid\n ? { userId: sender, agentId: task.agentId || \"acme-public\" }\n : undefined\n\n // MCP servers declared on this agent (including codegraph when\n // codegraph=true) — agentx already syncs these to .mcp.json for\n // CLI-backed tiers, but the orchestrator's in-process loop has its\n // own hand-rolled catalog and never reads that file. startMcpPool\n // inside generate()/generateStream() boots them as stdio children\n // and exposes their tools to the loop.\n const mcpServers = effectiveMcpConfig(agent)\n const hasMcp = Object.keys(mcpServers).length > 0\n\n const baseOpts = {\n task: fullTask,\n cwd: agent.workspace,\n provider: providerName as any,\n model: task.model || agent.model,\n apiKey,\n overwrite: true,\n interactive: false,\n context7: false,\n acmeContext,\n providerOpts,\n abortSignal: combined.signal,\n mcpServers: hasMcp ? mcpServers : undefined,\n }\n\n if (onDelta) {\n const { generateStream } = await import(\"@/agent\")\n let content = \"\"\n let tokensUsed: number | undefined\n let inputTokens: number | undefined\n let outputTokens: number | undefined\n let errorMsg: string | undefined\n for await (const event of generateStream(baseOpts)) {\n // Forward tool_call/tool_result (and a few other signal events)\n // to the caller-supplied onEvent so the daemon's SSE writer can\n // surface \"calling wiki_search…\" → \"✓ wiki_search done\" badges\n // on the jort-wiki chat without parsing internal stream-json.\n if (onEvent) {\n try { onEvent(event) } catch { /* subscriber crashed */ }\n }\n if (event.type === \"text_delta\") {\n content += event.text\n onDelta(event.text, content)\n } else if (event.type === \"thinking_delta\") {\n // Route thinking to the dedicated side-channel so the chat\n // endpoint can emit `delta.reasoning_content` (DeepSeek's\n // own convention) and the dashboard modal can render it in\n // a distinct lane. Keeping it out of onDelta is critical:\n // the chat SSE consumer (example.com) treats `delta.content`\n // as visible response text, so leaking thinking there\n // floods the chat with `💭 the / 💭 user / …` per token.\n if (event.text && onThinking) onThinking(event.text)\n } else if (event.type === \"generate_result\") {\n // Final canonical result — generate_result.content may include\n // text the provider emitted as a single payload alongside\n // tool_use, plus any files/followUp summarisation. Prefer it\n // when it diverges from our running accumulation.\n if (event.result.content && event.result.content !== content) {\n const trailing = event.result.content.slice(content.length)\n if (trailing) {\n content = event.result.content\n onDelta(trailing, content)\n } else {\n content = event.result.content\n }\n }\n tokensUsed = event.result.tokensUsed\n // Pick up split tokens so the SSE usage chunk is accurate\n // — same shape as the non-streaming path below.\n if (event.result.inputTokens != null) inputTokens = event.result.inputTokens\n if (event.result.outputTokens != null) outputTokens = event.result.outputTokens\n } else if (event.type === \"done\") {\n // Non-agentic path emits `done` with the full result.\n if (event.result.content && event.result.content !== content) {\n const trailing = event.result.content.slice(content.length)\n if (trailing) {\n content = event.result.content\n onDelta(trailing, content)\n } else {\n content = event.result.content\n }\n }\n if (event.result.tokensUsed != null) tokensUsed = event.result.tokensUsed\n } else if (event.type === \"error\") {\n errorMsg = event.error\n }\n }\n if (errorMsg && !content) {\n // Wall-clock cap fired — surface as a `timeout` envelope so\n // operators see the same \"timed out after Xm\" message every\n // other tier produces.\n if (timedOut) return timeoutEnvelope()\n // Operator-cancel: surface the same `errorKind: \"cancelled\"`\n // envelope claude-code uses, so the router / channel layer\n // suppresses the error echo and the registry's finally block\n // can route follow-ups normally.\n if (errorMsg === \"cancelled\" || abortSignal?.aborted || combined.signal.aborted) {\n return {\n content: \"\",\n error: \"task cancelled by operator\",\n errorKind: \"cancelled\",\n duration: Date.now() - start,\n }\n }\n return {\n content: \"\",\n ...buildErrorEnvelope(`Orchestrator error: ${errorMsg}`),\n duration: Date.now() - start,\n }\n }\n // Fallback estimate so the SSE usage chunk on the daemon /chat\n // path always has SOME values for example.com's cost-based billing\n // to work against (the floor still protects against undercharge).\n if ((inputTokens === undefined || outputTokens === undefined) && tokensUsed) {\n inputTokens = Math.round(tokensUsed * 0.7)\n outputTokens = tokensUsed - inputTokens\n }\n return {\n content: content || \"Done.\",\n tokensUsed,\n usage: (inputTokens !== undefined || outputTokens !== undefined)\n ? { inputTokens: inputTokens || 0, outputTokens: outputTokens || 0, cacheReadTokens: 0, cacheCreateTokens: 0 }\n : undefined,\n duration: Date.now() - start,\n }\n }\n\n // Non-streaming path — unchanged from the original orchestrator\n // behaviour so existing callers (telegram, mesh, crons) keep working.\n const { generate } = await import(\"@/agent\")\n const result = await generate(baseOpts)\n // Surface split tokens on `usage` so the daemon /chat handler (and\n // its example.com voice forwarder) can do cost-based billing instead\n // of the floor-only fallback. Falls back to a 30/70 estimate when\n // the provider only reports a combined total (CLI tier, legacy\n // loop) so downstream billing never sees zeros for a real turn.\n let inputTokens = result.inputTokens\n let outputTokens = result.outputTokens\n if ((inputTokens === undefined || outputTokens === undefined) && result.tokensUsed) {\n const total = result.tokensUsed\n inputTokens = Math.round(total * 0.7)\n outputTokens = total - inputTokens\n }\n return {\n content: result.content || \"Done.\",\n tokensUsed: result.tokensUsed,\n usage: (inputTokens !== undefined || outputTokens !== undefined)\n ? { inputTokens: inputTokens || 0, outputTokens: outputTokens || 0, cacheReadTokens: 0, cacheCreateTokens: 0 }\n : undefined,\n duration: Date.now() - start,\n }\n } catch (error: any) {\n if (timedOut) return timeoutEnvelope()\n // `generate()` (non-streaming) throws \"cancelled\" from the agentic\n // loop's iteration_start check when the combined signal fires.\n if (error?.message === \"cancelled\" || abortSignal?.aborted || combined.signal.aborted) {\n return {\n content: \"\",\n error: \"task cancelled by operator\",\n errorKind: \"cancelled\",\n duration: Date.now() - start,\n }\n }\n return {\n content: \"\",\n ...buildErrorEnvelope(`Orchestrator error: ${error.message}`),\n duration: Date.now() - start,\n }\n } finally {\n clearTimeout(wallClockTimer)\n if (abortSignal) abortSignal.removeEventListener(\"abort\", onOperatorAbort)\n }\n}\n\n/**\n * Route a task to the correct execution tier.\n */\n/**\n * Execute a task by reusing a persistent claude subprocess held in the\n * ProcessRegistry. Driven over `--input-format stream-json` on stdin;\n * the same parser path handles output. See\n * docs/architecture/persistent-claude-process.md for the design.\n *\n * Forwards every stream event to `onEvent` so the existing trace\n * capture site (registry.ts emitTraceStepsFromStreamEvent) continues\n * to populate task_trace_steps unchanged.\n *\n * Returns null when the registry can't allocate a slot (cap exceeded,\n * binary not installed, etc.) — caller falls back to spawn-per-task.\n */\n/**\n * Background work a claude run still has open (#892): tasks the CLI\n * announced with `system/task_started` and has not yet closed with\n * `system/task_notification`. A `result` while any is open ends a turn,\n * not the run: the CLI stays up and runs a new turn once the work ends.\n * A task the CLI marks as not backgrounded (a subagent it waits for)\n * ends inside its turn and is not counted.\n */\nexport class BackgroundTasks {\n private open = new Set<string>()\n\n observe(event: any): void {\n if (event?.type !== \"system\" || typeof event.task_id !== \"string\") return\n if (event.subtype === \"task_started\" && event.is_backgrounded !== false) this.open.add(event.task_id)\n else if (event.subtype === \"task_notification\") this.open.delete(event.task_id)\n }\n\n get pending(): number {\n return this.open.size\n }\n}\n\n/** Grace the CLI gets to exit on its own after its terminal event before\n * the reply proceeds without it. */\nconst POST_RESULT_EXIT_GRACE_MS = 1_000\n\n/**\n * Resolve with the process result when it exits, or with a synthetic clean\n * exit once the terminal event has been seen and the grace has passed. The\n * child keeps running unattended in the latter case; it exits on its own.\n */\nasync function settleAfterResult(proc: PromiseLike<any>, resultSeen: Promise<void>): Promise<any> {\n const exited = Promise.resolve(proc).then((r) => ({ kind: \"exit\" as const, r }))\n let timer: ReturnType<typeof setTimeout> | undefined\n const graced = resultSeen.then(() => new Promise<{ kind: \"grace\" }>((res) => {\n timer = setTimeout(() => res({ kind: \"grace\" }), POST_RESULT_EXIT_GRACE_MS)\n timer.unref?.()\n }))\n const first = await Promise.race([exited, graced])\n if (timer) clearTimeout(timer)\n if (first.kind === \"exit\") return first.r\n exited.catch(() => {})\n return { exitCode: 0, stdout: \"\", stderr: \"\", detachedAfterResult: true }\n}\n\nasync function executeClaudeCodePersistent(\n agent: AgentDef,\n task: AgentTask,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n abortSignal?: AbortSignal,\n onDelta?: StreamCallback,\n): Promise<AgentResponse | null> {\n const registry = getProcessRegistry()\n if (!registry) return null\n\n const start = Date.now()\n const { channel, chatId } = warmProcessChat(task.context)\n const key: ProcessKey = { agentId: task.agentId, channel, chatId }\n\n // Improvement plan #8 — caller-driven session reset for the\n // persistent path. Kill any existing handle BEFORE acquire so the\n // next call spawns a brand-new subprocess instead of reusing the\n // warm one. Awaiting the kill keeps acquire-after-kill race-free:\n // the registry entry is removed inside kill() before resolution.\n if (task.freshSession) {\n try { await registry.kill(key, \"freshSession=true\") } catch { /* observability best-effort */ }\n }\n\n let handle\n let wasFreshSpawn = false\n try {\n const before = registry.list().length\n handle = registry.acquire(key, {\n agentId: task.agentId,\n channel,\n chatId,\n workspace: agent.workspace,\n model: task.model || agent.model,\n permissionMode: agent.permissionMode,\n billing: agent.billing,\n systemPromptAppend: task.systemPromptAppend,\n resumeSessionId,\n extraArgs: task.claudeArgs,\n })\n wasFreshSpawn = registry.list().length > before\n } catch (e: any) {\n if (e instanceof RegistryCapExceeded) return null\n // Anything else — return null too; caller falls back. Persistent\n // path must never be more brittle than the spawn-per-task one.\n return null\n }\n // Audit log only on fresh spawns — reused handles already audited at\n // their original spawn. Cuts noise on chats that turn frequently.\n if (wasFreshSpawn) {\n logClaudeSpawn(task.agentId, agent, task.model, resumeSessionId, \"persistent\")\n }\n\n const prompt = buildPrompt(agent, task, historyContext)\n\n // Accumulate response data from the stream events. Mirrors what\n // parseClaudeJsonOutput does for the spawn-per-task JSON envelope.\n let finalText = \"\"\n let finalError: string | undefined\n let finalErrorKind: FriendlyError[\"kind\"] | undefined\n let usage: TokenUsage | undefined\n let contextTokens: number | undefined\n let numTurns: number | undefined\n let billedModel: string | undefined\n let sessionId: string | undefined\n // Text already forwarded to onDelta. Each assistant event carries one\n // message; a snapshot that extends the current message yields only its\n // suffix, and a new message after tool use starts a new paragraph.\n let streamedText = \"\"\n let messageText = \"\"\n const emitText = (text: string) => {\n if (!onDelta || !text) return\n let delta: string\n if (messageText && text.startsWith(messageText)) {\n delta = text.slice(messageText.length)\n } else {\n delta = streamedText && !streamedText.endsWith(\"\\n\") ? `\\n\\n${text}` : text\n }\n messageText = text\n if (!delta) return\n streamedText += delta\n try { onDelta(delta, streamedText) } catch { /* caller crash must not break the turn */ }\n }\n\n // Operator cancellation for the persistent path. Killing the handle\n // closes its stdio, which terminates the async iterator below; we then\n // emit a \"cancelled\" envelope just like the spawn-per-task paths.\n let cancelled = false\n const onAbort = () => {\n cancelled = true\n void registry.kill(key, \"operator-cancel\").catch(() => {})\n }\n if (abortSignal) {\n if (abortSignal.aborted) onAbort()\n else abortSignal.addEventListener(\"abort\", onAbort, { once: true })\n }\n\n let sawEvent = false\n try {\n const turnBudgetMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n for await (const evt of handle.runTurn({ message: prompt, taskId: task.taskId ?? \"unknown\", deadlineMs: turnBudgetMs })) {\n sawEvent = true\n // Forward to existing onEvent — trace step emitter, dashboard\n // formatter, etc. all keep working without changes.\n if (onEvent) {\n try { onEvent(evt.raw) } catch { /* observability best-effort */ }\n }\n\n if (evt.type === \"assistant\") {\n // Assistant snapshots are cumulative — overwrite, don't append.\n const blocks = ((evt.raw as any).message?.content ?? []) as Array<{ type: string; text?: string }>\n for (const block of blocks) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n finalText = block.text\n emitText(block.text)\n } else if (block.type === \"tool_use\") {\n messageText = \"\"\n }\n }\n // Per-call usage on the assistant envelope: the last call's\n // (input + cache) tokens = end-of-turn context size. See\n // AgentResponse.contextTokens for why this, not the cumulative\n // result usage, drives session rotation.\n const u = (evt.raw as any).message?.usage\n if (u) {\n const callContext =\n (u.input_tokens || 0) +\n (u.cache_read_input_tokens || 0) +\n (u.cache_creation_input_tokens || 0)\n if (callContext > 0) contextTokens = callContext\n }\n }\n\n if (evt.type === \"result\") {\n const r = evt.raw as any\n if (r.is_error) {\n const env = buildErrorEnvelope(typeof r.result === \"string\" ? r.result : \"claude error\")\n finalError = env.error\n finalErrorKind = env.errorKind\n } else if (typeof r.result === \"string\" && r.result.length > 0) {\n // Final result text (sometimes more authoritative than the last\n // assistant snapshot, especially for very short responses).\n finalText = r.result\n if (!streamedText) emitText(r.result)\n }\n if (typeof r.session_id === \"string\") sessionId = r.session_id\n if (r.usage) {\n usage = {\n inputTokens: r.usage.input_tokens || 0,\n outputTokens: r.usage.output_tokens || 0,\n cacheReadTokens: r.usage.cache_read_input_tokens || 0,\n cacheCreateTokens: r.usage.cache_creation_input_tokens || 0,\n }\n }\n if (typeof r.num_turns === \"number\") numTurns = r.num_turns\n if (typeof (r.message?.model) === \"string\") billedModel = r.message.model\n else if (typeof r.model === \"string\") billedModel = r.model\n else billedModel = primaryModelFromUsage(r.modelUsage) || billedModel\n }\n }\n } catch (e: any) {\n if (cancelled) {\n finalError = \"task cancelled by operator\"\n finalErrorKind = \"cancelled\"\n } else if (!sawEvent) {\n // Turn never started (handle killed between acquire and the first\n // write, stdin gone, etc.) — nothing streamed, so retrying is\n // side-effect-free. Return null and let the caller fall back to\n // spawn-per-task; an infra race must not surface as a task failure.\n console.error(`[runtime] persistent handle unusable before first event (${e?.message || e}); falling back to spawn-per-task`)\n registry.release(key, { kill: true, reason: \"pre-turn failure\" })\n if (abortSignal) abortSignal.removeEventListener(\"abort\", onAbort)\n return null\n } else if (e instanceof TurnInterrupted) {\n // Killed from outside the turn (daemon shutdown, eviction). Not a\n // timeout: raising maxExecutionMinutes would not have saved it.\n finalError = `Claude Code was stopped mid-turn (${e.reason}), before its time limit`\n finalErrorKind = \"interrupted\"\n } else if (e instanceof TurnDeadlineExceeded) {\n const env = buildErrorEnvelope(`Claude Code timed out after ${Math.round(e.budgetMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || task.agentId}\" if tasks need longer.`)\n finalError = env.error\n finalErrorKind = env.errorKind\n } else {\n const env = buildErrorEnvelope(`persistent claude process error: ${e?.message || String(e)}`)\n finalError = env.error\n finalErrorKind = env.errorKind\n }\n } finally {\n if (abortSignal) abortSignal.removeEventListener(\"abort\", onAbort)\n }\n\n if (cancelled && !finalError) {\n finalError = \"task cancelled by operator\"\n finalErrorKind = \"cancelled\"\n }\n\n return {\n content: cancelled ? \"\" : finalText,\n error: finalError,\n errorKind: finalErrorKind,\n duration: Date.now() - start,\n usage,\n contextTokens,\n numTurns,\n billedModel,\n claudeSessionId: sessionId,\n }\n}\n\n/** Guard flags for a claude spawn: the autonomy hook and the person-limit\n * hook, in one --settings. Guarded tasks always carry a taskId by the time\n * they get here (executeRestrictedTask assigns one). */\nfunction restrictedClaudeArgs(task: AgentTask): { args: string[] } | { error: string } {\n const restrictedLevel = isRestricted(task.autonomy)\n if (!restrictedLevel && !task.personLimits) return { args: [] }\n if (!task.taskId) return { error: \"a guarded task requires a task id to enforce\" }\n const autonomy = restrictedLevel ? autonomyClaudeArgs(task.autonomy, task.agentId, task.taskId) : { args: [] as string[] }\n if (\"error\" in autonomy || !task.personLimits) return autonomy\n return withPersonLimitHook(autonomy.args, task.taskId, task.agentId)\n}\n\n/** True when a task must run under a per-spawn guard hook. */\nfunction isGuarded(task: AgentTask): boolean {\n return isRestricted(task.autonomy) || !!task.personLimits\n}\n\n/**\n * Run a task under a restricted autonomy level (report | propose), or for\n * a person with tool and skill limits (#379), or both.\n *\n * Always a fresh spawn-per-task claude process carrying the guard hook:\n * a warm persistent process was spawned without it (and serves other turns\n * of the chat), so reusing it would silently skip enforcement. Session\n * continuity survives through --resume. Tiers that cannot carry the hook\n * refuse rather than run at full power.\n */\nasync function executeRestrictedTask(\n agent: AgentDef,\n task: AgentTask,\n onDelta?: StreamCallback,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n abortSignal?: AbortSignal,\n): Promise<AgentResponse> {\n const level = isRestricted(task.autonomy) ? task.autonomy : undefined\n const unsupported = (level && autonomyUnsupported(level, agent.tier)) || (task.personLimits && personLimitsUnsupported(agent.tier))\n if (unsupported) return { content: \"\", error: unsupported, ...(level ? { autonomy: level } : {}) }\n const taskId = task.taskId || `guarded-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`\n const restricted: AgentTask = { ...task, taskId, message: level ? `${autonomyBrief(level)}\\n\\n${task.message}` : task.message }\n const why = [level ? `level=${level}` : \"\", task.personLimits ? `person=${task.personLimits.personId}` : \"\"].filter(Boolean).join(\" \")\n process.stderr.write(`[claude-guarded] agent=${task.agentId} ${why} task=${taskId} via=spawn (persistent process bypassed)\\n`)\n if (task.personLimits) registerPersonLimits(taskId, task.personLimits)\n try {\n const response = onDelta\n ? await executeClaudeCodeStreaming(agent, restricted, onDelta, historyContext, resumeSessionId, onEvent, abortSignal)\n : await executeClaudeCode(agent, restricted, historyContext, resumeSessionId, abortSignal)\n if (!level) return response\n const blocks = takeAutonomyBlocks(taskId)\n return { ...response, autonomy: level, ...(blocks.length ? { autonomyBlocks: blocks } : {}) }\n } finally {\n if (task.personLimits) clearPersonLimits(taskId)\n }\n}\n\nexport async function executeTask(\n agent: AgentDef,\n task: AgentTask,\n providers: Record<string, { apiKey?: string }>,\n onDelta?: StreamCallback,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n abortSignal?: AbortSignal,\n onThinking?: ThinkingCallback,\n): Promise<AgentResponse> {\n if (isGuarded(task)) {\n return executeRestrictedTask(agent, task, onDelta, historyContext, resumeSessionId, onEvent, abortSignal)\n }\n switch (agent.tier) {\n case \"claude-code\":\n // Persistent-process path (improvement plan #5, persistent flavor).\n // Returns null when the registry isn't available or can't allocate\n // a slot — we fall through to the legacy spawn-per-task path so\n // a registry-only failure can never block dispatch.\n if (agent.persistentProcess) {\n const persistent = await executeClaudeCodePersistent(agent, task, historyContext, resumeSessionId, onEvent, abortSignal, onDelta)\n if (persistent !== null) return persistent\n }\n if (onDelta) {\n return executeClaudeCodeStreaming(agent, task, onDelta, historyContext, resumeSessionId, onEvent, abortSignal)\n }\n return executeClaudeCode(agent, task, historyContext, resumeSessionId, abortSignal)\n\n case \"codex-cli\":\n if (agent.persistentProcess) {\n try {\n const result = await codexProcessPool.run({\n key: JSON.stringify([task.agentId, task.context?.channel, task.context?.chatId]),\n cwd: agent.workspace, env: buildRuntimeEnv(agent, task),\n args: buildCodexAgentxMcpArgs(), model: task.model || agent.model,\n bypass: agent.permissionMode === \"bypassPermissions\",\n prompt: buildCodexPrompt(buildPrompt(agent, task, historyContext), task.systemPromptAppend),\n resume: resumeSessionId, fresh: task.freshSession,\n timeoutMs: Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000),\n signal: abortSignal, onEvent, onDelta,\n })\n return { ...result, ...(result.error ? buildErrorEnvelope(result.error) : {}) }\n } catch (error) {\n if (!(error instanceof CodexUnavailable)) throw error\n onEvent?.({ type: \"codex.fallback\", reason: error.message })\n if (abortSignal?.aborted) return { content: \"\", error: \"task cancelled by operator\", errorKind: \"cancelled\" }\n }\n }\n if (onDelta) {\n return executeCodexCliStreaming(agent, task, onDelta, historyContext, resumeSessionId, onEvent)\n }\n return executeCodexCli(agent, task, historyContext, resumeSessionId)\n\n case \"opencode\":\n return executeOpenCodeCli(agent, task, onDelta, historyContext, resumeSessionId, onEvent, abortSignal, onThinking)\n\n case \"sdk\": {\n const providerName = agent.provider || \"claude\"\n const apiKey = providers[providerName]?.apiKey\n if (!apiKey) {\n return {\n content: \"\",\n error: `No API key for provider \"${providerName}\". Configure providers.${providerName}.apiKey`,\n }\n }\n return executeSdk(agent, task, apiKey, historyContext)\n }\n\n case \"orchestrator\": {\n const providerName = agent.provider || \"claude-code\"\n const providerCfg = providers[providerName]\n const apiKey = providerCfg?.apiKey\n return executeOrchestrator(agent, task, apiKey, historyContext, onDelta, {\n thinking: (providerCfg as any)?.thinking,\n }, onThinking, abortSignal, onEvent)\n }\n\n default:\n return {\n content: \"\",\n error: `Unknown tier: ${agent.tier}`,\n }\n }\n}\n","import { spawn, type ChildProcessWithoutNullStreams } from \"node:child_process\"\nimport { createHash } from \"node:crypto\"\nimport { createInterface } from \"node:readline\"\n\ninterface Options {\n key: string\n cwd: string\n env: NodeJS.ProcessEnv\n args: string[]\n model?: string\n bypass: boolean\n prompt: string\n resume?: string\n fresh?: boolean\n timeoutMs: number\n signal?: AbortSignal\n onEvent?: (event: any) => void\n onDelta?: (delta: string, text: string) => void\n}\ninterface Result {\n content: string\n codexSessionId?: string\n billedModel?: string\n usage?: { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreateTokens: number }\n /** Input context of the final model call, distinct from cumulative turn usage. */\n contextTokens?: number\n error?: string\n duration: number\n}\n/** Only this error permits CLI fallback: no turn has been submitted. */\nexport class CodexUnavailable extends Error {}\ntype Spawn = (options: Options) => ChildProcessWithoutNullStreams\nconst launch: Spawn = (o) => spawn(\"codex\", [\"app-server\", ...o.args], {\n cwd: o.cwd, env: o.env, stdio: [\"pipe\", \"pipe\", \"pipe\"],\n})\n\nclass Connection {\n child: ChildProcessWithoutNullStreams\n pending = new Map<number, { resolve: (v: any) => void; reject: (e: Error) => void }>()\n nextId = 0\n dead = false\n busy = false\n threadId?: string\n idle?: NodeJS.Timeout\n notify?: (method: string, params: any) => void\n failTurn?: (e: Error) => void\n constructor(o: Options, launchProcess: Spawn) {\n this.child = launchProcess(o)\n this.child.stderr.resume() // Drain without exposing credentials or prompt text in logs.\n const lines = createInterface({ input: this.child.stdout })\n lines.on(\"line\", (line) => {\n let msg: any\n try { msg = JSON.parse(line) } catch { return }\n if (msg.method && msg.id !== undefined) {\n // AgentX has no interactive approval bridge here. Fail closed.\n this.write({ id: msg.id, error: { code: -32601, message: \"Interactive requests are not supported by AgentX\" } })\n } else if (msg.id !== undefined) {\n const p = this.pending.get(msg.id)\n if (!p) return\n this.pending.delete(msg.id)\n if (msg.error) p.reject(new Error(msg.error.message || \"Codex RPC failed\"))\n else p.resolve(msg.result)\n } else if (msg.method) this.notify?.(msg.method, msg.params)\n })\n this.child.on(\"error\", (e) => this.close(e))\n this.child.stdin.on(\"error\", (e) => this.close(e))\n this.child.on(\"exit\", () => this.close(new Error(\"Codex app-server exited\")))\n }\n write(message: any) { if (!this.dead) this.child.stdin.write(JSON.stringify(message) + \"\\n\") }\n rpc(method: string, params: any): Promise<any> {\n if (this.dead) return Promise.reject(new Error(\"Codex connection closed\"))\n const id = ++this.nextId\n return new Promise((resolve, reject) => {\n this.pending.set(id, { resolve, reject })\n this.write({ id, method, params })\n })\n }\n close(error = new Error(\"Codex connection closed\")) {\n if (this.dead) return\n this.dead = true\n clearTimeout(this.idle)\n for (const p of this.pending.values()) p.reject(error)\n this.pending.clear()\n this.failTurn?.(error)\n this.child.kill(\"SIGTERM\")\n const kill = setTimeout(() => { if (this.child.exitCode === null) this.child.kill(\"SIGKILL\") }, 5000)\n kill.unref()\n }\n}\n\n/** One process per conversation and effective configuration. Busy entries are never evicted. */\nexport class CodexProcessPool {\n private entries = new Map<string, { fingerprint: string; connection: Connection }>()\n constructor(private launchProcess: Spawn = launch, private capacity = 8, private idleMs = 300_000) {}\n stop() {\n for (const { connection } of this.entries.values()) connection.close()\n this.entries.clear()\n }\n async run(o: Options): Promise<Result> {\n const start = Date.now()\n if (o.signal?.aborted) return { content: \"\", error: \"task cancelled by operator\", duration: 0 }\n // The hash includes environment (including MCP identity), but never logs its contents.\n const fingerprint = createHash(\"sha256\").update(JSON.stringify([\n o.cwd, o.model, o.bypass, o.args, Object.entries(o.env).sort(([a], [b]) => a.localeCompare(b)),\n ])).digest(\"hex\")\n let entry = this.entries.get(o.key)\n if (entry?.connection.busy) throw new CodexUnavailable(\"Conversation already running\")\n if (entry && (entry.fingerprint !== fingerprint || o.fresh || entry.connection.dead)) {\n entry.connection.close()\n this.entries.delete(o.key)\n entry = undefined\n }\n const reused = !!entry\n if (!entry) {\n if (this.entries.size >= this.capacity) {\n const idle = [...this.entries].find(([, e]) => !e.connection.busy)\n if (!idle) throw new CodexUnavailable(\"Codex warm process capacity reached\")\n idle[1].connection.close()\n this.entries.delete(idle[0])\n }\n try { entry = { fingerprint, connection: new Connection(o, this.launchProcess) } }\n catch { throw new CodexUnavailable(\"Could not start Codex app-server\") }\n this.entries.set(o.key, entry)\n }\n const c = entry.connection\n c.busy = true\n clearTimeout(c.idle)\n let submitted = false\n let content = \"\"\n let finalText: string | undefined\n let threadId: string | undefined\n let usage: Result[\"usage\"]\n let contextTokens: number | undefined\n let firstOutput = false\n const abort = () => c.close(new Error(\"task cancelled by operator\"))\n o.signal?.addEventListener(\"abort\", abort, { once: true })\n const deadline = setTimeout(() => c.close(new Error(\"Codex turn timed out\")), o.timeoutMs)\n const startup = setTimeout(() => c.close(new Error(\"Codex initialization timed out\")), 15_000)\n try {\n if (!reused) {\n await c.rpc(\"initialize\", { clientInfo: { name: \"agentx\", version: \"1.0.0\" }, capabilities: {} })\n c.write({ method: \"initialized\", params: {} })\n }\n // Rejoin from the registry's session ID every turn. Without a resume ID,\n // start a new thread even when reusing a process: never infer conversation history.\n if (c.threadId && c.threadId !== o.resume) {\n await c.rpc(\"thread/unsubscribe\", { threadId: c.threadId })\n c.threadId = undefined\n }\n const params = { cwd: o.cwd, model: o.model, approvalPolicy: \"never\",\n sandbox: o.bypass ? \"danger-full-access\" : \"workspace-write\" }\n const thread = await c.rpc(o.resume && !o.fresh ? \"thread/resume\" : \"thread/start\",\n { ...params, ...(o.resume && !o.fresh ? { threadId: o.resume } : {}) })\n threadId = thread.thread.id\n c.threadId = threadId\n clearTimeout(startup)\n o.onEvent?.({ type: \"codex.ready\", reused, startupMs: Date.now() - start, model: o.model })\n const completed = new Promise<void>((resolve, reject) => {\n c.failTurn = reject\n c.notify = (method, p) => {\n if (p?.threadId !== threadId) return\n const item = p.item\n const itemTypes: Record<string, string> = {\n agentMessage: \"agent_message\", commandExecution: \"command_execution\",\n fileChange: \"file_change\", mcpToolCall: \"mcp_tool_call\",\n }\n o.onEvent?.({ ...p, type: method.replace(\"/\", \".\"),\n ...(item ? { item: { ...item, type: itemTypes[item.type] || item.type } } : {}) })\n if (method === \"item/completed\" && item?.type === \"agentMessage\" && item.phase === \"final_answer\") {\n finalText = item.text\n }\n if (method === \"item/agentMessage/delta\") {\n if (!firstOutput) {\n firstOutput = true\n o.onEvent?.({ type: \"codex.first_output\", elapsedMs: Date.now() - start, reused })\n }\n content += p.delta\n o.onDelta?.(p.delta, content)\n }\n if (method === \"thread/tokenUsage/updated\") {\n // `last` is ONE model call; a tool-using turn makes many, so sum\n // them (`total` spans the whole thread, which a resumed session\n // shares with earlier turns). OpenAI's inputTokens includes the\n // cached part; TokenUsage keeps them apart, Anthropic-style.\n const u = p.tokenUsage.last\n contextTokens = u.inputTokens\n const cached = u.cachedInputTokens || 0\n usage = {\n inputTokens: (usage?.inputTokens || 0) + Math.max(0, (u.inputTokens || 0) - cached),\n outputTokens: (usage?.outputTokens || 0) + (u.outputTokens || 0),\n cacheReadTokens: (usage?.cacheReadTokens || 0) + cached,\n cacheCreateTokens: (usage?.cacheCreateTokens || 0) + (u.cacheWriteInputTokens || 0),\n }\n }\n if (method === \"turn/completed\") {\n if (p.turn.status === \"completed\") resolve()\n else reject(new Error(p.turn.error?.message || `Codex turn ${p.turn.status}`))\n }\n }\n })\n // Attach a rejection handler before dispatch: process exit can precede the RPC response.\n completed.catch(() => {})\n submitted = true\n await c.rpc(\"turn/start\", { threadId, input: [{ type: \"text\", text: o.prompt, text_elements: [] }] })\n await completed\n return { content: finalText ?? content, codexSessionId: threadId, billedModel: o.model, usage, contextTokens, duration: Date.now() - start }\n } catch (e: any) {\n c.close()\n if (!submitted && !o.signal?.aborted) throw new CodexUnavailable(e.message)\n return { content, codexSessionId: threadId, usage, contextTokens, error: e.message, duration: Date.now() - start }\n } finally {\n clearTimeout(startup)\n clearTimeout(deadline)\n o.signal?.removeEventListener(\"abort\", abort)\n c.notify = undefined\n c.failTurn = undefined\n c.busy = false\n if (c.dead) {\n if (this.entries.get(o.key)?.connection === c) this.entries.delete(o.key)\n }\n else {\n c.idle = setTimeout(() => { c.close(); this.entries.delete(o.key) }, this.idleMs)\n c.idle.unref()\n }\n }\n }\n}\nexport const codexProcessPool = new CodexProcessPool()\n","// --- Translate Anthropic API / Claude Code errors into human-friendly text ---\n//\n// Raw errors from the Claude Code CLI look like:\n// API Error: 400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\n// \"message\":\"You're out of extra usage. Add more at ...\"}}\n// These are painful to surface in a chat reply or the dashboard. This module\n// parses the structured bit and returns a short, actionable sentence.\n\nexport interface FriendlyError {\n /** One-line, operator-facing message suitable for a dashboard card or chat reply. */\n message: string\n /** Optional next step the user can take. */\n fix?: string\n /** Machine-readable error category for routing / retries. */\n kind:\n | \"out_of_credits\"\n | \"overage_disabled\"\n | \"usage_cap\"\n | \"auth\"\n | \"permission\"\n | \"rate_limit\"\n | \"overloaded\"\n | \"context_too_large\"\n | \"invalid_request\"\n | \"upstream_api\"\n | \"timeout\"\n | \"cancelled\"\n | \"interrupted\"\n /** Ended by a stop signal; its resume plan is saved (#857). */\n | \"stopped\"\n | \"unknown\"\n /** Whether this error is transient — a retry has a reasonable chance of succeeding. */\n retryable: boolean\n /** Source error text retained for logs / debugging. */\n raw?: string\n}\n\n/**\n * Best-effort translator. If we recognise the shape we return a structured\n * FriendlyError; otherwise we hand back the original text wrapped as \"unknown\".\n * Never throws — errors are data in here.\n */\nexport function friendlyModelError(raw: string | undefined | null): FriendlyError {\n const source = (raw || \"\").trim()\n if (!source) {\n return { message: \"Unknown error from the model.\", kind: \"unknown\", retryable: false }\n }\n\n // Timeouts are wrapped by us upstream; pass through.\n if (/timed out after/i.test(source)) {\n return { message: source.slice(0, 300), kind: \"timeout\", retryable: true, raw: source }\n }\n\n // Claude Code CLI lost its OAuth session — \"Not logged in · Please run /login\".\n // Usually a transient refresh race when multiple agents share one credentials\n // file on the same host; occasionally the token is genuinely stale. Either way\n // it comes through as plain CLI text with no JSON envelope, so match it before\n // the parse step below.\n if (/not logged in|please run \\/login/i.test(source)) {\n return {\n kind: \"auth\",\n retryable: true,\n message: \"The agent's Claude Code session is logged out.\",\n fix: \"Usually a transient OAuth refresh race under concurrent load — retry in a moment. If it persists, run `/login` in the agent's workspace.\",\n raw: source,\n }\n }\n\n // User-set monthly /usage cap on the Claude Code subscription (the\n // `/usage` slash-command limit). Surfaces as \"You have reached your\n // specified API usage limits. You will regain access on <ISO date>\" —\n // wording introduced by Claude CLI v2.1.x. Distinct from\n // `overage_disabled` (which is the org-level toggle, retryable as soon\n // as someone flips it) and `out_of_credits` (programmatic API key\n // billing). The operator fix is to raise the cap at\n // claude.ai/settings/usage OR wait for the cycle reset; not retryable\n // until either happens.\n if (/specified API usage limits/i.test(source)) {\n const dateMatch = source.match(/regain access on ([^.\\n]+?)(?:\\.|$)/i)\n const when = dateMatch ? ` Resets ${dateMatch[1].trim()}.` : \"\"\n return {\n kind: \"usage_cap\",\n retryable: false,\n message: `Claude Code subscription /usage cap exhausted.${when}`,\n fix: \"Raise the cap at https://claude.ai/settings/usage (this is the user-set monthly limit on the OAuth account, not Anthropic API billing). If the daemon is using a different account than expected, check CLAUDE_CODE_OAUTH_TOKEN / ANTHROPIC_API_KEY in the spawn env — capped keys leak the same error.\",\n raw: source,\n }\n }\n\n // Plain-text Max-plan overage signal that may arrive without a JSON\n // envelope (the structured parse below also catches it via errMessage).\n // Order matters: this case must fire BEFORE the plain-text `out_of_credits`\n // branch — \"out of extra usage\" matches both regexes, but `overage_disabled`\n // is the more specific (and retryable) classification.\n if (/out of extra usage/i.test(source)) {\n return {\n kind: \"overage_disabled\",\n retryable: true,\n message: \"Claude Max-plan overage is unavailable (disabled or depleted at the org level).\",\n fix: \"Enable overage / extra usage at https://claude.ai/settings/usage (this is the subscription account's toggle — not Anthropic API billing). Warm sessions can still succeed via prompt cache; cold dispatches will keep failing until overage is re-enabled.\",\n raw: source,\n }\n }\n\n // Plain-text credit-balance signal (programmatic ANTHROPIC_API_KEY accounts)\n // that may arrive without a JSON envelope — some CLI paths surface the API\n // message verbatim. Matched here so we route to `out_of_credits` even when\n // the structured parse below would otherwise fall through to `unknown`.\n if (/credit balance (is )?too low|out of credit|credit(s)? exhausted|insufficient credit/i.test(source)) {\n return {\n kind: \"out_of_credits\",\n retryable: false,\n message: \"The agent is out of Anthropic credits.\",\n fix: \"Top up at https://console.anthropic.com/settings/billing (or claude.ai/settings/usage for Max subscribers), then retry.\",\n raw: source,\n }\n }\n\n // Look for the standard Anthropic JSON envelope anywhere in the string —\n // the CLI prefixes it with \"API Error: <status>\".\n const jsonMatch = source.match(/\\{.*\\}\\s*$/s) || source.match(/\\{[\\s\\S]*\\}/)\n let parsed: any\n if (jsonMatch) {\n try { parsed = JSON.parse(jsonMatch[0]) } catch { /* raw text only */ }\n }\n\n const inner = parsed?.error || parsed\n const errType: string | undefined = typeof inner?.type === \"string\" ? inner.type : undefined\n const errMessage: string | undefined = typeof inner?.message === \"string\" ? inner.message : undefined\n\n const statusMatch = source.match(/API Error:\\s*(\\d{3})/i)\n const status = statusMatch ? parseInt(statusMatch[1], 10) : undefined\n\n // Max-plan overage disabled — the most common root cause on subscription\n // accounts. Anthropic returns \"You're out of extra usage\" when a request\n // would spill past the regular Max allotment AND overage (\"extra usage\")\n // is disabled at the org level. Operator fix is flipping a toggle on\n // claude.ai, not topping up API credits — so route this to its own kind\n // with a retryable:true hint (re-enabling overage unblocks immediately).\n if (errMessage && /out of extra usage/i.test(errMessage)) {\n return {\n kind: \"overage_disabled\",\n retryable: true,\n message: \"Claude Max-plan overage is unavailable (disabled or depleted at the org level).\",\n fix: \"Enable overage / extra usage at https://claude.ai/settings/usage (this is the subscription account's toggle — not Anthropic API billing). Warm sessions can still succeed via prompt cache; cold dispatches will keep failing until overage is re-enabled.\",\n raw: source,\n }\n }\n\n // Real API credit exhaustion (programmatic ANTHROPIC_API_KEY accounts).\n // \"Credit balance is too low\" is what Anthropic's API actually returns on\n // depletion — earlier wording variants are kept for back-compat.\n if (errMessage && /out of (extra )?usage|out of credit|credit(s)? exhausted|insufficient credit|credit balance (is )?too low|low credit balance/i.test(errMessage)) {\n return {\n kind: \"out_of_credits\",\n retryable: false,\n message: \"The agent is out of Anthropic credits.\",\n fix: \"Top up at https://console.anthropic.com/settings/billing (or claude.ai/settings/usage for Max subscribers), then retry.\",\n raw: source,\n }\n }\n\n // Context-length issues.\n if (errMessage && /context|prompt is too long|too many tokens|maximum context length/i.test(errMessage)) {\n return {\n kind: \"context_too_large\",\n retryable: false,\n message: \"The conversation has grown past what the model can hold.\",\n fix: \"Clear session history or switch the agent to a bigger context model.\",\n raw: source,\n }\n }\n\n // Authentication & API key issues.\n if (errType === \"authentication_error\" || (errMessage && /authentication|invalid.*api.*key|no api key|not authenticated/i.test(errMessage))) {\n return {\n kind: \"auth\",\n retryable: false,\n message: \"Authentication failed talking to Anthropic.\",\n fix: \"Check ANTHROPIC_API_KEY in .env (or the Claude Code login for claude-code tier agents).\",\n raw: source,\n }\n }\n\n // Permission / forbidden.\n if (errType === \"permission_error\" || status === 403) {\n return {\n kind: \"permission\",\n retryable: false,\n message: errMessage || \"The API key doesn't have permission for this request.\",\n raw: source,\n }\n }\n\n // Rate limit from Anthropic's side.\n if (errType === \"rate_limit_error\" || status === 429) {\n return {\n kind: \"rate_limit\",\n retryable: true,\n message: \"Anthropic's API rate-limited this request.\",\n fix: \"AgentX will retry automatically. If it recurs, lower the agent's concurrency or spread scheduled jobs.\",\n raw: source,\n }\n }\n\n // Server-side overload.\n if (errType === \"overloaded_error\" || status === 529 || status === 503) {\n return {\n kind: \"overloaded\",\n retryable: true,\n message: \"Anthropic's API is temporarily overloaded.\",\n fix: \"AgentX will retry in a moment.\",\n raw: source,\n }\n }\n\n // Generic invalid request — surface the API message verbatim (truncated).\n if (errType === \"invalid_request_error\" || status === 400) {\n return {\n kind: \"invalid_request\",\n retryable: false,\n message: errMessage || \"The model rejected the request as invalid.\",\n raw: source,\n }\n }\n\n // Other Anthropic API errors (5xx catchall).\n if (status && status >= 500) {\n return {\n kind: \"upstream_api\",\n retryable: true,\n message: errMessage || `Anthropic API error (HTTP ${status}).`,\n fix: \"Transient — retry. Check https://status.anthropic.com if it persists.\",\n raw: source,\n }\n }\n\n // Fallback: unknown shape, keep the text short.\n return {\n kind: \"unknown\",\n retryable: false,\n message: source.replace(/\\s+/g, \" \").slice(0, 220),\n raw: source,\n }\n}\n\n/**\n * Render a FriendlyError back to a one-line string (primary message + fix),\n * which is what most call sites want when updating `response.error`.\n */\nexport function renderFriendlyError(f: FriendlyError): string {\n return f.fix ? `${f.message} — ${f.fix}` : f.message\n}\n","// The Claude Code CLI's result carries `modelUsage`, keyed by model id, with\n// one entry per model the run called. A multi-step run often has more than\n// one: the CLI makes small side calls on a cheaper model (Haiku) next to the\n// model the task asked for, and the side model can come first in the object.\n// The billed model is the one the run spent the most on (#455).\n\n/** The model in `modelUsage` with the highest `costUSD`, falling back to\n * the most tokens when no cost is given. Undefined when there is none. */\nexport function primaryModelFromUsage(modelUsage: unknown): string | undefined {\n if (!modelUsage || typeof modelUsage !== \"object\") return undefined\n let best: string | undefined\n let bestCost = -1\n let bestTokens = -1\n for (const [model, raw] of Object.entries(modelUsage as Record<string, any>)) {\n const u = raw && typeof raw === \"object\" ? raw : {}\n const cost = typeof u.costUSD === \"number\" ? u.costUSD : 0\n const tokens = (Number(u.inputTokens) || 0) + (Number(u.outputTokens) || 0)\n + (Number(u.cacheReadInputTokens) || 0) + (Number(u.cacheCreationInputTokens) || 0)\n if (cost > bestCost || (cost === bestCost && tokens > bestTokens)) {\n best = model\n bestCost = cost\n bestTokens = tokens\n }\n }\n return best\n}\n","import type { ProcessRegistry } from \"./process-registry\"\n\n// --- Singleton accessor for the live ProcessRegistry ---\n//\n// Lifetime: the daemon constructs a registry in AgentRegistry's\n// constructor (when at least one agent has `persistentProcess: true`)\n// and registers it here. `executeTask` reads the singleton when it\n// needs to dispatch via the persistent path. Tests use the\n// set/reset hooks to inject fakes without touching the daemon.\n//\n// Why a singleton (vs threading the registry through executeTask's\n// signature): executeTask is called from many sites — registry,\n// cron, mesh — and adding a parameter to all of them is invasive.\n// The registry is a process-global resource (one per daemon), same\n// lifetime story as the event bus.\n\nlet _registry: ProcessRegistry | null = null\n\nexport function getProcessRegistry(): ProcessRegistry | null {\n return _registry\n}\n\nexport function setProcessRegistry(r: ProcessRegistry | null): void {\n _registry = r\n}\n\n/** Test-only — guarantees a clean slate between cases. */\nexport function resetProcessRegistryForTesting(): void {\n _registry = null\n}\n","// --- Process Registry: persistent claude processes per session ---\n//\n// Tracks one `claude -p --input-format stream-json` subprocess per\n// (agentId, channel, chatId). Tasks for the same chat reuse the live\n// process across turns; the empirical cache-amortization win is ~3-5×\n// (turn 1 cache_create=12897, turn 2 cache_create=20 in the\n// 2026-05-03 spike). When a chat goes idle past idleTimeoutMs the\n// process is killed; when at the cap, the oldest idle process is\n// evicted to make room.\n//\n// This file is the registry only. Real subprocess wiring (spawn,\n// stdin write, stdout read) lives behind the ProcessFactory interface\n// so the registry can be tested without spawning real children.\n//\n// Design rationale (see docs/architecture/persistent-claude-process.md):\n// - Process-per-session (not pool-per-agent) — perfect chat\n// isolation; same composite key as SessionStore.\n// - Slash commands DO NOT work in `-p` mode (verbatim from the\n// headless docs). Rotation = kill + respawn, optionally with an\n// application-level summary in --append-system-prompt.\n// - Concurrency: same-chat turns serialize through the handle's\n// own queue; different-chat turns are independent processes.\n\nimport type { ClaudeBilling } from \"@/utils/workspace-env\"\n\nexport interface ProcessKey {\n agentId: string\n channel: string\n chatId: string\n}\n\nexport type ProcessKeyString = string\n\n/** Stringify the key for Map storage / logging. The composite is the\n * same one SessionStore uses, so process and session entries line up\n * without an extra mapping table. */\nexport function processKeyToString(k: ProcessKey): ProcessKeyString {\n return `${k.agentId}:${k.channel}:${k.chatId}`\n}\n\nexport function parseProcessKey(s: ProcessKeyString): ProcessKey {\n const [agentId, channel, ...rest] = s.split(\":\")\n return { agentId, channel, chatId: rest.join(\":\") }\n}\n\n/**\n * Lifecycle state of a process. Values are strings (not enum) so they\n * round-trip through JSON for `agentx process list` without an\n * encode/decode step.\n *\n * warm-cold → spawned, no claudeSessionId yet\n * warm-hot → at least one turn done, cache reuse expected\n * busy → a turn is streaming right now; never idle-killed\n * idle → not running a turn, eligible for eviction\n * dead → killed or exited; will be removed from the registry\n */\nexport type ProcessState = \"warm-cold\" | \"warm-hot\" | \"busy\" | \"idle\" | \"dead\"\n\nexport interface ProcessSnapshot {\n key: ProcessKey\n pid: number | null\n claudeSessionId: string | null\n state: ProcessState\n spawnedAt: number\n lastTurnAt: number\n turnCount: number\n /** Last result event's input_tokens — drives tier-2 rotation decisions. */\n lastInputTokens: number\n /** Trace ULID of the in-flight turn, when one is active. */\n pendingTaskId: string | null\n /** Reason the process is dead, when state === \"dead\". */\n deadReason?: string\n /** Managed-marker hash of CLAUDE.md at spawn time (null for unmarked\n * / user-edited files). The drift sweep re-reads the live file's\n * hash and kills idle handles whose hash no longer matches, so the\n * next dispatch picks up the edited prompt. */\n claudeMdHash: string | null\n}\n\nexport interface SpawnOptions {\n agentId: string\n channel: string\n chatId: string\n workspace: string\n model?: string\n permissionMode?: string\n /** The agent's `billing`; picks which Anthropic credential the spawn keeps. */\n billing?: ClaudeBilling\n systemPromptAppend?: string\n /** Pass-through to `claude --resume <id>`. Set when SessionStore has a\n * stored claudeSessionId for this chat (e.g. across daemon restart). */\n resumeSessionId?: string\n /** Extra `claude` flags for this chat's process, from the channel's\n * session profile (AgentTask.claudeArgs). The process key already\n * carries the channel, so one process never mixes profiles. */\n extraArgs?: string[]\n}\n\nexport interface TurnInput {\n /** User-facing text the parent wrote to stdin under\n * `{type:\"user\", message:{role:\"user\", content:<here>}}`. */\n message: string\n /** Trace ULID for correlating events back to a task_traces row. */\n taskId: string\n /** Absolute turn budget, from the agent's maxExecutionMinutes. */\n deadlineMs?: number\n}\n\nexport interface TurnEvent {\n /** stream-json event type — one of \"system\" | \"assistant\" | \"user\" |\n * \"result\" | \"stream_event\" | \"rate_limit_event\" | … */\n type: string\n /** Raw event line (parsed). Forwarded verbatim to the streaming\n * parser in registry.ts so trace step capture stays unchanged. */\n raw: Record<string, unknown>\n}\n\nexport interface ProcessHandle {\n readonly key: ProcessKey\n /** The options the process was spawned with. `acquire` compares them\n * with the next turn's to notice a model change. Optional so fakes\n * and older factories keep working. */\n readonly opts?: SpawnOptions\n state(): ProcessState\n snapshot(): ProcessSnapshot\n /**\n * Mark the handle as just-acquired: bumps lastTurnAt so the idle\n * sweeper can't kill it in the acquire→runTurn window (the sweep\n * runs every 5s; a handle acquired at idle 899s used to lose that\n * race and fail the dispatch with \"is dead (idle …)\").\n */\n claim?(): void\n /**\n * Send one user turn, yield stream-json events as they arrive, and\n * return when a `result` event is observed. The handle serializes\n * concurrent runTurn calls so two simultaneous tasks on the same\n * chat queue rather than racing.\n */\n runTurn(input: TurnInput): AsyncIterable<TurnEvent>\n /**\n * Kill the underlying process. SIGTERM, then SIGKILL after a grace\n * period. Resolves once the process is gone. `reason` shows up in\n * `agentx process list --include-dead` and the trace step.\n */\n kill(reason: string): Promise<void>\n}\n\nexport interface ProcessFactory {\n /** Spawn (or fake) a process bound to `key` with `opts`. */\n spawn(key: ProcessKey, opts: SpawnOptions): ProcessHandle\n}\n\n/**\n * Why a live handle cannot serve the next turn, or null when it can.\n * A busy handle is always reused: the in-flight turn must not be cut,\n * and the registry queues same-chat turns behind it anyway.\n */\nexport function staleHandleReason(handle: ProcessHandle, opts: SpawnOptions): string | null {\n const state = handle.state()\n if (state === \"busy\") return null\n const snap = handle.snapshot()\n const prevModel = handle.opts?.model\n if (prevModel && opts.model && prevModel !== opts.model) {\n return `model changed (${prevModel} -> ${opts.model})`\n }\n if (opts.resumeSessionId) {\n if (snap.claudeSessionId && snap.claudeSessionId !== opts.resumeSessionId) {\n return `session changed (${snap.claudeSessionId.slice(0, 8)} -> ${opts.resumeSessionId.slice(0, 8)})`\n }\n return null\n }\n // Claude reported a session id and the caller now passes none: the\n // session store was cleared by a rotation (stale, tier-2, max-turns,\n // continuity, planner). A process that never reported an id is reused;\n // the id is the only signal that distinguishes a rotation from a\n // caller that simply has nothing stored.\n if (snap.claudeSessionId) return \"session rotated\"\n return null\n}\n\n/**\n * Thrown by `acquire` when at the global or per-agent cap and no idle\n * handle is evictable. Callers — typically the per-turn driver in\n * runtime.ts — catch this and fall back to spawn-per-task. Using a\n * named error class is more robust than message-sniffing.\n */\nexport class RegistryCapExceeded extends Error {\n constructor(\n message: string,\n public readonly scope: \"global\" | \"agent\",\n public readonly cap: number,\n ) {\n super(message)\n this.name = \"RegistryCapExceeded\"\n }\n}\n\nexport interface ProcessRegistryConfig {\n /** Hard global cap. Default 64. When at cap, oldest idle handle is\n * evicted to make room for the next acquire. */\n maxProcessesGlobal?: number\n /** Per-agent cap. Default 16. Prevents one chatty agent from\n * starving another. */\n maxProcessesPerAgent?: number\n /** A handle is \"idle\" idleTimeoutMs after its last turn completed.\n * Idle handles are eligible for eviction; once `staleTimeoutMs` is\n * also exceeded, the handle is killed unconditionally on the next\n * sweep. Defaults: 30s idle, 45min stale. */\n idleTimeoutMs?: number\n staleTimeoutMs?: number\n /** Sweep interval. Default 5_000 (5s). */\n sweepIntervalMs?: number\n factory: ProcessFactory\n log?: (msg: string) => void\n /** Optional drift detector — called per idle handle on each sweep.\n * Returns the current workspace CLAUDE.md hash for the agent (or\n * null when unmarked / missing). The registry compares against the\n * hash stored on the handle's snapshot at spawn time and kills\n * handles whose hashes don't match.\n *\n * Callers wire this from src/agents/workspace-setup.ts where the\n * managed-marker hash logic already lives — the registry stays\n * decoupled from the marker format itself. When unset, drift\n * detection is disabled (default for tests). */\n currentWorkspaceHash?: (key: ProcessKey, workspace: string) => string | null\n}\n\nexport class ProcessRegistry {\n private handles = new Map<ProcessKeyString, ProcessHandle>()\n private sweeper?: ReturnType<typeof setInterval>\n /** Per-handle workspace path captured at acquire time. Drift sweep\n * re-reads each handle's workspace via the currentWorkspaceHash\n * hook to compare against the stored hash. */\n private handleWorkspace = new Map<ProcessKeyString, string>()\n private readonly cfg: Required<Omit<ProcessRegistryConfig, \"factory\" | \"log\" | \"currentWorkspaceHash\">> & {\n factory: ProcessFactory\n log: (msg: string) => void\n currentWorkspaceHash?: (key: ProcessKey, workspace: string) => string | null\n }\n\n constructor(cfg: ProcessRegistryConfig) {\n this.cfg = {\n maxProcessesGlobal: cfg.maxProcessesGlobal ?? 64,\n maxProcessesPerAgent: cfg.maxProcessesPerAgent ?? 16,\n idleTimeoutMs: cfg.idleTimeoutMs ?? 30_000,\n staleTimeoutMs: cfg.staleTimeoutMs ?? 45 * 60 * 1000,\n sweepIntervalMs: cfg.sweepIntervalMs ?? 5_000,\n factory: cfg.factory,\n log: cfg.log ?? (() => {}),\n currentWorkspaceHash: cfg.currentWorkspaceHash,\n }\n }\n\n /** Begin the idle-eviction sweep timer. Idempotent. */\n start(): void {\n if (this.sweeper) return\n this.sweeper = setInterval(() => this.sweepIdle(), this.cfg.sweepIntervalMs)\n if (typeof this.sweeper.unref === \"function\") this.sweeper.unref()\n }\n\n /**\n * Stop the sweeper and kill every live handle. Used on daemon\n * shutdown. Resolves when all kills complete.\n */\n async stop(): Promise<void> {\n if (this.sweeper) {\n clearInterval(this.sweeper)\n this.sweeper = undefined\n }\n const handles = Array.from(this.handles.values())\n this.handles.clear()\n await Promise.allSettled(handles.map((h) => h.kill(\"registry-stop\")))\n }\n\n /**\n * Get-or-spawn a process for `key`. Spawns through the factory when\n * no handle exists. When at the global or per-agent cap, evicts the\n * oldest idle handle to make room; throws if no idle handle is\n * available (caller can fall back to spawn-per-task or queue).\n */\n acquire(key: ProcessKey, opts: SpawnOptions): ProcessHandle {\n const ks = processKeyToString(key)\n const existing = this.handles.get(ks)\n if (existing && existing.state() !== \"dead\") {\n const stale = staleHandleReason(existing, opts)\n if (!stale) {\n existing.claim?.()\n return existing\n }\n // The warm process no longer matches what the caller asked for:\n // a rotation cleared the session, --resume points elsewhere, or\n // routing picked another model. Reusing it used to inject the\n // fresh-session context on top of the old one and silently keep\n // the old model. Respawn instead.\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n this.cfg.log(`[process-registry] respawning ${ks}: ${stale}`)\n void existing.kill(stale).catch(() => {})\n }\n\n // Make room if at cap.\n if (this.handles.size >= this.cfg.maxProcessesGlobal) {\n this.evictOldestIdle({ scope: \"global\" })\n }\n const perAgentCount = this.countForAgent(key.agentId)\n if (perAgentCount >= this.cfg.maxProcessesPerAgent) {\n this.evictOldestIdle({ scope: \"agent\", agentId: key.agentId })\n }\n if (this.handles.size >= this.cfg.maxProcessesGlobal) {\n throw new RegistryCapExceeded(\n `process registry at global cap (${this.cfg.maxProcessesGlobal}) and no idle handle to evict`,\n \"global\",\n this.cfg.maxProcessesGlobal,\n )\n }\n if (this.countForAgent(key.agentId) >= this.cfg.maxProcessesPerAgent) {\n throw new RegistryCapExceeded(\n `process registry at per-agent cap for ${key.agentId} (${this.cfg.maxProcessesPerAgent}) and no idle handle to evict`,\n \"agent\",\n this.cfg.maxProcessesPerAgent,\n )\n }\n\n const handle = this.cfg.factory.spawn(key, opts)\n this.handles.set(ks, handle)\n this.handleWorkspace.set(ks, opts.workspace)\n this.cfg.log(`[process-registry] spawned ${ks}`)\n return handle\n }\n\n /**\n * Drop a key from the registry, optionally killing the process.\n * Without `kill: true`, the handle stays alive — useful when the\n * caller wants the registry to forget about it (e.g. config reload)\n * but the in-flight turn should finish.\n */\n release(key: ProcessKey, opts: { kill?: boolean; reason?: string } = {}): void {\n const ks = processKeyToString(key)\n const handle = this.handles.get(ks)\n if (!handle) return\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n if (opts.kill) {\n void handle.kill(opts.reason ?? \"release\")\n }\n }\n\n /** Operator surface: snapshot every live handle. */\n list(): ProcessSnapshot[] {\n return Array.from(this.handles.values()).map((h) => h.snapshot())\n }\n\n /** True when a non-dead handle exists for this key. Used by the preflight\n * overage gate so persistent-process agents aren't treated as cold even\n * when no --resume session ID is stored for a given chatId. */\n hasLive(key: ProcessKey): boolean {\n const ks = processKeyToString(key)\n const h = this.handles.get(ks)\n return h != null && h.state() !== \"dead\"\n }\n\n /** Force-kill one handle, removing it from the registry. */\n async kill(key: ProcessKey, reason = \"operator\"): Promise<void> {\n const ks = processKeyToString(key)\n const handle = this.handles.get(ks)\n if (!handle) return\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n await handle.kill(reason)\n }\n\n // ---------- internals ----------\n\n private countForAgent(agentId: string): number {\n let n = 0\n for (const h of this.handles.values()) {\n if (h.key.agentId === agentId && h.state() !== \"dead\") n += 1\n }\n return n\n }\n\n /**\n * Evict the oldest idle handle in the requested scope. \"Oldest\" is\n * defined by lastTurnAt (LRU). Returns true when one was evicted.\n * No-op when no idle handle is found — the caller is responsible\n * for the cap-exceeded error path.\n */\n private evictOldestIdle(scope: { scope: \"global\" } | { scope: \"agent\"; agentId: string }): boolean {\n let target: ProcessHandle | undefined\n let oldest = Number.POSITIVE_INFINITY\n for (const h of this.handles.values()) {\n if (h.state() !== \"idle\") continue\n if (scope.scope === \"agent\" && h.key.agentId !== scope.agentId) continue\n const ts = h.snapshot().lastTurnAt\n if (ts < oldest) {\n oldest = ts\n target = h\n }\n }\n if (!target) return false\n const ks = processKeyToString(target.key)\n this.handles.delete(ks)\n void target.kill(\"evicted (cap reached)\")\n this.cfg.log(`[process-registry] evicted ${ks} (cap)`)\n return true\n }\n\n /**\n * Per-tick sweep — kills idle processes past timeout, reaps dead\n * handles, and detects CLAUDE.md drift on idle handles. Drift\n * kills only fire on idle handles to avoid disturbing a turn that's\n * still streaming; the next sweep catches the handle once it\n * transitions back to idle.\n */\n private sweepIdle(): void {\n const now = Date.now()\n for (const [ks, h] of this.handles.entries()) {\n const snap = h.snapshot()\n if (h.state() === \"dead\") {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n this.cfg.log(`[process-registry] reaped dead ${ks}`)\n continue\n }\n if (h.state() !== \"idle\") continue\n const idleFor = now - snap.lastTurnAt\n if (idleFor >= this.cfg.staleTimeoutMs) {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n void h.kill(`stale (idle ${Math.round(idleFor / 1000)}s)`)\n this.cfg.log(`[process-registry] killed stale ${ks}`)\n continue\n }\n if (idleFor >= this.cfg.idleTimeoutMs) {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n void h.kill(`idle (${Math.round(idleFor / 1000)}s)`)\n this.cfg.log(`[process-registry] killed idle ${ks}`)\n continue\n }\n\n // Drift detection — only when the operator wired a hash hook.\n // Reads the current managed-marker hash from the workspace's\n // CLAUDE.md and kills handles whose hash has changed since\n // spawn, so the next dispatch loads the edited prompt.\n if (this.cfg.currentWorkspaceHash) {\n const workspace = this.handleWorkspace.get(ks)\n if (workspace) {\n let currentHash: string | null = null\n try {\n currentHash = this.cfg.currentWorkspaceHash(h.key, workspace)\n } catch (e: any) {\n // Hash-read failure must not crash the sweeper. Log + skip.\n this.cfg.log(`[process-registry] drift-check failed for ${ks}: ${e?.message || e}`)\n continue\n }\n if (currentHash !== snap.claudeMdHash) {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n const fromHash = snap.claudeMdHash ?? \"unmarked\"\n const toHash = currentHash ?? \"unmarked\"\n void h.kill(`claude-md drifted (${fromHash} → ${toHash})`)\n this.cfg.log(`[process-registry] killed drifted ${ks} (${fromHash} → ${toHash})`)\n }\n }\n }\n }\n }\n}\n","import { spawn, type ChildProcessWithoutNullStreams } from \"child_process\"\nimport { randomUUID } from \"crypto\"\nimport { existsSync, readFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport {\n type ProcessFactory,\n type ProcessHandle,\n type ProcessKey,\n type ProcessSnapshot,\n type ProcessState,\n type SpawnOptions,\n type TurnEvent,\n type TurnInput,\n} from \"./process-registry\"\nimport { readManagedHash } from \"./workspace-setup\"\nimport { claudeBillingEnv } from \"@/utils/workspace-env\"\n\n// --- Real subprocess factory for `claude` ---\n//\n// Step 3 of the persistent-claude-process design (see\n// docs/architecture/persistent-claude-process.md). Wraps the `claude`\n// CLI in a long-lived child driven via stream-json on stdin and\n// stdout. The wire format was verified empirically on 2026-05-03:\n//\n// stdin: {\"type\":\"user\",\"message\":{\"role\":\"user\",\"content\":\"<text>\"}}\\n\n// stdout: newline-delimited JSON events (system/init, assistant,\n// rate_limit_event, result, ...). The `result` event marks\n// the end of one turn; subsequent turns can be sent on the\n// same stdin without spawning again.\n//\n// A `result` is not always the answer to the last line written: when a\n// background task of the agent ends, Claude runs a turn nobody asked for,\n// with its own `result`. So each user line carries a uuid, and with\n// --replay-user-messages Claude echoes it on stdout as that question's\n// turn starts (verified on 2.1.288, 2026-10-03). Only what follows the\n// echo belongs to the question (#585).\n//\n// Concurrency: same-chat turns serialize through `turnQueue`. The\n// handle never runs two turns simultaneously — Claude's session state\n// is intrinsically sequential.\n\ninterface ParsedEvent {\n type: string\n [k: string]: unknown\n}\n\n/**\n * Read the managed-marker hash from a workspace's CLAUDE.md, returning\n * null when the file is missing or has no marker (user-edited). Used\n * both at spawn (to record the baseline) and from the registry's drift\n * sweep (to detect changes). Read failures swallowed so a permission\n * issue doesn't crash the spawn or the sweeper.\n */\nexport function readClaudeMdHashSafe(workspace: string): string | null {\n const path = resolve(workspace, \"CLAUDE.md\")\n if (!existsSync(path)) return null\n try {\n const content = readFileSync(path, \"utf8\")\n return readManagedHash(content)\n } catch {\n return null\n }\n}\n\nconst TURN_DEADLINE_MS = 20 * 60 * 1000 // default when the caller passes no deadlineMs\nconst KILL_GRACE_MS = 5_000 // SIGTERM → wait → SIGKILL\n\nexport interface ClaudeProcessFactoryOptions {\n /** Override the binary. Default: \"claude\" (resolved via PATH). */\n binary?: string\n /** Extra flags appended to every spawn. Useful for tests / overrides. */\n extraArgs?: string[]\n /** Logger; default no-op. */\n log?: (msg: string) => void\n}\n\nexport class TurnDeadlineExceeded extends Error {\n constructor(readonly budgetMs: number) {\n super(`turn exceeded its ${Math.round(budgetMs / 60_000)}m budget`)\n }\n}\n\n/** The process was killed mid-turn by something other than the turn's own\n * deadline: a daemon shutdown, an eviction, an operator kill. */\nexport class TurnInterrupted extends Error {\n constructor(readonly reason: string) {\n super(`claude process stopped mid-turn (${reason})`)\n }\n}\n\n/** Who this warm process serves, for the tools it launches (the agentx MCP\n * server reads these to name the caller of a delegation, #277). A warm\n * process is keyed by (agent, channel, chatId), so these never go stale;\n * the per-turn AGENTX_TASK_ID does, and is left out. */\nexport function persistentCallerEnv(env: NodeJS.ProcessEnv, key: ProcessKey): NodeJS.ProcessEnv {\n env.AGENTX_AGENT_ID = key.agentId\n env.AGENTX_CHANNEL = key.channel\n env.AGENTX_CHAT_ID = key.chatId\n delete env.AGENTX_TASK_ID\n return env\n}\n\nexport class ClaudeProcessFactory implements ProcessFactory {\n constructor(private opts: ClaudeProcessFactoryOptions = {}) {}\n\n spawn(key: ProcessKey, opts: SpawnOptions): ProcessHandle {\n return new ClaudeProcessHandle(key, opts, this.opts)\n }\n}\n\nclass ClaudeProcessHandle implements ProcessHandle {\n private child: ChildProcessWithoutNullStreams\n private buf = \"\"\n private pendingEvents: ParsedEvent[] = []\n private waiters: Array<(e: ParsedEvent | null) => void> = []\n private exited = false\n private exitCode: number | null = null\n private killReason?: string\n /** Promise chain that serialises runTurn calls. Each call appends. */\n private turnQueue: Promise<void> = Promise.resolve()\n private snap: ProcessSnapshot\n private log: (msg: string) => void\n\n constructor(\n public readonly key: ProcessKey,\n public readonly opts: SpawnOptions,\n private factoryOpts: ClaudeProcessFactoryOptions,\n ) {\n const binary = factoryOpts.binary ?? \"claude\"\n const args = this.buildArgs(opts)\n const log = this.log = factoryOpts.log ?? (() => {})\n\n this.child = spawn(binary, args, {\n cwd: opts.workspace,\n env: claudeBillingEnv(persistentCallerEnv({ ...process.env }, key), opts.billing),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n }) as ChildProcessWithoutNullStreams\n\n this.snap = {\n key,\n pid: this.child.pid ?? null,\n claudeSessionId: opts.resumeSessionId ?? null,\n state: \"warm-cold\",\n spawnedAt: Date.now(),\n lastTurnAt: Date.now(),\n turnCount: 0,\n lastInputTokens: 0,\n pendingTaskId: null,\n claudeMdHash: readClaudeMdHashSafe(opts.workspace),\n }\n\n this.child.stdout.setEncoding(\"utf8\")\n this.child.stdout.on(\"data\", (chunk) => this.onStdout(chunk))\n this.child.stderr.setEncoding(\"utf8\")\n this.child.stderr.on(\"data\", (chunk) => log(`[claude pid=${this.snap.pid}] stderr: ${chunk.trim()}`))\n this.child.on(\"exit\", (code) => {\n this.exited = true\n this.exitCode = code\n this.snap = { ...this.snap, state: \"dead\", deadReason: this.killReason ?? `exit=${code}` }\n // Wake every waiter — they get null which is interpreted as EOF.\n const ws = this.waiters.splice(0)\n for (const w of ws) w(null)\n })\n this.child.on(\"error\", (err) => {\n log(`[claude pid=${this.snap.pid}] spawn error: ${err.message}`)\n this.killReason = `spawn-error: ${err.message}`\n })\n }\n\n state(): ProcessState {\n return this.snap.state\n }\n\n snapshot(): ProcessSnapshot {\n return { ...this.snap }\n }\n\n /**\n * Run one user turn. Yields stream-json events until a `result` event\n * arrives (turn complete) or the process exits (turn aborted). Concurrent\n * calls on the same handle queue behind each other — Claude's session\n * is sequential by construction.\n */\n async *runTurn(input: TurnInput): AsyncIterable<TurnEvent> {\n // Take our slot in the queue. We hold it until the iterator finishes.\n let release!: () => void\n const myDone = new Promise<void>((r) => { release = r })\n const prevQueue = this.turnQueue\n this.turnQueue = prevQueue.then(() => myDone)\n await prevQueue\n\n if (this.exited || this.snap.state === \"dead\") {\n release()\n throw new Error(`claude process for ${this.key.agentId}:${this.key.chatId} is dead (${this.snap.deadReason ?? \"?\"})`)\n }\n\n // Mark the turn in-flight. Without this the handle kept reporting\n // \"idle\" while streaming, so the registry sweeper idle-killed\n // processes mid-turn once (now − previous turn's end) crossed\n // idleTimeoutMs — any turn longer than the idle window died.\n const turnStart = Date.now()\n this.snap = { ...this.snap, state: \"busy\", pendingTaskId: input.taskId, lastTurnAt: turnStart }\n\n // Write the user line. JSON.stringify guarantees no embedded\n // newlines so a single \\n delimits the message.\n const uuid = randomUUID()\n const line = JSON.stringify({\n type: \"user\",\n uuid,\n message: { role: \"user\", content: input.message },\n })\n this.child.stdin.write(line + \"\\n\")\n\n const budgetMs = input.deadlineMs ?? TURN_DEADLINE_MS\n const deadline = turnStart + budgetMs\n const timedOut = () => {\n // Stop the turn for real: a process left running would keep working\n // on a handle marked idle until the sweeper killed it mid-step.\n void this.kill(`turn deadline (${Math.round(budgetMs / 60_000)}m)`)\n return new TurnDeadlineExceeded(budgetMs)\n }\n\n // Until Claude echoes our uuid, the events are from a turn nobody\n // asked for (queued while the handle sat idle, or still running).\n // Its reply is not this question's answer: log it and drop it.\n let echoed = false\n let init: ParsedEvent | null = null\n\n try {\n while (true) {\n const remaining = deadline - Date.now()\n if (remaining <= 0) throw timedOut()\n const evt = await this.nextEvent(remaining)\n // nextEvent yields null on EOF, on its own timeout, and when kill()\n // wakes it before the child has exited. Only the timeout is ours.\n if (evt === null && this.killReason) throw new TurnInterrupted(this.killReason)\n if (evt === null && !this.exited) throw timedOut()\n if (evt === null) {\n throw new Error(`claude process exited mid-turn (code=${this.exitCode}, reason=${this.snap.deadReason ?? \"?\"})`)\n }\n if (!echoed) {\n if (evt.type === \"user\" && evt.uuid === uuid) {\n echoed = true\n // The init of our own turn arrives before the echo.\n if (init) yield { type: init.type, raw: init }\n } else if (evt.type === \"system\" && evt.subtype === \"init\") {\n init = evt\n } else if (evt.type === \"result\") {\n init = null\n const text = typeof evt.result === \"string\" ? evt.result : \"\"\n this.log(`[claude pid=${this.snap.pid}] dropped a reply no question asked for (${text.length} chars): ${text.slice(0, 120)}`)\n }\n continue\n }\n yield { type: evt.type, raw: evt }\n\n if (evt.type === \"result\") {\n this.onResultEvent(evt)\n return\n }\n }\n } finally {\n // Success path already transitioned busy → idle in onResultEvent.\n // On error paths (deadline, mid-turn exit) restore idle here so a\n // handle can never leak in \"busy\" and dodge the sweeper forever.\n const stillBusy = this.snap.state === \"busy\"\n this.snap = {\n ...this.snap,\n ...(stillBusy ? { state: \"idle\" as const, lastTurnAt: Date.now() } : {}),\n pendingTaskId: null,\n }\n release()\n }\n }\n\n /** See ProcessHandle.claim — bump lastTurnAt on acquire so the idle\n * sweeper can't kill the handle before the turn's first write. */\n claim(): void {\n if (this.snap.state === \"dead\") return\n this.snap = { ...this.snap, lastTurnAt: Date.now() }\n }\n\n async kill(reason: string): Promise<void> {\n if (this.exited) return\n this.killReason = reason\n this.snap = { ...this.snap, state: \"dead\", deadReason: reason }\n try {\n this.child.kill(\"SIGTERM\")\n } catch { /* already gone */ }\n\n // Best-effort wake any waiters so they don't block forever.\n const ws = this.waiters.splice(0)\n for (const w of ws) w(null)\n\n // Force after grace.\n const forced = await new Promise<boolean>((resolve) => {\n const t = setTimeout(() => {\n try { this.child.kill(\"SIGKILL\") } catch { /* */ }\n resolve(true)\n }, KILL_GRACE_MS)\n this.child.once(\"exit\", () => { clearTimeout(t); resolve(false) })\n })\n void forced\n }\n\n // ---------- internals ----------\n\n private buildArgs(opts: SpawnOptions): string[] {\n const args: string[] = [\n \"-p\",\n \"--input-format\", \"stream-json\",\n \"--output-format\", \"stream-json\",\n \"--verbose\",\n \"--replay-user-messages\",\n ]\n if (opts.model) args.push(\"--model\", opts.model)\n if (opts.permissionMode === \"bypassPermissions\") {\n args.push(\"--dangerously-skip-permissions\")\n }\n if (opts.resumeSessionId) {\n args.push(\"--resume\", opts.resumeSessionId)\n }\n if (opts.systemPromptAppend && opts.systemPromptAppend.trim().length > 0) {\n args.push(\"--append-system-prompt\", opts.systemPromptAppend)\n }\n if (opts.extraArgs) {\n args.push(...opts.extraArgs)\n }\n if (this.factoryOpts.extraArgs) {\n args.push(...this.factoryOpts.extraArgs)\n }\n return args\n }\n\n private onStdout(chunk: string): void {\n this.buf += chunk\n let nl: number\n while ((nl = this.buf.indexOf(\"\\n\")) !== -1) {\n const line = this.buf.slice(0, nl)\n this.buf = this.buf.slice(nl + 1)\n if (!line.trim()) continue\n let evt: ParsedEvent\n try {\n evt = JSON.parse(line) as ParsedEvent\n } catch {\n // Malformed line — drop. Claude CLI very occasionally emits\n // non-JSON warnings; resilience > strictness here.\n continue\n }\n if (this.waiters.length > 0) {\n const w = this.waiters.shift()!\n w(evt)\n } else {\n this.pendingEvents.push(evt)\n }\n }\n }\n\n /**\n * Pull the next event from the queue, or wait for one. Returns null\n * when the process has exited and no more events will arrive.\n * `timeoutMs` lets the caller cap how long it'll wait — used to\n * enforce per-turn deadlines.\n */\n private nextEvent(timeoutMs: number): Promise<ParsedEvent | null> {\n if (this.pendingEvents.length > 0) {\n return Promise.resolve(this.pendingEvents.shift()!)\n }\n if (this.exited) return Promise.resolve(null)\n\n return new Promise<ParsedEvent | null>((resolve) => {\n let resolved = false\n const t = setTimeout(() => {\n if (resolved) return\n resolved = true\n const idx = this.waiters.indexOf(resolver)\n if (idx >= 0) this.waiters.splice(idx, 1)\n resolve(null)\n }, timeoutMs)\n const resolver = (e: ParsedEvent | null) => {\n if (resolved) return\n resolved = true\n clearTimeout(t)\n resolve(e)\n }\n this.waiters.push(resolver)\n })\n }\n\n /**\n * Update the snapshot from a `result` event — usage, session_id,\n * state transition warm-cold → warm-hot → idle.\n */\n private onResultEvent(evt: ParsedEvent): void {\n const usage = (evt.usage ?? {}) as Record<string, number>\n const sessionId = typeof evt.session_id === \"string\" ? evt.session_id : this.snap.claudeSessionId\n this.snap = {\n ...this.snap,\n lastTurnAt: Date.now(),\n turnCount: this.snap.turnCount + 1,\n lastInputTokens: typeof usage.input_tokens === \"number\" ? usage.input_tokens : this.snap.lastInputTokens,\n claudeSessionId: sessionId,\n state: \"idle\",\n }\n }\n}\n","import { resolve, join } from \"path\"\nimport { openDb } from \"@/storage/sqlite\"\nimport { newEventId } from \"@/intent/ulid\"\nimport type { GuardInput, Verdict } from \"./types\"\nimport type { GuardMode } from \"./types\"\n\n// --- Guardrail audit (PRD R7) ---\n//\n// Every guard verdict is recorded in the `guardrail_decisions` table in the\n// observability db (`.agentx/db.sqlite`). The row is written by the\n// short-lived hook process, so we resolve the db against the daemon's install\n// ROOT (not the agent workspace cwd) and set a busy_timeout so a concurrent\n// daemon write doesn't lose the audit row.\n\nexport interface AuditInput {\n root: string\n input: GuardInput\n verdict: Verdict\n mode: GuardMode\n taskId?: string | null\n approver?: string | null\n ms?: number | null\n}\n\nconst COMMAND_CAP = 4000\n\n/** Write one guard decision. Best-effort and never throws — an audit failure\n * must not change whether the command runs (the verdict is already decided).\n * Returns the row id, or null when the db couldn't be opened. */\nexport function recordDecision(a: AuditInput): string | null {\n try {\n const dbPath = join(resolve(a.root), \".agentx\", \"db.sqlite\")\n const db = openDb({ path: dbPath })\n if (!db) return null\n try {\n db.pragma(\"busy_timeout = 3000\")\n } catch {\n /* pragma best-effort */\n }\n const id = newEventId()\n const cmd = a.input.command ? a.input.command.slice(0, COMMAND_CAP) : a.input.filePath ?? null\n db.prepare(\n `INSERT INTO guardrail_decisions\n (id, ts, agent_id, task_id, tool, command, resolved_target, matched_rule,\n verdict, effective_action, mode, severity, approver, ms)\n VALUES (@id, @ts, @agent_id, @task_id, @tool, @command, @resolved_target, @matched_rule,\n @verdict, @effective_action, @mode, @severity, @approver, @ms)`,\n ).run({\n id,\n ts: Date.now(),\n agent_id: a.input.agentId ?? null,\n task_id: a.taskId ?? null,\n tool: a.input.tool,\n command: cmd,\n resolved_target: a.verdict.resolvedTarget,\n matched_rule: a.verdict.ruleId,\n verdict: a.verdict.action,\n effective_action: a.verdict.effectiveAction,\n mode: a.mode,\n severity: a.verdict.severity,\n approver: a.approver ?? null,\n ms: a.ms ?? null,\n })\n return id\n } catch (e: any) {\n process.stderr.write(`[agentx-guard] audit write failed: ${e?.message}\\n`)\n return null\n }\n}\n\nexport interface DecisionRow {\n id: string\n ts: number\n agent_id: string | null\n task_id: string | null\n tool: string\n command: string | null\n resolved_target: string | null\n matched_rule: string | null\n verdict: string\n effective_action: string\n mode: string | null\n severity: string | null\n approver: string | null\n ms: number | null\n}\n\nexport interface ListDecisionsFilters {\n root: string\n limit?: number\n verdict?: string\n agentId?: string\n taskId?: string\n}\n\n/** Read recent decisions (newest first) for `agentx guard log`. */\nexport function listDecisions(f: ListDecisionsFilters): DecisionRow[] {\n const dbPath = join(resolve(f.root), \".agentx\", \"db.sqlite\")\n const db = openDb({ path: dbPath })\n if (!db) return []\n const where: string[] = []\n const params: Record<string, unknown> = {}\n if (f.verdict) {\n where.push(\"verdict = @verdict\")\n params.verdict = f.verdict\n }\n if (f.agentId) {\n where.push(\"agent_id = @agentId\")\n params.agentId = f.agentId\n }\n if (f.taskId) {\n where.push(\"task_id = @taskId\")\n params.taskId = f.taskId\n }\n const sql =\n `SELECT * FROM guardrail_decisions` +\n (where.length ? ` WHERE ${where.join(\" AND \")}` : \"\") +\n ` ORDER BY ts DESC LIMIT @limit`\n params.limit = f.limit ?? 50\n return db.prepare(sql).all(params) as DecisionRow[]\n}\n","import { recordDecision } from \"./audit\"\nimport { autonomyLevelSchema, evaluateAutonomy, isRestricted, type AutonomyLevel } from \"./autonomy\"\nimport type { PreToolUsePayload } from \"./check\"\nimport type { Verdict } from \"./types\"\n\n// --- Routine autonomy: per-task enforcement wiring ---\n//\n// Enforcement is PER SPAWN, never per agent. A restricted task (autonomy\n// report|propose) is run as its own `claude` process with two extra flags:\n//\n// --settings '{\"hooks\":{\"PreToolUse\":[{\"matcher\":\"*\", ...}]}}'\n// A PreToolUse hook on EVERY tool (native + MCP) that POSTs the call to\n// the daemon's loopback /guard/check with ?autonomy=<level>&task=<id>.\n// It layers on top of the workspace's own guard hook; Claude Code\n// applies the strictest decision, and hooks fire under\n// bypassPermissions too. If the daemon cannot be reached the hook\n// exits 2, which BLOCKS the call: a restricted routine fails closed,\n// unlike the workspace guard hook which fails open.\n// --disallowedTools Write,Edit,MultiEdit,NotebookEdit (report only)\n// Belt and braces for the file-writing tools, independent of the hook.\n//\n// Because the flags live on that one process, other chats with the same\n// agent — including the agent's warm persistent processes — are untouched,\n// and the restricted task never reuses a warm process spawned without them\n// (the runtime forces spawn-per-task for restricted levels).\n\nexport interface AutonomyBlock {\n ts: number\n tool: string\n /** Command or file path the call targeted, truncated. */\n target: string | null\n ruleId: string\n reason: string\n}\n\nconst MAX_BLOCKS_PER_TASK = 50\nconst BLOCK_TTL_MS = 6 * 60 * 60 * 1000\n\nlet hookPort: string | null = null\nconst blocksByTask = new Map<string, { at: number; blocks: AutonomyBlock[] }>()\n\n/** Set by the daemon at start from its bind address. Until it is set,\n * restricted tasks refuse to run (there is nowhere to send the hook). */\nexport function setAutonomyHookPort(port: string | number | null): void {\n hookPort = port === null ? null : String(port)\n}\n\nexport function getAutonomyHookPort(): string | null {\n return hookPort\n}\n\n/** The hook command. Exit 2 on transport failure = block (fail closed). */\nexport function autonomyHookCommand(port: string, agentId: string, level: AutonomyLevel, taskId: string): string {\n const q = new URLSearchParams({ agent: agentId, autonomy: level, task: taskId }).toString()\n const url = `http://127.0.0.1:${port}/guard/check?${q}`\n return `curl -sf --max-time 10 -H 'Content-Type: application/json' --data-binary @- '${url}' || ` +\n `{ echo '[agentx-autonomy] guard unreachable: tool call blocked (fail-closed)' >&2; exit 2; }`\n}\n\n/**\n * Extra `claude` CLI args for a restricted task, or an error explaining why\n * the task cannot be enforced (and so must not run). Empty for `act`.\n */\nexport function autonomyClaudeArgs(\n level: AutonomyLevel | undefined,\n agentId: string,\n taskId: string,\n): { args: string[] } | { error: string } {\n if (!isRestricted(level)) return { args: [] }\n if (!hookPort) return { error: `autonomy \"${level}\" cannot be enforced: guard hook endpoint is not configured` }\n const settings = {\n hooks: {\n PreToolUse: [\n { matcher: \"*\", hooks: [{ type: \"command\", command: autonomyHookCommand(hookPort, agentId, level, taskId), timeout: 15 }] },\n ],\n },\n }\n const args: string[] = []\n if (level === \"report\") args.push(\"--disallowedTools\", \"Write,Edit,MultiEdit,NotebookEdit\")\n args.push(\"--settings\", JSON.stringify(settings))\n return { args }\n}\n\n/** Why a tier cannot enforce a restricted level, or null when it can. */\nexport function autonomyUnsupported(level: AutonomyLevel | undefined, tier: string | undefined): string | null {\n if (!isRestricted(level)) return null\n if (tier === \"claude-code\") return null\n return `autonomy \"${level}\" is only enforceable on the claude-code tier (agent tier: ${tier ?? \"unknown\"}); refusing to run it with full permissions`\n}\n\n// ---------------------------------------------------------------- blocks\n\nfunction sweep(now: number): void {\n for (const [k, v] of blocksByTask) if (now - v.at > BLOCK_TTL_MS) blocksByTask.delete(k)\n}\n\nexport function recordAutonomyBlock(taskId: string, block: AutonomyBlock): void {\n const now = Date.now()\n sweep(now)\n const entry = blocksByTask.get(taskId) ?? { at: now, blocks: [] }\n if (entry.blocks.length < MAX_BLOCKS_PER_TASK) entry.blocks.push(block)\n entry.at = now\n blocksByTask.set(taskId, entry)\n}\n\n/** Remove and return the blocks recorded for a task. */\nexport function takeAutonomyBlocks(taskId: string | undefined): AutonomyBlock[] {\n if (!taskId) return []\n const entry = blocksByTask.get(taskId)\n blocksByTask.delete(taskId)\n return entry?.blocks ?? []\n}\n\n// ---------------------------------------------------------------- daemon side\n\nexport interface AutonomyCheckResult {\n /** Body for the hook: \"\" = allow (defer to the other hooks). */\n stdout: string\n blocked: AutonomyBlock | null\n}\n\nfunction denyOutput(reason: string): string {\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: reason,\n },\n })\n}\n\n/**\n * Judge one PreToolUse payload from a restricted task's hook. Blocks are\n * always enforced (no warn mode: the operator opted this routine into the\n * restriction), written to the guard log, and kept for the run result.\n * Never throws; an internal error denies the call.\n */\nexport function checkAutonomyPayload(\n payload: PreToolUsePayload,\n opts: { root: string; agentId?: string; level: string | null; taskId?: string | null },\n): AutonomyCheckResult {\n const tool = payload.tool_name ?? \"unknown\"\n const command = typeof payload.tool_input?.command === \"string\" ? payload.tool_input.command : undefined\n const filePath = typeof payload.tool_input?.file_path === \"string\"\n ? payload.tool_input.file_path\n : typeof payload.tool_input?.notebook_path === \"string\" ? payload.tool_input.notebook_path : undefined\n try {\n const parsed = autonomyLevelSchema.safeParse(opts.level)\n const decision = parsed.success\n ? evaluateAutonomy(parsed.data, { tool, command, filePath })\n : { allowed: false, ruleId: \"autonomy.invalid\", reason: `unknown autonomy level \"${opts.level}\"` }\n if (decision.allowed) return { stdout: \"\", blocked: null }\n\n const level = parsed.success ? parsed.data : \"report\"\n const ruleId = decision.ruleId ?? \"autonomy\"\n const reason = decision.reason ?? \"blocked\"\n const block: AutonomyBlock = {\n ts: Date.now(),\n tool,\n target: (command ?? filePath ?? null)?.slice(0, 300) ?? null,\n ruleId,\n reason,\n }\n const verdict: Verdict = {\n matched: true,\n action: \"deny\",\n effectiveAction: \"deny\",\n ruleId,\n severity: \"high\",\n message: reason,\n resolvedTarget: null,\n }\n recordDecision({\n root: opts.root,\n input: { tool, command: command ?? (filePath ? undefined : tool), filePath, agentId: opts.agentId },\n verdict,\n mode: \"enforce\",\n taskId: opts.taskId ?? null,\n })\n if (opts.taskId) recordAutonomyBlock(opts.taskId, block)\n return {\n stdout: denyOutput(\n `[agentx-autonomy] Blocked: this routine runs at autonomy \"${level}\" and ${reason} (rule: ${ruleId}). ` +\n `Do not work around it. Finish the task within the limit and list this step in your final answer as a recommendation.`,\n ),\n blocked: block,\n }\n } catch (e: any) {\n return { stdout: denyOutput(`[agentx-autonomy] Guard error (fail-closed): ${e?.message ?? e}`), blocked: null }\n }\n}\n","import { recordDecision } from \"./audit\"\nimport { getAutonomyHookPort } from \"./autonomy-enforce\"\nimport type { PreToolUsePayload } from \"./check\"\nimport type { Verdict } from \"./types\"\nimport { nameMatches, type PersonLimits } from \"@/people/people\"\n\n// --- People permissions: per tool and per skill, enforced per run (#379) ---\n//\n// Same wiring as routine autonomy (autonomy-enforce.ts). A run whose person\n// has deny lists is its own `claude` process, never a warm persistent one,\n// with a PreToolUse hook on every tool that POSTs the call to the daemon's\n// loopback /guard/check?person=1&task=<id>. The daemon judges it against the\n// limits registered for that task here. The hook carries only the task id:\n// the limits never leave the daemon, so nothing the run writes can change\n// them. If the daemon cannot be reached, or holds no limits for the task,\n// the call is blocked (fail closed).\n\nconst LIMIT_TTL_MS = 6 * 60 * 60 * 1000\nconst limitsByTask = new Map<string, { at: number; limits: PersonLimits }>()\n\nfunction sweep(now: number): void {\n for (const [k, v] of limitsByTask) if (now - v.at > LIMIT_TTL_MS) limitsByTask.delete(k)\n}\n\n/** Called just before the run spawns. */\nexport function registerPersonLimits(taskId: string, limits: PersonLimits): void {\n const now = Date.now()\n sweep(now)\n limitsByTask.set(taskId, { at: now, limits })\n}\n\n/** Called when the run ends. */\nexport function clearPersonLimits(taskId: string): void {\n limitsByTask.delete(taskId)\n}\n\n/** The hook command. Exit 2 on transport failure = block (fail closed). */\nexport function personLimitHookCommand(port: string, taskId: string, agentId?: string): string {\n const q = new URLSearchParams({ person: \"1\", task: taskId, ...(agentId ? { agent: agentId } : {}) }).toString()\n const url = `http://127.0.0.1:${port}/guard/check?${q}`\n return `curl -sf --max-time 10 -H 'Content-Type: application/json' --data-binary @- '${url}' || ` +\n `{ echo '[agentx-people] guard unreachable: tool call blocked (fail-closed)' >&2; exit 2; }`\n}\n\n/** Add the person-limit hook to a run's claude args. Joins an existing\n * `--settings` (the autonomy hook) rather than passing a second one. */\nexport function withPersonLimitHook(args: string[], taskId: string, agentId?: string): { args: string[] } | { error: string } {\n const port = getAutonomyHookPort()\n if (!port) return { error: \"this person's tool and skill limits cannot be enforced: guard hook endpoint is not configured\" }\n const entry = { matcher: \"*\", hooks: [{ type: \"command\", command: personLimitHookCommand(port, taskId, agentId), timeout: 15 }] }\n const out = [...args]\n const at = out.indexOf(\"--settings\")\n if (at === -1) {\n out.push(\"--settings\", JSON.stringify({ hooks: { PreToolUse: [entry] } }))\n return { args: out }\n }\n const settings = JSON.parse(out[at + 1])\n settings.hooks = settings.hooks ?? {}\n settings.hooks.PreToolUse = [...(settings.hooks.PreToolUse ?? []), entry]\n out[at + 1] = JSON.stringify(settings)\n return { args: out }\n}\n\n/** Why a tier cannot carry the hook, or null when it can. */\nexport function personLimitsUnsupported(tier: string | undefined): string | null {\n return tier === \"claude-code\" ? null\n : `this person's tool and skill limits are only enforceable on the claude-code tier (agent tier: ${tier ?? \"unknown\"}); refusing to run without them`\n}\n\nexport interface PersonLimitBlock {\n personId: string\n tool: string\n /** The skill, when the call was the Skill tool. */\n skill: string | null\n reason: string\n}\n\nfunction denyOutput(reason: string): string {\n return JSON.stringify({\n hookSpecificOutput: { hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: reason },\n })\n}\n\n/** What a call is denied for, or null when it may go ahead. */\nfunction denialOf(limits: PersonLimits, tool: string, input: Record<string, unknown>): { skill: string | null; what: string } | null {\n if (limits.tools.some((p) => nameMatches(p, tool))) return { skill: null, what: tool }\n if (tool === \"Skill\") {\n const skill = typeof input.skill === \"string\" ? input.skill : typeof input.command === \"string\" ? input.command : \"\"\n if (skill && limits.skills.some((p) => nameMatches(p, skill))) return { skill, what: `the skill ${skill}` }\n }\n return null\n}\n\n/**\n * Judge one PreToolUse payload from a limited run's hook. A denial is\n * written to the guard log and returned so the caller can keep it in the\n * person's trail. Never throws; an internal error denies the call.\n */\nexport function checkPersonLimitPayload(\n payload: PreToolUsePayload,\n opts: { root: string; taskId?: string | null; agentId?: string },\n): { stdout: string; blocked: PersonLimitBlock | null } {\n const tool = payload.tool_name ?? \"unknown\"\n const input = (payload.tool_input ?? {}) as Record<string, unknown>\n try {\n const entry = opts.taskId ? limitsByTask.get(opts.taskId) : undefined\n if (!entry) return { stdout: denyOutput(\"[agentx-people] No limits are on record for this run, so the call is blocked (fail-closed).\"), blocked: null }\n const { limits } = entry\n const denied = denialOf(limits, tool, input)\n if (!denied) return { stdout: \"\", blocked: null }\n const reason = `${limits.name} may not use ${denied.what}`\n const verdict: Verdict = {\n matched: true, action: \"deny\", effectiveAction: \"deny\", ruleId: \"people.deny\",\n severity: \"high\", message: reason, resolvedTarget: null,\n }\n recordDecision({\n root: opts.root,\n input: { tool, command: typeof input.command === \"string\" ? input.command : tool, agentId: opts.agentId },\n verdict,\n mode: \"enforce\",\n taskId: opts.taskId ?? null,\n })\n return {\n stdout: denyOutput(\n `[agentx-people] Blocked: ${reason} here (set by the owner). Do not work around it. ` +\n `Finish what you can without it and say in your answer what was not done and why.`,\n ),\n blocked: { personId: limits.personId, tool, skill: denied.skill, reason },\n }\n } catch (e: any) {\n return { stdout: denyOutput(`[agentx-people] Guard error (fail-closed): ${e?.message ?? e}`), blocked: null }\n }\n}\n","import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from \"fs\"\nimport { resolve } from \"path\"\nimport type { ChannelAdapter, SeededMessage } from \"@/channels/types\"\nimport { debug } from \"@/observability/debug\"\n\n// --- Conversation session store ---\n// One session per agent + channel + chatId + day.\n// For CLI-backed tiers: stores native session IDs so we can resume them.\n// For other tiers: stores message history as text, prepended to each prompt.\n\nexport interface SessionMessage {\n role: \"user\" | \"agent\"\n name?: string // sender name or agent name\n content: string\n timestamp: string\n /** True when this message was injected by SessionStore.seedFromAdapter()\n * reading the live channel on cold session create — never via the inbound\n * router pipeline. Marked so observers can distinguish seeded turns from\n * live ones; the model sees them identically in buildHistoryContext. */\n seeded?: boolean\n /** Channel-side message id (Telegram update_id, GitLab note id, WhatsApp\n * key.id). Used to dedup re-seeds — if a message with this externalId is\n * already in the session, appendSeededMessages skips it. */\n externalId?: string\n}\n\nexport interface Session {\n id: string\n agentId: string\n channel: string\n chatId: string\n day: string // YYYY-MM-DD\n claudeSessionId?: string // Claude Code native session ID (for --resume)\n codexSessionId?: string // Codex CLI thread/session ID (for exec resume)\n opencodeSessionId?: string // OpenCode native session ID (for run --session)\n messages: SessionMessage[]\n createdAt: string\n updatedAt: string\n /** Number of completed turns on the current claudeSessionId. Resets to 0\n * when the session is rotated (cleared). Used to bound --resume growth:\n * Claude CLI replays the entire prior session on every turn, so long\n * sessions pay a linearly growing cache-read tax. */\n turnCount?: number\n /** CUMULATIVE billable input of the LAST turn on this claudeSessionId\n * (inputTokens + cacheReadTokens + cacheCreateTokens summed across every\n * API call in the agentic loop). Kept for observability/billing only —\n * a tool-heavy turn sums to millions via cache reads, so this must NOT\n * drive rotation decisions. See lastTurnContextTokens. */\n lastTurnInputTokens?: number\n /** End-of-turn CONTEXT size: the last API call's (input + cacheRead +\n * cacheCreate) inside the previous turn — how full the conversation\n * window actually is. This is the tier-2 rotation metric. Absent when\n * the turn ran through a non-streaming path (no per-call usage). */\n lastTurnContextTokens?: number\n}\n\nconst MAX_HISTORY_CHARS = 12000 // Keep last ~12k chars of history to fit in context\nconst MAX_MESSAGES = 30 // Keep last 30 messages max\n/** Default stale timeout. Overridable via SessionStore options so each node\n * can trade cache-hit ratio against \"fresh context\". 12 h matches how chat\n * channels are actually used: a task discussed in the morning must still\n * resume that evening (the old 45 min rotated on every coffee break and\n * read as amnesia); overnight silence starts fresh — with the rotation\n * memo carrying the open task forward. Size-based rotation (tier-2) now\n * measures true per-request context, so a long-lived session no longer\n * needs a short stale window to keep replay bounded. */\nconst DEFAULT_STALE_SESSION_MINUTES = 720\n/** Default hard cap on turns per Claude session — a backstop only. Claude\n * CLI `--resume` replays the prior session on each turn, so replay cost\n * grows with turn count; but the tier-2 check now measures the ACTUAL\n * per-request context, so it catches bloat directly. 40 turns lets an\n * active chat run all day without a mid-task reset. */\nconst DEFAULT_MAX_TURNS_PER_SESSION = 40\n/** Default tier-2 trigger. Claude bills the 1.5× long-context rate when a\n * single REQUEST's input exceeds 200K. Rotating at 180K of measured\n * context leaves headroom for the next turn's additions before we'd\n * enter the multiplier. (Must match the config schema default.) */\nconst DEFAULT_TIER_TWO_THRESHOLD_TOKENS = 180_000\n\n/** Session has a compacted summary prepended to its messages */\nconst COMPACTION_MARKER = \"[Compacted conversation summary\"\n\n/** Continuity memo captured from a dying session at rotation time —\n * injected deterministically into the chat's next fresh session. */\nexport interface RotationMemoRecord {\n memo: string\n reason: string\n capturedAt: string\n}\n\n/** A memo older than this never injects — a week-old \"open task\" brief is\n * more likely to mislead than to help. */\nconst ROTATION_MEMO_TTL_MS = 7 * 86_400_000\n\n/** Keywords that suggest the current message references another chat, an\n * earlier conversation, or a peer agent — and therefore benefits from the\n * cross-chat summary. Tuned for English + Arabic (Tunisian team usage);\n * extend as needed. Matched case-insensitively against raw substrings so\n * \"mentioned\" also catches \"mention\", \"mentioning\", etc. */\nconst CROSS_CHAT_HINT_PATTERNS: RegExp[] = [\n // English — temporal/conversational references\n /\\bearlier\\b/i, /\\bbefore\\b/i, /\\bjust said\\b/i, /\\btold (?:me|you|us|him|her|them)\\b/i,\n /\\bmention(?:ed|ing)?\\b/i, /\\bask(?:ed)? (?:me|you|him|her|them|about)\\b/i,\n /\\bping(?:ed)?\\b/i, /\\bdm\\b/i, /\\bdirect message\\b/i,\n // Explicit cross-chat references\n /\\bother (?:chat|group|conversation|thread)\\b/i, /\\bthat (?:chat|group|conversation|thread)\\b/i,\n /\\bsame (?:issue|thread|conversation)\\b/i, /\\bcontinuing\\b/i,\n // Bot mention — @something_bot / @handle (peer agent reference)\n /@\\w{2,}_?(?:bot|agent)\\b/i,\n // Arabic — conversational refs the team actually uses\n /قلت/, /قال(?:لك|لي|له|لها|لنا)?/, /قبل/, /الحين/, /الثاني/, /المجموعة/,\n]\n\nexport function referencesOtherChat(message: string): boolean {\n if (!message) return false\n return CROSS_CHAT_HINT_PATTERNS.some((re) => re.test(message))\n}\n\n/** Long-memory cues — phrases that imply the user is referencing something\n * beyond the current claude session window. Used by the registry to\n * pre-fetch a wider /recall window before the agent runs, so it doesn't\n * have to ask \"when?\" or guess. Returns the inferred lookback in days, or\n * 0 when no long-memory cue is present (default short-memory regime). */\nconst LONG_MEMORY_PATTERNS: Array<{ re: RegExp; days: number }> = [\n // Days-scale (~1 day)\n { re: /\\byesterday\\b/i, days: 2 },\n { re: /\\bأمس\\b/, days: 2 },\n // Days-scale (~3 days)\n { re: /\\bcouple (?:of )?days\\b/i, days: 4 },\n { re: /\\bfew days\\b/i, days: 4 },\n // Week-scale (~7 days)\n { re: /\\blast week\\b/i, days: 8 },\n { re: /\\bالأسبوع الماضي\\b/, days: 8 },\n { re: /\\bweek ago\\b/i, days: 8 },\n // Generic \"remember/previously/we discussed\" — moderate look-back\n { re: /\\bremember\\b/i, days: 3 },\n { re: /\\bpreviously\\b/i, days: 3 },\n { re: /\\bwe (?:discussed|talked|agreed|decided)\\b/i, days: 3 },\n { re: /\\bas (?:i|we) (?:said|told|mentioned)\\b/i, days: 3 },\n { re: /\\bcontinue from\\b/i, days: 3 },\n { re: /\\bتذكر\\b/, days: 3 },\n]\n\nexport function detectLongMemoryHint(message: string): { lookbackDays: number } | null {\n if (!message) return null\n let max = 0\n for (const p of LONG_MEMORY_PATTERNS) {\n if (p.re.test(message)) max = Math.max(max, p.days)\n }\n return max > 0 ? { lookbackDays: max } : null\n}\n\n/** True when appending `(role, name, content)` would duplicate the last\n * message in `messages`. Used to collapse retries and double-sends at\n * insert time, so dedup is stable across reload (duplicates never reach\n * disk in the first place). */\nfunction isDuplicateOfLast(\n messages: SessionMessage[],\n role: \"user\" | \"agent\",\n name: string,\n content: string,\n): boolean {\n if (messages.length === 0) return false\n const last = messages[messages.length - 1]\n return last.role === role && (last.name ?? \"\") === name && last.content === content\n}\n\n/**\n * The user message before `currentMessage`, for a dispatch that has already\n * recorded its own turn.\n *\n * By the time the session-continuity seat runs, executeInternal has already\n * appended the incoming request via addUserMessage — so the last user\n * message in the session IS the request being dispatched. Reading it\n * naively asks \"does this request continue itself\", which answers yes with\n * high confidence every time and pins the seat permanently shut.\n *\n * At most one tail entry can be the current message: addUserMessage\n * collapses consecutive exact duplicates, so a genuine re-send is stored\n * once. When the tail is NOT the current message (the append was deduped,\n * or the caller runs before it), the tail already is the prior request and\n * is returned unchanged.\n */\nexport function priorUserMessage(\n messages: SessionMessage[] | undefined,\n currentMessage: string,\n): string | null {\n const users = (messages ?? []).filter((m) => m.role === \"user\")\n const tail = users[users.length - 1]\n if (!tail) return null\n const prior = tail.content === currentMessage ? users[users.length - 2] : tail\n return prior?.content ?? null\n}\n\n/**\n * Up to `count` user requests from BEFORE the immediate predecessor,\n * oldest first. Companion to priorUserMessage, and skips the current turn\n * for the same reason.\n *\n * Returns fewer than `count` near the start of a conversation, and an\n * empty array when `count` is 0 — which is what keeps the widened state\n * strictly opt-in.\n */\nexport function priorUserRequests(\n messages: SessionMessage[] | undefined,\n currentMessage: string,\n count: number,\n): string[] {\n if (count <= 0) return []\n const users = (messages ?? []).filter((m) => m.role === \"user\")\n const tail = users[users.length - 1]\n // Drop the current turn, then the predecessor: priorUserMessage already\n // carries that one and repeating it would just pay twice for it.\n const end = (tail && tail.content === currentMessage ? users.length - 1 : users.length) - 1\n if (end <= 0) return []\n return users.slice(Math.max(0, end - count), end).map((m) => m.content)\n}\n\n/** Bucket an ISO timestamp into a stable 15-min window label (e.g. \"14:45\").\n * Used in history rendering so the prompt's bucket headers change at most\n * every 15 minutes — the prefix stays byte-stable across intra-bucket\n * messages and the server-side prompt cache is preserved. */\nfunction bucketLabel(iso: string): string {\n const m = iso.slice(11, 16) // HH:MM\n const hh = m.slice(0, 2)\n const mm = parseInt(m.slice(3, 5), 10)\n if (Number.isNaN(mm)) return m\n const qtr = Math.floor(mm / 15) * 15\n return `${hh}:${qtr.toString().padStart(2, \"0\")}`\n}\n\nexport class SessionStore {\n private sessionsDir: string\n private cache: Map<string, Session> = new Map()\n private staleMinutes: number\n private maxTurnsPerSession: number\n private tierTwoThresholdTokens: number\n private tierTwoThresholdTokensByChannel: Record<string, number>\n /** Resolves a channel name to its adapter — installed by the daemon after\n * channels are registered (see daemon/index.ts after router.register). The\n * cold-create branch consults it to call adapter.seedHistory(). Optional;\n * when unset, seeding is a no-op (back-compat for tests, CLI tools, and\n * any caller that constructs SessionStore standalone). */\n private adapterResolver?: (channel: string) => ChannelAdapter | undefined\n /** In-flight seed promises keyed by sessionKey. Coalesces concurrent\n * cold-creates against the same (agent, channel, chatId, day) so we hit\n * the channel API exactly once per fresh session. */\n private seedingPromises: Map<string, Promise<void>> = new Map()\n\n constructor(\n baseDir: string = process.cwd(),\n opts: {\n staleMinutes?: number\n maxTurnsPerSession?: number\n tierTwoThresholdTokens?: number\n tierTwoThresholdTokensByChannel?: Record<string, number>\n } = {},\n ) {\n this.sessionsDir = resolve(baseDir, \".agentx/sessions\")\n if (!existsSync(this.sessionsDir)) {\n mkdirSync(this.sessionsDir, { recursive: true })\n }\n this.staleMinutes = Math.max(1, opts.staleMinutes ?? DEFAULT_STALE_SESSION_MINUTES)\n this.maxTurnsPerSession = Math.max(2, opts.maxTurnsPerSession ?? DEFAULT_MAX_TURNS_PER_SESSION)\n this.tierTwoThresholdTokens = Math.max(50_000, opts.tierTwoThresholdTokens ?? DEFAULT_TIER_TWO_THRESHOLD_TOKENS)\n this.tierTwoThresholdTokensByChannel = Object.fromEntries(\n Object.entries(opts.tierTwoThresholdTokensByChannel ?? {}).map(([channel, tokens]) => [channel, Math.max(50_000, tokens)]),\n )\n }\n\n /** Install the channel-adapter resolver. Called once at daemon startup\n * after MessageRouter has registered every adapter. Hot-reload of channel\n * adapters re-uses the same router, so the resolver stays valid across\n * /reload cycles without re-installation. */\n setAdapterResolver(fn: (channel: string) => ChannelAdapter | undefined): void {\n this.adapterResolver = fn\n }\n\n /** Seed a fresh session from the live channel before any user/agent\n * messages get appended. No-op when:\n * - the session already has messages (warm cache or partially loaded)\n * - no adapter resolver is installed\n * - the channel adapter has no seedHistory method\n * Caller-supplied seeds (from a client that owns the transcript, such\n * as OpenCode) take the adapter's place.\n * Concurrent calls against the same key share one in-flight promise so\n * the channel API is hit exactly once per cold create. Errors are\n * swallowed — a seeding failure should never block a turn. */\n async seedIfEmpty(agentId: string, channel: string, chatId: string, callerSeeds?: SeededMessage[]): Promise<void> {\n if (callerSeeds?.length) {\n const session = this.getSession(agentId, channel, chatId)\n if (session.messages.length === 0) this.appendSeededMessages(session, callerSeeds)\n return\n }\n if (!this.adapterResolver) return\n const key = this.sessionKey(agentId, channel, chatId)\n const inFlight = this.seedingPromises.get(key)\n if (inFlight) return inFlight\n\n const session = this.getSession(agentId, channel, chatId)\n if (session.messages.length > 0) return\n\n const adapter = this.adapterResolver(channel)\n if (!adapter?.seedHistory) return\n\n const promise = (async () => {\n try {\n const seeds = await adapter.seedHistory!(chatId, {\n maxMessages: MAX_MESSAGES,\n maxChars: MAX_HISTORY_CHARS,\n })\n if (seeds.length > 0) this.appendSeededMessages(session, seeds)\n } catch {\n // best-effort\n }\n })()\n this.seedingPromises.set(key, promise)\n try {\n await promise\n } finally {\n this.seedingPromises.delete(key)\n }\n }\n\n /** Append seeded messages to a session in chronological order, deduping\n * by externalId against anything already present. Marks each entry with\n * `seeded: true` so observers can distinguish them; the model's prompt\n * (buildHistoryContext) renders them identically to live turns. */\n private appendSeededMessages(session: Session, seeds: SeededMessage[]): void {\n if (seeds.length === 0) return\n const existingIds = new Set<string>()\n for (const m of session.messages) {\n if (m.externalId) existingIds.add(m.externalId)\n }\n let appended = 0\n for (const s of seeds) {\n if (s.externalId && existingIds.has(s.externalId)) continue\n session.messages.push({\n role: s.role,\n name: s.name,\n content: s.content,\n timestamp: s.timestamp,\n seeded: true,\n externalId: s.externalId,\n })\n appended++\n }\n if (appended === 0) return\n this.trim(session)\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Build a deterministic session key.\n */\n private sessionKey(agentId: string, channel: string, chatId: string): string {\n const day = new Date().toISOString().slice(0, 10) // YYYY-MM-DD\n return `${agentId}:${channel}:${chatId}:${day}`\n }\n\n private sessionFile(key: string): string {\n // Sanitize key for filesystem\n const safe = key.replace(/[^a-zA-Z0-9_:-]/g, \"_\")\n return resolve(this.sessionsDir, `${safe}.json`)\n }\n\n /**\n * Get or create a session for this agent+channel+chat+day.\n */\n getSession(agentId: string, channel: string, chatId: string): Session {\n const key = this.sessionKey(agentId, channel, chatId)\n\n // Check cache\n if (this.cache.has(key)) {\n return this.cache.get(key)!\n }\n\n // Try loading from disk\n const file = this.sessionFile(key)\n if (existsSync(file)) {\n try {\n const data = JSON.parse(readFileSync(file, \"utf-8\")) as Session\n this.cache.set(key, data)\n return data\n } catch {\n // Corrupted file — start fresh\n }\n }\n\n // Create new session. The key is day-scoped, so without carry-over a\n // conversation crossing UTC midnight would silently lose its --resume\n // id — guaranteed daily amnesia even mid-task. Seed continuity\n // metadata (native session ids + rotation counters + updatedAt) from\n // yesterday's record; the normal stale/tier-2/max-turns checks then\n // decide whether to actually resume. Messages stay day-scoped.\n const prev = this.loadPreviousDaySession(agentId, channel, chatId)\n const day = new Date().toISOString().slice(0, 10)\n const session: Session = {\n id: key,\n agentId,\n channel,\n chatId,\n day,\n messages: [],\n createdAt: new Date().toISOString(),\n updatedAt: prev?.updatedAt ?? new Date().toISOString(),\n claudeSessionId: prev?.claudeSessionId,\n codexSessionId: prev?.codexSessionId,\n turnCount: prev?.turnCount,\n lastTurnInputTokens: prev?.lastTurnInputTokens,\n lastTurnContextTokens: prev?.lastTurnContextTokens,\n }\n\n this.cache.set(key, session)\n this.save(session)\n return session\n }\n\n /** Yesterday's (UTC) session record for the same triple, or null. Only\n * one day back — a gap of 2+ days is stale at any sane staleMinutes,\n * so scanning further would never change the outcome. */\n private loadPreviousDaySession(agentId: string, channel: string, chatId: string): Session | null {\n const prevDay = new Date(Date.now() - 86_400_000).toISOString().slice(0, 10)\n const prevKey = `${agentId}:${channel}:${chatId}:${prevDay}`\n const file = this.sessionFile(prevKey)\n if (!existsSync(file)) return null\n try {\n return JSON.parse(readFileSync(file, \"utf-8\")) as Session\n } catch {\n return null\n }\n }\n\n /**\n * Add a user message to the session. Consecutive duplicates from the same\n * sender are collapsed — when a user re-sends the exact same text (retry,\n * accidental double-tap, telegram glitches), storing every copy bloats the\n * replayed history and re-burns tokens on every subsequent turn.\n */\n addUserMessage(agentId: string, channel: string, chatId: string, senderName: string, content: string): void {\n const session = this.getSession(agentId, channel, chatId)\n if (isDuplicateOfLast(session.messages, \"user\", senderName, content)) {\n return\n }\n session.messages.push({\n role: \"user\",\n name: senderName,\n content,\n timestamp: new Date().toISOString(),\n })\n this.trim(session)\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Pop the last user message if it matches `expectedContent` (exact equality\n * after trim). Used by the operator Update flow when Stop was pressed\n * first — the cancelled user turn has no agent reply, so leaving it in\n * history makes the next Update read as a bare orphan ask. Match guard\n * prevents removing something a concurrent path just added.\n */\n removeLastUserMessageIfMatches(\n agentId: string,\n channel: string,\n chatId: string,\n expectedContent: string,\n ): boolean {\n const session = this.getSession(agentId, channel, chatId)\n const last = session.messages[session.messages.length - 1]\n if (!last || last.role !== \"user\") return false\n if ((last.content || \"\").trim() !== (expectedContent || \"\").trim()) return false\n session.messages.pop()\n session.updatedAt = new Date().toISOString()\n this.save(session)\n return true\n }\n\n /**\n * Add an agent response to the session. Same dedup rule as user messages —\n * retries and duplicate responses shouldn't be replayed on every turn.\n */\n addAgentMessage(agentId: string, channel: string, chatId: string, content: string): void {\n const session = this.getSession(agentId, channel, chatId)\n if (isDuplicateOfLast(session.messages, \"agent\", agentId, content)) {\n return\n }\n session.messages.push({\n role: \"agent\",\n name: agentId,\n content,\n timestamp: new Date().toISOString(),\n })\n this.trim(session)\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Get the Claude Code session ID for this conversation (for --resume).\n * Returns undefined if no session yet (first message of the day).\n */\n getClaudeSessionId(agentId: string, channel: string, chatId: string): string | undefined {\n const session = this.getSession(agentId, channel, chatId)\n return session.claudeSessionId\n }\n\n /**\n * Store the Claude Code session ID after first invocation.\n */\n setClaudeSessionId(agentId: string, channel: string, chatId: string, claudeSessionId: string): void {\n const session = this.getSession(agentId, channel, chatId)\n session.claudeSessionId = claudeSessionId\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n getCodexSessionId(agentId: string, channel: string, chatId: string): string | undefined {\n const session = this.getSession(agentId, channel, chatId)\n return session.codexSessionId\n }\n\n setCodexSessionId(agentId: string, channel: string, chatId: string, codexSessionId: string): void {\n const session = this.getSession(agentId, channel, chatId)\n session.codexSessionId = codexSessionId\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n getOpenCodeSessionId(agentId: string, channel: string, chatId: string): string | undefined {\n const session = this.getSession(agentId, channel, chatId)\n return session.opencodeSessionId\n }\n\n setOpenCodeSessionId(agentId: string, channel: string, chatId: string, opencodeSessionId: string): void {\n const session = this.getSession(agentId, channel, chatId)\n session.opencodeSessionId = opencodeSessionId\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Build conversation history string to prepend to the prompt.\n * Returns empty string if no history.\n * Used for non-claude-code tiers that don't have native sessions.\n */\n buildHistoryContext(\n agentId: string,\n channel: string,\n chatId: string,\n opts: { maxMessages?: number; maxChars?: number } = {},\n ): string {\n const session = this.getSession(agentId, channel, chatId)\n if (session.messages.length === 0) return \"\"\n const maxMessages = Math.max(1, opts.maxMessages ?? MAX_MESSAGES)\n const maxChars = Math.max(200, opts.maxChars ?? MAX_HISTORY_CHARS)\n const messages: SessionMessage[] = []\n let chars = 0\n for (let i = session.messages.length - 1; i >= 0 && messages.length < maxMessages; i--) {\n const msg = session.messages[i]\n chars += msg.content.length\n messages.push(msg)\n if (chars >= maxChars) break\n }\n messages.reverse()\n\n const lines: string[] = [\n `[Conversation history for today (${session.day})]`,\n ]\n\n // Bucket messages into 15-min windows. Stamping every line with its own\n // HH:MM broke the server-side prompt cache every turn (each new message\n // adds a never-seen timestamp near the tail). A bucket header changes\n // only every 15 min, so most turns within the window preserve cache.\n let currentBucket = \"\"\n for (const msg of messages) {\n const bucket = bucketLabel(msg.timestamp)\n if (bucket !== currentBucket) {\n lines.push(`— ${bucket} —`)\n currentBucket = bucket\n }\n const name = msg.name || (msg.role === \"user\" ? \"User\" : \"Agent\")\n lines.push(`${name}: ${msg.content}`)\n }\n\n lines.push(\"[End of history — respond to the latest message above]\")\n lines.push(\"\")\n\n return lines.join(\"\\n\")\n }\n\n /**\n * Recall conversation turns across multiple stored sessions for an agent.\n *\n * Used by the `/recall` HTTP endpoint and the [Conversation Recall] skill —\n * when an agent receives a message that references prior context (anaphora,\n * \"as discussed\", \"correct me if I'm wrong\"), it can call this to fetch\n * the actual prior turns rather than guessing or running cold-start tool\n * calls. Read-only; no side effects.\n *\n * Pagination: cursor by timestamp, descending (newest first). Pass the\n * previous response's `oldestTs` as `before` to walk further back.\n *\n * Scoping: required `agentId`. `channel`/`chatId` narrow to a specific\n * thread; omit them to recall across all of this agent's conversations.\n * The store is per-agent on disk so we never leak another agent's chats.\n */\n recallTurns(opts: {\n agentId: string\n channel?: string\n chatId?: string\n before?: string\n after?: string\n /** Convenience override for `after`: number of days back from now. */\n lookbackDays?: number\n limit?: number\n query?: string\n participants?: string[]\n }): {\n turns: Array<{\n ts: string\n role: \"user\" | \"agent\"\n senderName: string\n content: string\n channel: string\n chatId: string\n day: string\n sessionFile: string\n }>\n oldestTs: string | null\n hasMore: boolean\n totalScanned: number\n } {\n const limit = Math.min(Math.max(opts.limit ?? 10, 1), 100)\n // Default window: TODAY (UTC). Per the short-vs-long-memory split, most\n // recall calls are about \"what did we just say\" — capping the window to\n // today keeps the result tight and noise-free. Caller widens explicitly\n // when the user message signals long memory (\"yesterday\", \"last week\",\n // \"remember when…\") via `lookbackDays` or an explicit `after`.\n const todayStart = new Date()\n todayStart.setUTCHours(0, 0, 0, 0)\n const defaultAfter =\n typeof opts.lookbackDays === \"number\" && opts.lookbackDays > 0\n ? new Date(Date.now() - opts.lookbackDays * 24 * 60 * 60 * 1000).toISOString()\n : todayStart.toISOString()\n const after = opts.after ?? defaultAfter\n const before = opts.before ?? new Date(Date.now() + 1000).toISOString()\n\n const safeAgentId = opts.agentId.replace(/[^a-zA-Z0-9_:-]/g, \"_\")\n let allFiles: string[]\n try {\n allFiles = readdirSync(this.sessionsDir).filter(f =>\n f.startsWith(`${safeAgentId}:`) && f.endsWith(\".json\"),\n )\n } catch {\n return { turns: [], oldestTs: null, hasMore: false, totalScanned: 0 }\n }\n\n // Day-level prefilter via the trailing `:YYYY-MM-DD.json` segment so we\n // don't open/parse files outside the requested window.\n const afterDay = after.slice(0, 10)\n const beforeDay = before.slice(0, 10)\n const dayRe = /:(\\d{4}-\\d{2}-\\d{2})\\.json$/\n const candidateFiles = allFiles.filter(f => {\n const m = f.match(dayRe)\n if (!m) return false\n const day = m[1]\n return day >= afterDay && day <= beforeDay\n })\n\n type CollectedTurn = {\n ts: string\n role: \"user\" | \"agent\"\n senderName: string\n content: string\n channel: string\n chatId: string\n day: string\n sessionFile: string\n }\n const collected: CollectedTurn[] = []\n let totalScanned = 0\n const queryLower = opts.query?.toLowerCase()\n const participantSet = opts.participants?.length ? new Set(opts.participants) : undefined\n\n for (const file of candidateFiles) {\n let data: Session\n try {\n data = JSON.parse(readFileSync(resolve(this.sessionsDir, file), \"utf-8\")) as Session\n } catch {\n continue\n }\n if (data.agentId !== opts.agentId) continue\n if (opts.channel && data.channel !== opts.channel) continue\n if (opts.chatId && data.chatId !== opts.chatId) continue\n\n for (const msg of data.messages) {\n totalScanned++\n if (!msg.timestamp || msg.timestamp < after || msg.timestamp >= before) continue\n if (participantSet && msg.role === \"user\" && !participantSet.has(msg.name ?? \"\")) continue\n if (queryLower && !msg.content.toLowerCase().includes(queryLower)) continue\n collected.push({\n ts: msg.timestamp,\n role: msg.role,\n senderName: msg.name ?? \"\",\n content: msg.content,\n channel: data.channel,\n chatId: data.chatId,\n day: data.day,\n sessionFile: file,\n })\n }\n }\n\n collected.sort((a, b) => b.ts.localeCompare(a.ts))\n\n const page = collected.slice(0, limit)\n const oldestTs = page.length > 0 ? page[page.length - 1].ts : null\n const hasMore = collected.length > limit\n\n return { turns: page, oldestTs, hasMore, totalScanned }\n }\n\n /** Cross-agent view of a chat. Aggregates session messages from EVERY\n * agent that has a session for `(channel, chatId)` in the requested\n * window, sorts oldest-first, and dedups exact (timestamp, role, name,\n * content) collisions (which happen when two agents in the same group\n * both record the same inbound).\n *\n * Differs from recallTurns in two ways:\n * 1. Not scoped to one agentId — answers \"what's in this chat across\n * all agents\", which is the question the cx→devops→marketing thread\n * on 2026-04-29 needed and didn't have.\n * 2. Returns oldest-first so the caller reads it like a transcript;\n * recallTurns returns newest-first because it paginates.\n *\n * Bounded by the same MAX_MESSAGES default as buildHistoryContext so\n * callers don't accidentally pull a 5000-turn group into one prompt. */\n recentByChatId(opts: {\n channel: string\n chatId: string\n sinceISO?: string\n limit?: number\n }): Array<{\n ts: string\n role: \"user\" | \"agent\"\n senderName: string\n content: string\n agentId: string\n sessionFile: string\n }> {\n const limit = Math.min(Math.max(opts.limit ?? MAX_MESSAGES, 1), 200)\n // Default window: last 24h. Wider than recallTurns' \"today only\" because\n // the typical use is \"what was just said\" — covers overnight gaps that\n // span midnight UTC.\n const sinceISO = opts.sinceISO ?? new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()\n const sinceDay = sinceISO.slice(0, 10)\n\n let allFiles: string[]\n try {\n allFiles = readdirSync(this.sessionsDir).filter((f) => f.endsWith(\".json\"))\n } catch {\n return []\n }\n\n // The session-file naming convention is `{agentId}:{channel}:{chatId}:{day}.json`.\n // Build a substring key that captures channel+chatId; the agentId prefix\n // and day suffix vary so we filter those after parse. Note `chatId` may\n // itself contain colons (GitLab chatIds look like \"group/project:issue:123\"),\n // so we can't naively split on \":\". Cheap pre-filter: must contain\n // `:${channel}:${chatId}:` as a substring.\n const key = `:${opts.channel}:${opts.chatId}:`\n const candidates = allFiles.filter((f) => {\n if (!f.includes(key)) return false\n // Day suffix prefilter — skip files whose day is older than sinceDay.\n const m = f.match(/:(\\d{4}-\\d{2}-\\d{2})\\.json$/)\n if (!m) return false\n return m[1] >= sinceDay\n })\n\n type Row = {\n ts: string\n role: \"user\" | \"agent\"\n senderName: string\n content: string\n agentId: string\n sessionFile: string\n }\n const rows: Row[] = []\n for (const file of candidates) {\n let data: Session\n try {\n data = JSON.parse(readFileSync(resolve(this.sessionsDir, file), \"utf-8\")) as Session\n } catch {\n continue\n }\n // Defensive re-check after parse — substring match could collide with\n // an agent whose id literally contains \":telegram:1816212449:\".\n if (data.channel !== opts.channel || data.chatId !== opts.chatId) continue\n\n for (const msg of data.messages) {\n if (!msg.timestamp || msg.timestamp < sinceISO) continue\n rows.push({\n ts: msg.timestamp,\n role: msg.role,\n senderName: msg.name ?? \"\",\n content: msg.content,\n agentId: data.agentId,\n sessionFile: file,\n })\n }\n }\n\n // Dedup exact collisions across agents (same inbound recorded by two\n // agents in the same group). Key on (ts, role, name, content).\n const seen = new Set<string>()\n const dedupped: Row[] = []\n for (const r of rows) {\n const k = `${r.ts}|${r.role}|${r.senderName}|${r.content}`\n if (seen.has(k)) continue\n seen.add(k)\n dedupped.push(r)\n }\n\n dedupped.sort((a, b) => a.ts.localeCompare(b.ts))\n // Take the most recent `limit` (last entries when sorted oldest-first).\n return dedupped.slice(-limit)\n }\n\n /**\n * Check if a native CLI session is stale (idle too long for resume to be useful).\n * When stale, the resumed context will be too far behind — better to start fresh.\n */\n /**\n * How long since this session last did anything, in ms.\n *\n * Used to reason about whether the provider's prompt cache for it is\n * still warm — a cold cache changes which model is cheaper, because the\n * saving from a cached read only exists while the cache exists. Returns\n * null when there is no native session to resume.\n */\n sessionIdleMs(agentId: string, channel: string, chatId: string): number | null {\n const session = this.getSession(agentId, channel, chatId)\n if (!session.claudeSessionId && !session.codexSessionId && !session.opencodeSessionId) return null\n return Date.now() - new Date(session.updatedAt).getTime()\n }\n\n /**\n * Whether the session has been idle longer than staleMinutes. Pass the\n * idle time read before this turn touched the session: recording the\n * inbound message bumps updatedAt, so reading it afterwards always sees\n * an idle time of about zero.\n */\n isSessionStale(agentId: string, channel: string, chatId: string, idleMs?: number | null): boolean {\n const session = this.getSession(agentId, channel, chatId)\n if (!session.claudeSessionId && !session.codexSessionId && !session.opencodeSessionId) return false\n const elapsed = idleMs ?? Date.now() - new Date(session.updatedAt).getTime()\n return elapsed > this.staleMinutes * 60 * 1000\n }\n\n /**\n * Build a summary of recent messages from OTHER sessions for the same agent today.\n * This bridges the cross-chat amnesia gap — if someone shared info in a DM,\n * the group session gets a hint about it.\n *\n * Gated by `message`: cross-chat hints are expensive (up to ~1.5K tokens of\n * unrelated conversation) AND cache-breaking (the content changes as other\n * chats get traffic, invalidating the prompt cache every turn). We only\n * return hints when the current message actually references another\n * conversation or a peer agent. Pass `message = \"\"` to force-include\n * (legacy behavior) — useful for testing.\n */\n getCrossSessionSummary(\n agentId: string,\n channel: string,\n chatId: string,\n message?: string,\n ): string {\n if (message !== undefined && message !== \"\" && !referencesOtherChat(message)) {\n return \"\"\n }\n const day = new Date().toISOString().slice(0, 10)\n const currentKey = this.sessionKey(agentId, channel, chatId)\n\n try {\n const files = readdirSync(this.sessionsDir).filter(f =>\n f.startsWith(agentId.replace(/[^a-zA-Z0-9_:-]/g, \"_\")) &&\n f.includes(day) &&\n f.endsWith(\".json\")\n )\n\n const hints: string[] = []\n\n for (const file of files) {\n try {\n const data = JSON.parse(readFileSync(resolve(this.sessionsDir, file), \"utf-8\")) as Session\n if (data.id === currentKey) continue // skip own session\n if (data.messages.length === 0) continue\n\n // Take the last few messages as a hint\n const recent = data.messages.slice(-4)\n const isDM = !data.chatId.startsWith(\"-\") && /^\\d+$/.test(data.chatId)\n const scope = isDM ? \"DM\" : data.chatId\n const lines = recent.map(m => {\n const time = m.timestamp.slice(11, 16)\n return ` [${time}] ${m.name || m.role}: ${m.content.slice(0, 200)}`\n })\n hints.push(`From ${scope} (${data.channel}):\\n${lines.join(\"\\n\")}`)\n } catch {\n // Skip corrupted files\n }\n }\n\n if (hints.length === 0) return \"\"\n\n return [\n \"[Cross-chat context — recent activity from your other conversations today]\",\n ...hints,\n \"[End cross-chat context — the user in THIS chat may be referring to actions from above]\",\n \"\",\n ].join(\"\\n\")\n } catch {\n return \"\"\n }\n }\n\n /**\n * Check if session history needs compaction and compact if so.\n * Call this after adding messages. Returns true if compaction was performed.\n */\n async compactIfNeeded(\n agentId: string,\n channel: string,\n chatId: string,\n memoryStore?: import(\"./memory-store\").MemoryStore,\n ): Promise<{ compacted: boolean; qualityScore?: number; lostEntities?: string[]; drift?: import(\"./drift-detection\").DriftReport }> {\n const session = this.getSession(agentId, channel, chatId)\n const { needsCompaction, compactSession, applyCompaction } = await import(\"./compaction\")\n\n if (!needsCompaction(session.messages)) return { compacted: false }\n\n const result = await compactSession(session.messages, agentId, memoryStore, { channel, chatId })\n if (result.compactedCount === 0) return { compacted: false }\n\n session.messages = applyCompaction(session.messages, result)\n session.updatedAt = new Date().toISOString()\n\n // We deliberately KEEP native session IDs, turnCount, and lastTurnInputTokens.\n //\n // Why: AgentX's `session.messages` is a stored copy used to seed a fresh\n // Claude session whenever one legitimately rotates (tier-2, max-turns,\n // stale, restart). Claude's own session has its own copy of the\n // conversation that --resume replays — it doesn't read from\n // session.messages. So compacting our copy doesn't reduce what Claude\n // sees on resume; dropping claudeSessionId here was just paying a fresh\n // ~16K-token cache-create tax on the next call without any context\n // benefit. Particularly painful for bursty workloads (project-bot\n // queues) where each burst trips compaction and forces N cache rebuilds\n // per Max-plan window.\n //\n // The compacted summary gets used at the next legitimate rotation —\n // exactly when it matters. Until then, Claude keeps its hot session.\n\n this.save(session)\n return {\n compacted: true,\n qualityScore: result.qualityScore,\n lostEntities: result.lostEntities,\n drift: result.drift,\n }\n }\n\n /**\n * Check if current session already has a compacted summary.\n */\n hasCompaction(agentId: string, channel: string, chatId: string): boolean {\n const session = this.getSession(agentId, channel, chatId)\n return session.messages.length > 0 &&\n session.messages[0].content.startsWith(COMPACTION_MARKER)\n }\n\n /**\n * Clear stored native CLI session IDs so next invocation starts fresh.\n * Also resets the per-session turn counter and last-turn input size —\n * those are only meaningful relative to the current native session.\n */\n clearClaudeSessionId(agentId: string, channel: string, chatId: string): void {\n const session = this.getSession(agentId, channel, chatId)\n delete session.claudeSessionId\n delete session.codexSessionId\n delete session.opencodeSessionId\n delete session.turnCount\n delete session.lastTurnInputTokens\n delete session.lastTurnContextTokens\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Reset the entire AgentX-side conversation for a delegated fresh session.\n * Claude needs the native session id cleared; Codex and other non-native\n * runtimes also need the rendered message history removed or they will see\n * stale turns through buildHistoryContext().\n */\n clearSession(agentId: string, channel: string, chatId: string): void {\n const session = this.getSession(agentId, channel, chatId)\n session.messages = []\n delete session.claudeSessionId\n delete session.codexSessionId\n delete session.opencodeSessionId\n delete session.turnCount\n delete session.lastTurnInputTokens\n delete session.lastTurnContextTokens\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Check whether this session has hit the max-turns cap. Rotate when true\n * to stop --resume replay from growing unbounded across a long chat.\n */\n shouldRotateByTurns(agentId: string, channel: string, chatId: string): boolean {\n const session = this.getSession(agentId, channel, chatId)\n if (!session.claudeSessionId && !session.codexSessionId && !session.opencodeSessionId) return false\n return (session.turnCount ?? 0) >= this.maxTurnsPerSession\n }\n\n /**\n * Check whether the LAST turn left the conversation context near the\n * tier-2 boundary. Uses `lastTurnContextTokens` (the final API call's\n * per-request input — the true context size) when the runtime captured\n * it; falls back to the cumulative turn total only when it didn't.\n * The cumulative number sums cache reads across every call in an\n * agentic turn (easily 10-20× the real context), so preferring the\n * per-call metric is what stops rotation from firing on every\n * tool-heavy turn — the \"agent forgets mid-task\" bug.\n */\n shouldRotateByTierTwo(agentId: string, channel: string, chatId: string): boolean {\n const session = this.getSession(agentId, channel, chatId)\n if (!session.claudeSessionId && !session.codexSessionId && !session.opencodeSessionId) return false\n const contextSize = session.lastTurnContextTokens ?? session.lastTurnInputTokens ?? 0\n return contextSize >= this.getTierTwoThresholdTokens(channel)\n }\n\n /**\n * Record a completed turn's token usage and bump the turn counter.\n * Called after a successful native CLI response returns with usage info.\n * `contextTokens` is the end-of-turn per-request context size (see\n * AgentResponse.contextTokens); undefined on non-streaming paths.\n */\n recordTurnUsage(\n agentId: string,\n channel: string,\n chatId: string,\n usage: { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreateTokens: number },\n contextTokens?: number,\n ): void {\n const session = this.getSession(agentId, channel, chatId)\n session.turnCount = (session.turnCount ?? 0) + 1\n session.lastTurnInputTokens =\n (usage.inputTokens || 0) + (usage.cacheReadTokens || 0) + (usage.cacheCreateTokens || 0)\n // Overwrite (including back to undefined) rather than keep a stale\n // value: a non-streaming turn after a streaming one must not reuse\n // the older context reading.\n session.lastTurnContextTokens = contextTokens\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n // --- Rotation memos -----------------------------------------------------\n //\n // When a session rotates, a Haiku call distills the dying session into a\n // continuity memo (rotation-memo.ts). Storing it ONLY in MemoryStore made\n // carry-over BM25-luck: the memo rarely scored high enough to surface on\n // the very next turn, so rotation still read as amnesia. This map keeps\n // the latest memo per chat for DETERMINISTIC injection into the next\n // fresh session. Overwritten on each rotation; TTL guards ancient memos.\n\n private rotationMemoFile(): string {\n return resolve(this.sessionsDir, \"_rotation-memos.json\")\n }\n\n private readRotationMemos(): Record<string, RotationMemoRecord> {\n try {\n const parsed = JSON.parse(readFileSync(this.rotationMemoFile(), \"utf-8\"))\n return parsed && typeof parsed === \"object\" ? parsed : {}\n } catch {\n return {}\n }\n }\n\n setRotationMemo(agentId: string, channel: string, chatId: string, memo: string, reason: string): void {\n const memos = this.readRotationMemos()\n memos[`${agentId}:${channel}:${chatId}`] = {\n memo,\n reason,\n capturedAt: new Date().toISOString(),\n }\n writeFileSync(this.rotationMemoFile(), JSON.stringify(memos, null, 2))\n }\n\n /** Latest memo for this chat, or null when absent/expired (7-day TTL). */\n getRotationMemo(agentId: string, channel: string, chatId: string): RotationMemoRecord | null {\n const rec = this.readRotationMemos()[`${agentId}:${channel}:${chatId}`]\n if (!rec?.memo) return null\n const age = Date.now() - Date.parse(rec.capturedAt)\n if (!Number.isFinite(age) || age > ROTATION_MEMO_TTL_MS) return null\n return rec\n }\n\n /** Diagnostic getters — used by registry logging. */\n getTurnCount(agentId: string, channel: string, chatId: string): number {\n return this.getSession(agentId, channel, chatId).turnCount ?? 0\n }\n getLastTurnInputTokens(agentId: string, channel: string, chatId: string): number {\n return this.getSession(agentId, channel, chatId).lastTurnInputTokens ?? 0\n }\n /** The rotation metric shouldRotateByTierTwo actually used: per-request\n * context size when available, else the cumulative fallback. */\n getLastTurnContextTokens(agentId: string, channel: string, chatId: string): number {\n const s = this.getSession(agentId, channel, chatId)\n return s.lastTurnContextTokens ?? s.lastTurnInputTokens ?? 0\n }\n /** The per-request context reading alone, with no cumulative fallback.\n * Null when the last turn left none (a non-streaming turn). */\n getRawLastTurnContextTokens(agentId: string, channel: string, chatId: string): number | null {\n return this.getSession(agentId, channel, chatId).lastTurnContextTokens ?? null\n }\n getMaxTurnsPerSession(): number { return this.maxTurnsPerSession }\n getTierTwoThresholdTokens(channel?: string): number {\n return (channel ? this.tierTwoThresholdTokensByChannel[channel] : undefined) ?? this.tierTwoThresholdTokens\n }\n\n /**\n * Trim session to stay within limits.\n */\n private trim(session: Session): void {\n const beforeCount = session.messages.length\n // Trim by message count\n if (session.messages.length > MAX_MESSAGES) {\n session.messages = session.messages.slice(-MAX_MESSAGES)\n }\n\n // Trim by total character count\n let totalChars = session.messages.reduce((sum, m) => sum + m.content.length, 0)\n while (totalChars > MAX_HISTORY_CHARS && session.messages.length > 2) {\n const removed = session.messages.shift()!\n totalChars -= removed.content.length\n }\n\n // Surface silent drops under `--debug context`. Without this, a long\n // chat that rolls past the 30-message / 12K-char caps quietly loses\n // older turns and the operator has no signal — exactly the failure\n // mode that disguises itself as \"agent forgot\" amnesia.\n const dropped = beforeCount - session.messages.length\n if (dropped > 0) {\n debug.cat(\n \"context\",\n `[${session.agentId}] ${session.channel}:${session.chatId} trim dropped ${dropped} message(s): ${beforeCount} -> ${session.messages.length} (${totalChars}c)`,\n )\n }\n }\n\n private save(session: Session): void {\n try {\n const file = this.sessionFile(session.id)\n writeFileSync(file, JSON.stringify(session, null, 2))\n } catch {\n // Best-effort persistence\n }\n }\n}\n","// --- Rate limiter for agent tasks ---\n// Prevents message floods from exhausting Claude API/subscription.\n// Per-agent, sliding window. `acquire()` queues callers when the window is\n// full instead of failing them outright — good for chatty channels where\n// dropping a message would look like the bot is broken.\n\nexport class RateLimiter {\n private windows: Map<string, number[]> = new Map()\n private maxPerMinute: number\n private maxPerHour: number\n\n constructor(maxPerMinute: number = 10, maxPerHour: number = 100) {\n this.maxPerMinute = maxPerMinute\n this.maxPerHour = maxPerHour\n }\n\n /**\n * Check if an agent can process a task right now. Does NOT wait. Call\n * `acquire()` if you want queueing behaviour.\n */\n check(agentId: string): { allowed: boolean; reason?: string; waitMs?: number } {\n const now = Date.now()\n const timestamps = this.windows.get(agentId) || []\n\n // Clean old entries (older than 1 hour).\n const hourAgo = now - 3600_000\n const recent = timestamps.filter((t) => t > hourAgo)\n\n // Per-minute window.\n const minuteAgo = now - 60_000\n const inMinute = recent.filter((t) => t > minuteAgo)\n if (inMinute.length >= this.maxPerMinute) {\n const oldest = inMinute[0]\n const waitMs = Math.max(50, oldest + 60_000 - now)\n return {\n allowed: false,\n reason: `Rate limit: ${inMinute.length}/${this.maxPerMinute} per minute`,\n waitMs,\n }\n }\n\n // Per-hour window.\n if (recent.length >= this.maxPerHour) {\n const oldest = recent[0]\n const waitMs = Math.max(1000, oldest + 3600_000 - now)\n return {\n allowed: false,\n reason: `Rate limit: ${recent.length}/${this.maxPerHour} per hour`,\n waitMs,\n }\n }\n\n // Record this slot and allow.\n recent.push(now)\n this.windows.set(agentId, recent)\n return { allowed: true }\n }\n\n /**\n * Wait until a slot opens for this agent, then record + return ok. If the\n * caller exceeds `maxWaitMs`, give up with the last rate-limit reason. A\n * concurrent task that acquires a slot first will push THIS task's wake-up\n * further out — that's intended: FIFO-ish through the agent's normal\n * message queue, no starvation in practice.\n *\n * `onWait` fires once at the start of waiting so callers can surface the\n * \"queued\" state (logging, dashboard hint, etc).\n */\n async acquire(\n agentId: string,\n opts: { maxWaitMs?: number; onWait?: (reason: string, waitMs: number) => void } = {},\n ): Promise<{ ok: true } | { ok: false; reason: string }> {\n const maxWaitMs = opts.maxWaitMs ?? 5 * 60_000\n const deadline = Date.now() + maxWaitMs\n let firstWait = true\n\n while (true) {\n const r = this.check(agentId)\n if (r.allowed) return { ok: true }\n if (Date.now() >= deadline) {\n return { ok: false, reason: `${r.reason} (waited ${Math.round(maxWaitMs / 1000)}s, giving up)` }\n }\n if (firstWait) {\n firstWait = false\n opts.onWait?.(r.reason!, r.waitMs!)\n }\n // Cap the sleep so concurrent waiters re-check often enough to grab the\n // next slot in turn, rather than all firing at the same instant.\n const remaining = deadline - Date.now()\n const sleepMs = Math.min(r.waitMs ?? 1000, remaining, 5_000)\n await new Promise((resolve) => setTimeout(resolve, sleepMs))\n }\n }\n\n /**\n * Current usage for an agent — used by the dashboard and `agentx usage`.\n */\n usage(agentId: string): { lastMinute: number; lastHour: number } {\n const now = Date.now()\n const timestamps = this.windows.get(agentId) || []\n return {\n lastMinute: timestamps.filter((t) => t > now - 60_000).length,\n lastHour: timestamps.filter((t) => t > now - 3600_000).length,\n }\n }\n}\n","// --- Agent Context Engine ---\n//\n// Structured, layered context with token budget management.\n// Each context layer has a priority, max tokens, and rules.\n//\n// Layers (highest priority first):\n// 1. Channel — where the message came from + channel-specific rules\n// 2. Scope — group/personal/project + scope-specific constraints\n// 3. Landscape — world model: team roster, channels, rules (cached at startup)\n// 4. Identity — who the agent is, what it can do\n// 5. Intent — what the user is asking about (extracted from message)\n// 6. Artifacts — project, issue, MR, file references\n// 7. History — group conversation log or session history\n// 8. Wiki — relevant knowledge articles\n//\n// Token budget: each layer gets a max allocation. Total capped at configurable limit.\n// Layers are rendered top-down; if budget exhausted, lower layers are truncated or skipped.\n\nexport interface ContextLayer {\n name: string\n priority: number // lower = higher priority (1 = most important)\n maxTokens: number // max tokens for this layer\n content: string // rendered text\n tags: string[] // metadata tags for filtering/matching\n rules?: string[] // constraints/instructions specific to this layer\n}\n\nexport interface ContextConfig {\n /** Total token budget for all context combined (default: 4000) */\n totalBudget: number\n /** Per-layer budgets override (layer name -> max tokens) */\n layerBudgets?: Record<string, number>\n}\n\nconst DEFAULT_CONFIG: ContextConfig = {\n totalBudget: 6000,\n layerBudgets: {\n channel: 200,\n scope: 200,\n landscape: 800,\n identity: 200,\n bootstrap: 500,\n references: 500,\n intent: 200,\n artifacts: 500,\n procedures: 400,\n \"workflow-run\": 200,\n memory: 400,\n history: 1200,\n \"cross-chat\": 800,\n // The wiki is the long-term store, so it gets the room and the\n // priority. Skills, procedures, patterns and memory are all derived\n // from it or staged into it; when the budget is tight they are the\n // ones that should shrink, not the source of truth. Until this was\n // inverted the wiki had the lowest priority of the five and half the\n // budget of skills, which is why agents read everything except it.\n wiki: 2500,\n },\n}\n\n// Rough estimate: 1 token ≈ 4 chars for English\nconst CHARS_PER_TOKEN = 4\n\nexport interface ContextInput {\n // Channel layer\n channel: string // \"telegram\", \"whatsapp\", \"gitlab\", \"discord\"\n channelScope?: \"group\" | \"personal\" | \"project\"\n /** Canonical chat id used by routing — telegram user/group id, github\n * \"owner/repo:issue:N\", whatsapp jid, etc. Surfaced verbatim in the\n * prompt so the agent can pass it back to /recall and /send without\n * guessing. */\n chatId?: string\n\n // Bootstrap identity files (from workspace)\n bootstrapContext?: string // from buildBootstrapContext()\n\n // Verified deterministic references (from references registry + recipes)\n references?: string // from renderReferences()\n\n // Behavioral patterns (self-improving loop)\n patternContext?: string // from PatternStore.buildContext()\n\n // Matched procedures (user-perspective SOPs — known-good step sequences)\n procedureContext?: string // from renderProcedureContext()\n /** The workflow run this task is wrapped in (#858, workflows.required). */\n workflowRunContext?: string\n\n // Auto-injected skills (matched to current message)\n skillInjection?: string // from getAutoInjectSkills()\n\n // Scope layer\n groupName?: string\n projectPath?: string // \"org/my-project\"\n issueMR?: { type: string; iid: string; title: string }\n\n // Identity layer\n agentId: string\n agentName: string\n agentHandle?: string // \"@my_bot\"\n systemPrompt?: string\n\n // Participants\n sender: string\n senderId?: string // platform user ID (e.g. Telegram user ID)\n senderUsername?: string // platform username (e.g. @username)\n senderRole?: string // \"user\", \"agent:other-agent\"\n\n // Landscape (cached world model from LandscapeBuilder)\n landscape?: string\n\n // Channel meta (verified facts from the channel adapter — prevents hallucination)\n channelMeta?: {\n agents?: Array<{ id: string; name: string; handle?: string }>\n project?: string\n issue?: { type: string; iid: string; title: string }\n facts?: string[]\n }\n\n // Artifacts\n mediaPath?: string\n mediaType?: string\n replyToText?: string\n\n // Memory (persistent cross-session facts)\n memoryContext?: string // from MemoryStore.buildContext()\n\n // History\n groupHistory?: string // from GroupLog.buildContext()\n sessionHistory?: string // from SessionStore.buildHistoryContext()\n crossChatContext?: string // from SessionStore.getCrossSessionSummary()\n /** Long-memory recall pre-fetched by the registry when the user message\n * contains an explicit long-memory cue (\"yesterday\", \"last week\",\n * \"remember when…\"). Saves the agent a /recall round-trip in obvious\n * cases and prevents context-loss from being papered over with\n * fabricated facts via unrelated tools. */\n longMemoryRecall?: string\n\n // One line naming the MCP tools that fetch the landscape, history and\n // cross-chat context a lean session (#615) does not get pushed. From\n // session-profile.ts onDemandContextNote(); fresh sessions only.\n contextOnDemand?: string\n\n // Wiki\n wikiContext?: string // from WikiStore.buildContext()\n\n // Handover — one-shot note injected on the first message after an\n // operator-initiated handover takes effect. See channels/handover-store.\n handoverNote?: {\n fromAgent: string\n summary?: string\n at: string // ISO timestamp of the handover\n }\n\n // Continuity memo distilled from this chat's previous (rotated) Claude\n // session. Injected on FRESH sessions only (registry gates on\n // !resumeSessionId) so an ongoing task survives rotation instead of\n // the agent opening with amnesia. See SessionStore rotation memos.\n rotationMemo?: {\n memo: string\n reason: string // stale | tier-2 | max-turns\n capturedAt: string // ISO timestamp of the rotation\n }\n\n // Events matched by the agent's `digest` subscriptions since its last\n // turn. FRESH sessions only (registry gates on !resumeSessionId): a\n // resumed session reads newer events with agentx_events instead.\n eventDigest?: string\n\n // How to show files on this channel (the phone app's <agentx-artifact>\n // lines). FRESH sessions only (registry-gated): the resumed session\n // already has it from its first turn.\n attachHint?: string\n\n // Intent (graph classification — when absent, Intent layer falls back\n // to the legacy regex tag extractor).\n intent?: {\n path: string[] // root → leaf node ids\n pathLabel: string // \"Business › Acme › DevOps › Review MR\"\n pathId: string // hash — also carried as wiki `graph:<pathId>` tag\n axes?: Record<string, Record<string, string>>\n leaf?: { input?: string; output?: string }\n status: \"pending\" | \"approved\"\n }\n\n // Message\n message: string\n}\n\n/**\n * Build optimized context string from structured input.\n * Respects token budgets per layer and total cap.\n */\nexport function buildAgentContext(input: ContextInput, config: ContextConfig = DEFAULT_CONFIG): string {\n const layers = buildLayers(input, config)\n\n // Sort by priority (lower = first)\n layers.sort((a, b) => a.priority - b.priority)\n\n // Render within budget\n const parts: string[] = []\n let totalChars = 0\n const maxChars = config.totalBudget * CHARS_PER_TOKEN\n\n for (const layer of layers) {\n if (!layer.content) continue\n\n const layerMaxChars = layer.maxTokens * CHARS_PER_TOKEN\n const trimmed = layer.content.length > layerMaxChars\n ? layer.content.slice(0, layerMaxChars) + \"...\"\n : layer.content\n\n if (totalChars + trimmed.length > maxChars) {\n // Budget exhausted — add what fits or skip\n const remaining = maxChars - totalChars\n if (remaining > 100) {\n parts.push(trimmed.slice(0, remaining) + \"...\")\n }\n break\n }\n\n parts.push(trimmed)\n totalChars += trimmed.length\n }\n\n return parts.join(\"\\n\\n\")\n}\n\n/**\n * Build individual context layers from input.\n */\nfunction buildLayers(input: ContextInput, config: ContextConfig): ContextLayer[] {\n const budget = (name: string, fallback: number) =>\n config.layerBudgets?.[name] ?? fallback\n\n const layers: ContextLayer[] = []\n\n // 1. Channel layer\n layers.push(buildChannelLayer(input, budget(\"channel\", 200)))\n\n // 2. Scope layer\n layers.push(buildScopeLayer(input, budget(\"scope\", 200)))\n\n // 3. Landscape (cached world model — team, channels, rules)\n if (input.landscape) {\n layers.push({\n name: \"landscape\",\n priority: 3,\n maxTokens: budget(\"landscape\", 350),\n content: input.landscape,\n tags: [\"landscape\", \"world-model\"],\n })\n }\n\n // 4. Identity (only first line of systemPrompt — agent already has CLAUDE.md)\n if (input.systemPrompt) {\n layers.push({\n name: \"identity\",\n priority: 4,\n maxTokens: budget(\"identity\", 200),\n content: input.systemPrompt.split(\"\\n\")[0],\n tags: [\"identity\", input.agentId],\n })\n }\n\n // 4.5 Bootstrap identity files (SOUL.md, IDENTITY.md, USER.md, AGENTS.md)\n if (input.bootstrapContext) {\n layers.push({\n name: \"bootstrap\",\n priority: 4.5,\n maxTokens: budget(\"bootstrap\", 500),\n content: input.bootstrapContext,\n tags: [\"bootstrap\", \"identity\", \"personality\"],\n })\n }\n\n // 4.7 Verified references — deterministic facts (SSH hosts, project IDs,\n // paths, contacts) resolved from the references registry + recipes.\n // Sits before Intent so the agent reads facts before interpretation.\n if (input.references) {\n layers.push({\n name: \"references\",\n priority: 4.7,\n maxTokens: budget(\"references\", 500),\n content: input.references,\n tags: [\"references\", \"verified\", \"deterministic\"],\n })\n }\n\n // 5. Intent — prefer graph classification (hierarchical path) when the\n // caller ran the classifier. Fall back to the regex tag extractor\n // otherwise so legacy installs with graph.enabled=false behave as before.\n if (input.intent && input.intent.path.length > 0) {\n const lines: string[] = [\n `[Intent path: ${input.intent.pathLabel}${input.intent.status === \"pending\" ? \" (pending approval)\" : \"\"}]`,\n ]\n const leafId = input.intent.path[input.intent.path.length - 1]\n const leafAxes = input.intent.axes?.[leafId]\n if (leafAxes && Object.keys(leafAxes).length) {\n const kv = Object.entries(leafAxes)\n .map(([k, v]) => ` ${k}: ${v}`)\n .join(\"\\n\")\n lines.push(kv)\n }\n if (input.intent.leaf?.input) lines.push(` input: ${input.intent.leaf.input}`)\n layers.push({\n name: \"intent\",\n priority: 5,\n maxTokens: budget(\"intent\", 200),\n content: lines.join(\"\\n\"),\n tags: [\"intent\", ...input.intent.path],\n })\n } else {\n const intentTags = extractIntentTags(input.message)\n if (intentTags.length) {\n layers.push({\n name: \"intent\",\n priority: 5,\n maxTokens: budget(\"intent\", 200),\n content: `[Intent: ${intentTags.join(\", \")}]`,\n tags: intentTags,\n })\n }\n }\n\n // 6. Artifacts (media, reply-to, issue/MR context)\n const artifactLines: string[] = []\n if (input.replyToText) {\n artifactLines.push(`[Replying to]: ${input.replyToText.slice(0, 300)}`)\n }\n if (input.mediaPath) {\n artifactLines.push(`[Attached file: ${input.mediaPath}]`)\n artifactLines.push(`[File type: ${input.mediaType || \"unknown\"}]`)\n if (input.mediaType?.startsWith(\"image/\")) artifactLines.push(\"Please view this image and respond to it.\")\n else if (input.mediaType?.startsWith(\"audio/\")) artifactLines.push(\"Please transcribe this audio and respond.\")\n }\n if (input.issueMR) {\n artifactLines.push(`[${input.issueMR.type} #${input.issueMR.iid}: ${input.issueMR.title}]`)\n }\n if (artifactLines.length) {\n layers.push({\n name: \"artifacts\",\n priority: 6,\n maxTokens: budget(\"artifacts\", 500),\n content: artifactLines.join(\"\\n\"),\n tags: [\"artifacts\", ...(input.mediaType ? [\"media\"] : [])],\n })\n }\n\n // 6.2 Auto-injected skills (matched to current task)\n if (input.skillInjection) {\n layers.push({\n name: \"skills\",\n priority: 6.4,\n maxTokens: budget(\"skills\", 800),\n content: input.skillInjection,\n tags: [\"skills\", \"auto-inject\"],\n })\n }\n\n // 6.3 Behavioral patterns (self-improving loop)\n if (input.patternContext) {\n layers.push({\n name: \"patterns\",\n priority: 6.5,\n maxTokens: budget(\"patterns\", 300),\n content: input.patternContext,\n tags: [\"patterns\", \"behavioral\", \"self-improving\"],\n })\n }\n\n // 6.4 Matched procedures (mined SOPs — the known-good path for this task)\n if (input.procedureContext) {\n layers.push({\n name: \"procedures\",\n priority: 6.6,\n maxTokens: budget(\"procedures\", 400),\n content: input.procedureContext,\n tags: [\"procedures\", \"sop\"],\n })\n }\n\n // 6.45 The workflow run this task is wrapped in (#858). Its own layer:\n // in the procedures budget it could be cut, and the agent would not\n // know its run id.\n if (input.workflowRunContext) {\n layers.push({\n name: \"workflow-run\",\n priority: 6.55,\n maxTokens: budget(\"workflow-run\", 200),\n content: input.workflowRunContext,\n tags: [\"workflow\", \"plan\"],\n })\n }\n\n // 6.5 Agent memory (persistent cross-session facts from Haiku extraction)\n if (input.memoryContext) {\n layers.push({\n name: \"memory\",\n priority: 6.7,\n maxTokens: budget(\"memory\", 400),\n content: input.memoryContext,\n tags: [\"memory\", \"persistent\"],\n })\n }\n\n // 7. History (group > session — prefer group if available)\n const history = input.groupHistory || input.sessionHistory\n if (history) {\n layers.push({\n name: \"history\",\n priority: 7,\n maxTokens: budget(\"history\", 1200),\n content: history,\n tags: [\"history\", \"conversation\"],\n })\n }\n\n // 7b. Cross-chat context (bridges DM ↔ group amnesia)\n if (input.crossChatContext) {\n layers.push({\n name: \"cross-chat\",\n priority: 7,\n maxTokens: budget(\"cross-chat\", 800),\n content: input.crossChatContext,\n tags: [\"history\", \"cross-chat\"],\n })\n }\n\n // 7a. Context on demand — stands where history and cross-chat would be\n // in a lean session, so the agent knows what it can still fetch.\n if (input.contextOnDemand) {\n layers.push({\n name: \"on-demand\",\n priority: 7,\n maxTokens: budget(\"on-demand\", 150),\n content: input.contextOnDemand,\n tags: [\"history\", \"on-demand\", \"lean\"],\n })\n }\n\n // 7c. Long-memory recall (cue-triggered: \"yesterday\", \"remember\", etc.)\n if (input.longMemoryRecall) {\n layers.push({\n name: \"long-memory\",\n priority: 7,\n maxTokens: budget(\"long-memory\", 1500),\n content: input.longMemoryRecall,\n tags: [\"history\", \"long-memory\"],\n })\n }\n\n // 7c. Handover note — one-shot briefing when this agent is taking over\n // a conversation from another agent. Rendered just before wiki.\n if (input.handoverNote) {\n const lines = [\n `[Handover] You are taking over this conversation from ${input.handoverNote.fromAgent}.`,\n ]\n if (input.handoverNote.summary) {\n lines.push(`Operator summary: ${input.handoverNote.summary}`)\n }\n lines.push(`Handover time: ${input.handoverNote.at}`)\n layers.push({\n name: \"handover\",\n priority: 7.5,\n maxTokens: budget(\"handover\", 400),\n content: lines.join(\"\\n\"),\n tags: [\"handover\", input.handoverNote.fromAgent],\n })\n }\n\n // 7d. Rotation continuity memo — the previous Claude session for this\n // chat was rotated (${reason}); its distilled facts/open tasks are\n // handed to the fresh session so the conversation picks up where\n // it left off. Only present on fresh sessions (registry-gated).\n if (input.rotationMemo) {\n layers.push({\n name: \"rotation-memo\",\n priority: 7.6,\n maxTokens: budget(\"rotation-memo\", 800),\n content: [\n `[Continuity memo — your previous session for this chat ended (${input.rotationMemo.reason}, ${input.rotationMemo.capturedAt}). Carry on from this work state; do not claim you lack prior context. It is a summary, not checked facts: re-check account, billing, outage or deploy state before stating it, or say it is unverified and ask the owner:]`,\n input.rotationMemo.memo,\n ].join(\"\\n\"),\n tags: [\"continuity\", \"rotation-memo\"],\n })\n }\n\n // 7e. Event digest — subscribed events since the agent's last turn.\n // Fresh sessions only (registry-gated).\n if (input.eventDigest) {\n layers.push({\n name: \"events\",\n priority: 7.7,\n maxTokens: budget(\"events\", 500),\n content: input.eventDigest,\n tags: [\"events\", \"subscriptions\"],\n })\n }\n\n // 7f. How to attach files on this channel. Fresh sessions only.\n if (input.attachHint) {\n layers.push({\n name: \"attachments\",\n priority: 2.5,\n maxTokens: budget(\"attachments\", 250),\n content: input.attachHint,\n tags: [\"channel\", \"attachments\"],\n })\n }\n\n // 8. Wiki knowledge\n if (input.wikiContext) {\n layers.push({\n name: \"wiki\",\n priority: 6.1,\n maxTokens: budget(\"wiki\", 2500),\n content: input.wikiContext,\n tags: [\"wiki\", \"knowledge\"],\n })\n }\n\n return layers\n}\n\n/**\n * Build channel-specific context with rules.\n */\nfunction buildChannelLayer(input: ContextInput, maxTokens: number): ContextLayer {\n const lines: string[] = [`Channel: ${input.channel}`]\n if (input.chatId) lines.push(`Chat ID: ${input.chatId} (use this verbatim in /recall, /send)`)\n const rules: string[] = []\n const tags = [input.channel]\n\n switch (input.channel) {\n case \"telegram\":\n if (input.agentHandle) lines.push(`Your handle: ${input.agentHandle}`)\n lines.push(`From: ${input.sender}`)\n if (input.senderId) lines.push(`Telegram user ID: ${input.senderId}`)\n if (input.senderUsername) lines.push(`Username: @${input.senderUsername}`)\n rules.push(\"Format responses using Telegram-compatible markdown\")\n rules.push(\"Be brief — 2-4 sentences max for the main point. Humans scan, not read\")\n rules.push(\"Lead with the action or answer, skip preamble\")\n break\n\n case \"whatsapp\":\n lines.push(`From: ${input.sender}`)\n rules.push(\"Be brief — 2-3 sentences max. WhatsApp is mobile-first\")\n rules.push(\"No rich formatting — plain text only\")\n rules.push(\"Lead with the action or answer, skip preamble\")\n break\n\n case \"gitlab\":\n lines.push(`From: ${input.sender}`)\n rules.push(\"Reply as a short, actionable GitLab comment — 3-5 lines for the main message\")\n rules.push(\"Do NOT mention Telegram handles — they don't work on GitLab\")\n rules.push(\"Do NOT delegate to other agents\")\n rules.push(\"Reference issues with #IID and merge requests with !IID\")\n rules.push(\"Put verbose details (logs, full commands, step-by-step) inside collapsible sections: <details><summary>Title</summary>\\\\n\\\\ncontent\\\\n</details>\")\n rules.push(\"Never narrate what you are about to do — just do it and report the result\")\n tags.push(\"code-review\")\n break\n\n case \"discord\":\n lines.push(`From: ${input.sender}`)\n rules.push(\"Use Discord markdown for formatting\")\n rules.push(\"Be brief — 2-4 sentences for the main point\")\n break\n\n default:\n if (input.channel.startsWith(\"webhook:\")) {\n rules.push(\"This is an automated event — respond with actionable steps\")\n tags.push(\"webhook\", \"automated\")\n }\n }\n\n if (rules.length) {\n lines.push(\"\")\n lines.push(\"[Rules]\")\n lines.push(...rules.map(r => `- ${r}`))\n }\n\n return { name: \"channel\", priority: 1, maxTokens, content: lines.join(\"\\n\"), tags, rules }\n}\n\n/**\n * Build scope context (group/personal/project).\n */\nfunction buildScopeLayer(input: ContextInput, maxTokens: number): ContextLayer {\n const lines: string[] = []\n const tags: string[] = []\n\n if (input.channelScope === \"group\" && input.groupName) {\n lines.push(`Group: ${input.groupName}`)\n tags.push(\"group\", input.groupName)\n } else if (input.channelScope === \"project\" && input.projectPath) {\n lines.push(`Project: ${input.projectPath}`)\n tags.push(\"project\", input.projectPath)\n } else if (input.channelScope === \"personal\") {\n lines.push(\"Direct message\")\n tags.push(\"dm\")\n }\n\n // Inject verified channel metadata (prevents hallucination)\n if (input.channelMeta) {\n const meta = input.channelMeta\n if (meta.agents?.length) {\n lines.push(`[Verified bots in this chat: ${meta.agents.map(a => a.handle || a.name || a.id).join(\", \")}]`)\n lines.push(\"Only mention these agents as group members — do NOT assume others are present.\")\n }\n if (meta.project) {\n lines.push(`Project: ${meta.project}`)\n }\n if (meta.issue) {\n lines.push(`${meta.issue.type} #${meta.issue.iid}${meta.issue.title ? `: ${meta.issue.title}` : \"\"}`)\n }\n if (meta.facts?.length) {\n for (const fact of meta.facts) {\n lines.push(`• ${fact}`)\n }\n }\n }\n\n return { name: \"scope\", priority: 2, maxTokens, content: lines.join(\"\\n\"), tags }\n}\n\n/**\n * Extract intent tags from message (lightweight, no LLM).\n */\nfunction extractIntentTags(message: string): string[] {\n const tags: string[] = []\n const lower = message.toLowerCase()\n\n // Action intents\n if (/deploy|push|release|ship/.test(lower)) tags.push(\"deployment\")\n if (/review|check|look at|approve/.test(lower)) tags.push(\"review\")\n if (/fix|bug|broken|error|issue/.test(lower)) tags.push(\"bugfix\")\n if (/create|add|build|implement/.test(lower)) tags.push(\"feature\")\n if (/test|spec|coverage/.test(lower)) tags.push(\"testing\")\n if (/refactor|clean|improve/.test(lower)) tags.push(\"refactor\")\n if (/docs|document|readme/.test(lower)) tags.push(\"docs\")\n if (/security|vuln|auth|token/.test(lower)) tags.push(\"security\")\n if (/perf|slow|optim|fast/.test(lower)) tags.push(\"performance\")\n if (/status|update|progress|standup/.test(lower)) tags.push(\"status\")\n if (/help|how|what|explain/.test(lower)) tags.push(\"question\")\n\n // Domain intents\n if (/gitlab|merge|mr|issue|pipeline/.test(lower)) tags.push(\"gitlab\")\n if (/seo|analytics|content|marketing/.test(lower)) tags.push(\"marketing\")\n if (/infra|server|docker|k8s|devops/.test(lower)) tags.push(\"devops\")\n\n return tags\n}\n\n/**\n * Estimate token count from text.\n */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / CHARS_PER_TOKEN)\n}\n","// --- Where a run came from, and how to re-enter it after a restart ---\n//\n// Stored as JSON on the run's journal row (task_traces.resume_origin). The\n// code that started a run knows how to deliver its answer, so it records\n// its own kind and registers a resumer for it (coordinator.ts):\n//\n// router — a chat message (Telegram, WhatsApp, GitLab, GitHub…). Resumed\n// through the router, so the answer lands in the original chat.\n// mesh — a chat message another node received and forwarded here\n// (e.g. a GitLab webhook on the server, run on the Mac). Re-run\n// here; the answer goes back through that node's adapter.\n// direct — anything else (voice, agent-to-agent, webhooks, API). Re-run\n// with the same message and context; nothing delivers its answer,\n// so it is only resumed for channels the operator opts in.\n\nexport interface RouterOrigin {\n kind: \"router\"\n /** Channel adapter name. */\n adapter: string\n /** The incoming message, as the router's inflight log stores it. */\n message: Record<string, unknown>\n}\n\nexport interface DirectOrigin {\n kind: \"direct\"\n context?: Record<string, unknown>\n model?: string\n autonomy?: string\n}\n\nexport interface MeshOrigin {\n kind: \"mesh\"\n /** Node that received the message and forwarded it; absent from older\n * senders, in which case any healthy peer hosting the channel is used. */\n node?: string\n channel: string\n chatId: string\n /** Posts as this agent on the forwarding node, like a live reply. */\n agentId?: string\n /** The incoming message id, so the answer threads like a live reply. */\n replyTo?: string\n accountId?: string\n context?: Record<string, unknown>\n}\n\nexport type RunOrigin = RouterOrigin | MeshOrigin | DirectOrigin\n\n/** Marks a turn the resume step itself starts on a calling agent (a notice\n * or a re-run's answer). Such a turn is never \"delivered\" again. */\nexport const RESUME_DELIVERY_FLAG = \"resumeDelivery\"\n\n/** The agent that asked for a direct agent-to-agent run, when the run's\n * context names one (`sender: \"agent:<id>\"`), or null. The answer of such\n * a run can be delivered: as a new turn on that agent. */\nexport function callerAgentOf(origin: RunOrigin | null): string | null {\n if (!origin || origin.kind !== \"direct\" || !origin.context) return null\n if (origin.context[RESUME_DELIVERY_FLAG]) return null\n const channel = String(origin.context.channel ?? \"\").toLowerCase().split(\"@\")[0]\n if (channel !== \"a2a\") return null\n const sender = String(origin.context.sender ?? \"\")\n const m = sender.match(/^agent:([^\\s]+)$/)\n return m ? m[1] : null\n}\n\n/** Bigger than this is not stored: the run is reported instead of resumed. */\nexport const MAX_ORIGIN_BYTES = 32_000\n\nexport function serializeOrigin(origin: RunOrigin | undefined): string | null {\n if (!origin) return null\n try {\n const json = JSON.stringify(origin)\n return json.length <= MAX_ORIGIN_BYTES ? json : null\n } catch {\n return null\n }\n}\n\nexport function parseOrigin(json: string | null | undefined): RunOrigin | null {\n if (!json) return null\n try {\n const o = JSON.parse(json)\n if (o?.kind === \"router\" && typeof o.adapter === \"string\" && o.message && typeof o.message === \"object\") return o\n if (o?.kind === \"mesh\" && typeof o.channel === \"string\" && typeof o.chatId === \"string\") return o\n if (o?.kind === \"direct\") return o\n } catch { /* corrupt → not resumable */ }\n return null\n}\n","// --- Message Queue with Modes ---\n//\n// Handles messages arriving while an agent is busy processing.\n// Prevents duplicate runs, lost messages, and race conditions.\n//\n// Modes:\n// collect — batch messages arriving during a run, deliver all at once when done\n// followup — queue each message as a separate follow-up turn after current run\n// drop — silently discard messages while busy (for non-critical channels)\n\nexport type QueueMode = \"collect\" | \"followup\" | \"drop\"\n\nexport interface QueuedMessage {\n text: string\n sender: string\n timestamp: number\n channel: string\n chatId: string\n /** Full context from the original message, preserved for re-routing */\n originalContext?: Record<string, unknown>\n /** When the oldest message folded into this one was queued. Set on a\n * collect batch, whose `timestamp` is the flush time; a single queued\n * message's own `timestamp` already is its queue time. */\n queuedAt?: number\n}\n\n/** A queued turn that waited longer than this is told the world may have\n * moved on while it waited (#282). Shorter waits are the normal back-to-back\n * case and get nothing, so a busy chat does not pay for the note. */\nexport const STALE_QUEUE_NOTE_AFTER_MS = 60_000\n\nfunction hhmm(ms: number): string {\n return new Date(ms).toISOString().slice(11, 16)\n}\n\n/**\n * The one-line note a flushed queued turn carries when it waited long\n * enough for what it refers to — a PR head, an issue's state, a file — to\n * have changed. The message itself still describes the moment it was\n * queued, and agents otherwise report on that stale snapshot (\"I checked\n * the new head X\" after Y was pushed). Null when the wait was short.\n */\nexport function staleQueueNote(queuedAt: number, now: number = Date.now()): string | null {\n if (!Number.isFinite(queuedAt) || now - queuedAt <= STALE_QUEUE_NOTE_AFTER_MS) return null\n return `[queued at ${hhmm(queuedAt)} UTC, running at ${hhmm(now)} UTC — anything it refers to (PR head, issue state, files) may have changed; re-check the current state before acting or replying]`\n}\n\ninterface SessionQueue {\n mode: QueueMode\n /** Messages waiting to be processed */\n pending: QueuedMessage[]\n /** Whether an agent run is currently in progress for this session */\n running: boolean\n /** Callback to invoke when a queued message should be processed */\n onFlush?: (messages: QueuedMessage[]) => Promise<void>\n}\n\n/**\n * Per-agent, per-session message queue.\n * Keyed by `agentId:channel:chatId`.\n */\nexport class MessageQueue {\n private queues: Map<string, SessionQueue> = new Map()\n private defaultMode: QueueMode\n\n constructor(defaultMode: QueueMode = \"collect\") {\n this.defaultMode = defaultMode\n }\n\n private key(agentId: string, channel: string, chatId: string): string {\n return `${agentId}:${channel}:${chatId}`\n }\n\n private getOrCreate(agentId: string, channel: string, chatId: string): SessionQueue {\n const k = this.key(agentId, channel, chatId)\n if (!this.queues.has(k)) {\n this.queues.set(k, {\n mode: this.defaultMode,\n pending: [],\n running: false,\n })\n }\n return this.queues.get(k)!\n }\n\n /**\n * Set the queue mode for a specific session.\n */\n setMode(agentId: string, channel: string, chatId: string, mode: QueueMode): void {\n this.getOrCreate(agentId, channel, chatId).mode = mode\n }\n\n /**\n * Register a flush callback for when queued messages should be processed.\n */\n onFlush(\n agentId: string,\n channel: string,\n chatId: string,\n callback: (messages: QueuedMessage[]) => Promise<void>,\n ): void {\n this.getOrCreate(agentId, channel, chatId).onFlush = callback\n }\n\n /**\n * Check if an agent is currently busy for a given session.\n */\n isBusy(agentId: string, channel: string, chatId: string): boolean {\n return this.getOrCreate(agentId, channel, chatId).running\n }\n\n /**\n * Mark the start of an agent run. Returns false if already running.\n */\n markRunning(agentId: string, channel: string, chatId: string): boolean {\n const q = this.getOrCreate(agentId, channel, chatId)\n if (q.running) return false\n q.running = true\n return true\n }\n\n /**\n * Enqueue a message that arrived while the agent is busy.\n * Returns the queue mode applied (or null if not busy and no queueing needed).\n */\n enqueue(\n agentId: string,\n channel: string,\n chatId: string,\n message: QueuedMessage,\n ): QueueMode | null {\n const q = this.getOrCreate(agentId, channel, chatId)\n\n if (!q.running) return null // Not busy, process normally\n\n switch (q.mode) {\n case \"drop\":\n return \"drop\"\n\n case \"collect\":\n case \"followup\":\n q.pending.push(message)\n return q.mode\n }\n }\n\n /**\n * Mark the end of an agent run. Flushes pending messages based on mode.\n * Returns messages to process (if any).\n */\n async markDone(\n agentId: string,\n channel: string,\n chatId: string,\n ): Promise<QueuedMessage[]> {\n const q = this.getOrCreate(agentId, channel, chatId)\n q.running = false\n\n if (q.pending.length === 0) return []\n\n const messages = [...q.pending]\n q.pending = []\n\n switch (q.mode) {\n case \"collect\": {\n // Batch all pending messages into a single combined message. The\n // `[sender, HH:MM]:` prefix only helps disambiguate multi-message\n // batches (e.g. several group-chat users speaking while the agent\n // is busy); for a single queued message the prefix turned out to\n // be actively harmful — agents read \"[dashboard, 19:03]: ...\" as\n // a non-human source and got suspicious. Strip it in that case.\n const combinedText = messages.length === 1\n ? messages[0].text\n : messages.map((m) =>\n `[${m.sender}, ${new Date(m.timestamp).toISOString().slice(11, 16)}]: ${m.text}`\n ).join(\"\\n\")\n const combined: QueuedMessage = {\n text: combinedText,\n sender: messages[messages.length - 1].sender,\n timestamp: Date.now(),\n channel: messages[0].channel,\n chatId: messages[0].chatId,\n originalContext: messages[messages.length - 1].originalContext,\n queuedAt: Math.min(...messages.map((m) => m.queuedAt ?? m.timestamp)),\n }\n\n // Fire flush callback with combined message\n if (q.onFlush) {\n await q.onFlush([combined])\n }\n return [combined]\n }\n\n case \"followup\":\n // Process each message as a separate follow-up turn\n if (q.onFlush) {\n await q.onFlush(messages)\n }\n return messages\n\n case \"drop\":\n return []\n\n default:\n return messages\n }\n }\n\n /**\n * Get the number of pending messages for a session.\n */\n pendingCount(agentId: string, channel: string, chatId: string): number {\n return this.getOrCreate(agentId, channel, chatId).pending.length\n }\n\n /**\n * Clear all queued messages for a session (e.g., on session reset).\n */\n clear(agentId: string, channel: string, chatId: string): void {\n const k = this.key(agentId, channel, chatId)\n this.queues.delete(k)\n }\n\n /**\n * Get queue stats for monitoring.\n */\n stats(): Array<{ key: string; mode: QueueMode; pending: number; running: boolean }> {\n return Array.from(this.queues.entries()).map(([key, q]) => ({\n key,\n mode: q.mode,\n pending: q.pending.length,\n running: q.running,\n }))\n }\n}\n","import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { buildIndexCached, scoreAll } from \"../memory/bm25\"\n\n// --- Behavioral Pattern Store ---\n//\n// Agents learn HOW to work, not just WHAT they know.\n// After each response, Haiku extracts behavioral patterns:\n// \"When user asks about deployment, check CI status first\"\n// \"Code reviews should mention test coverage\"\n//\n// Before each prompt, relevant patterns are injected so the agent\n// improves over time — a self-correcting loop.\n\nexport interface BehavioralPattern {\n id: string\n agentId: string\n pattern: string // the learned behavior\n context: string // when this applies\n confidence: number // 0-1, increases with validation\n accessCount: number\n createdAt: string\n lastUsed?: string\n source: { channel: string; date: string }\n}\n\nconst MAX_PATTERNS = 50\nconst EXTRACTION_MODEL = \"claude-haiku-4-5-20251001\"\n\nconst PATTERN_EXTRACTION_PROMPT = `You are a behavioral pattern extractor. Given a conversation between a user and an AI agent, extract behavioral patterns — lessons about HOW to approach tasks, not facts about the world.\n\nExtract patterns like:\n- \"When asked about X, the approach that works is Y\"\n- \"User prefers Z style of communication\"\n- \"For deployment tasks, always check A before doing B\"\n- \"This codebase uses pattern X, so generate code following X\"\n\nSKIP:\n- Factual information (names, dates, URLs)\n- Routine task completions\n- Greetings or small talk\n\nOutput a JSON array:\n[{\"pattern\":\"the behavioral lesson\",\"context\":\"when this applies\",\"confidence\":0.5}]\n\nIf nothing worth extracting, output: []`\n\nexport class PatternStore {\n private patternsDir: string\n\n constructor(baseDir: string = process.cwd()) {\n this.patternsDir = resolve(baseDir, \".agentx/patterns\")\n if (!existsSync(this.patternsDir)) {\n mkdirSync(this.patternsDir, { recursive: true })\n }\n }\n\n private filePath(agentId: string): string {\n return resolve(this.patternsDir, `${agentId}.jsonl`)\n }\n\n getAll(agentId: string): BehavioralPattern[] {\n const file = this.filePath(agentId)\n if (!existsSync(file)) return []\n try {\n return readFileSync(file, \"utf-8\")\n .split(\"\\n\")\n .filter(l => l.trim())\n .map(l => { try { return JSON.parse(l) } catch { return null } })\n .filter((p): p is BehavioralPattern => p !== null)\n } catch {\n return []\n }\n }\n\n addPattern(agentId: string, pattern: Omit<BehavioralPattern, \"id\" | \"createdAt\" | \"accessCount\">): void {\n const entry: BehavioralPattern = {\n ...pattern,\n id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,\n accessCount: 0,\n createdAt: new Date().toISOString(),\n }\n appendFileSync(this.filePath(agentId), JSON.stringify(entry) + \"\\n\")\n }\n\n /**\n * Find patterns relevant to the current task.\n */\n findRelevant(message: string, agentId: string, limit: number = 5): BehavioralPattern[] {\n const patterns = this.getAll(agentId)\n if (patterns.length === 0) return []\n\n const docs = patterns.map(p => `${p.context} ${p.pattern}`)\n const cachePath = resolve(this.patternsDir, `${agentId}_bm25_cache.json`)\n const index = buildIndexCached(docs, cachePath)\n const scored = scoreAll(message, index)\n\n const results = scored\n .slice(0, limit)\n .map(s => patterns[s.docIndex])\n .filter(p => p.confidence >= 0.3) // only confident patterns\n\n // Track access\n if (results.length > 0) {\n this.recordAccess(agentId, results.map(p => p.id))\n }\n\n return results\n }\n\n /**\n * Build context string from relevant patterns for prompt injection.\n */\n buildContext(patterns: BehavioralPattern[]): string {\n if (patterns.length === 0) return \"\"\n\n const lines = [\"[Learned Patterns — behavioral lessons from past interactions]\"]\n for (const p of patterns) {\n lines.push(`- ${p.pattern} (context: ${p.context}, confidence: ${Math.round(p.confidence * 100)}%)`)\n }\n lines.push(\"[Apply these patterns when relevant to the current task]\")\n return lines.join(\"\\n\")\n }\n\n /**\n * Check if a similar pattern already exists.\n */\n hasSimilar(agentId: string, pattern: string): boolean {\n const existing = this.getAll(agentId)\n const words = pattern.toLowerCase().split(/\\s+/).filter(w => w.length > 3)\n return existing.some(p => {\n const pWords = p.pattern.toLowerCase()\n const overlap = words.filter(w => pWords.includes(w)).length\n return overlap >= words.length * 0.6\n })\n }\n\n private recordAccess(agentId: string, ids: string[]): void {\n const patterns = this.getAll(agentId)\n const idSet = new Set(ids)\n let changed = false\n for (const p of patterns) {\n if (idSet.has(p.id)) {\n p.accessCount++\n p.lastUsed = new Date().toISOString()\n // Boost confidence slightly on each use (max 1.0)\n p.confidence = Math.min(1.0, p.confidence + 0.05)\n changed = true\n }\n }\n if (changed) {\n writeFileSync(this.filePath(agentId), patterns.map(p => JSON.stringify(p)).join(\"\\n\") + \"\\n\")\n }\n }\n\n /**\n * Prune low-confidence or old patterns. Cap at MAX_PATTERNS.\n */\n prune(agentId: string): void {\n let patterns = this.getAll(agentId)\n if (patterns.length <= MAX_PATTERNS) return\n\n // Sort by confidence * accessCount (most valuable first)\n patterns.sort((a, b) => (b.confidence * b.accessCount) - (a.confidence * a.accessCount))\n patterns = patterns.slice(0, MAX_PATTERNS)\n writeFileSync(this.filePath(agentId), patterns.map(p => JSON.stringify(p)).join(\"\\n\") + \"\\n\")\n }\n}\n\n/**\n * Extract behavioral patterns from a conversation (fire-and-forget).\n */\nexport async function extractPatterns(\n agentId: string,\n userMessage: string,\n agentResponse: string,\n source: { channel: string; date: string },\n store: PatternStore,\n): Promise<void> {\n if (userMessage.length < 30 && agentResponse.length < 100) return\n\n const { createProvider } = await import(\"@/agent/providers\")\n const provider = createProvider(\"claude\")\n\n const result = await provider.generate(\n [\n { role: \"system\", content: PATTERN_EXTRACTION_PROMPT },\n { role: \"user\", content: `User: ${userMessage.slice(0, 500)}\\n\\nAgent: ${agentResponse.slice(0, 1000)}` },\n ],\n { model: EXTRACTION_MODEL, maxTokens: 512 },\n )\n\n const jsonMatch = result.content.trim().match(/\\[[\\s\\S]*\\]/)\n if (!jsonMatch) return\n\n const patterns = JSON.parse(jsonMatch[0]) as Array<{\n pattern: string\n context: string\n confidence: number\n }>\n\n if (!Array.isArray(patterns) || patterns.length === 0) return\n\n for (const p of patterns) {\n if (store.hasSimilar(agentId, p.pattern)) continue\n store.addPattern(agentId, {\n agentId,\n pattern: p.pattern,\n context: p.context,\n confidence: p.confidence || 0.5,\n source,\n })\n }\n\n // Probabilistic pruning\n if (Math.random() < 0.05) store.prune(agentId)\n}\n","import { EventEmitter } from \"events\"\nimport {\n capSummary, currentRoot, EventRing, newEventId,\n type EventEnvelope, type RecentQuery,\n} from \"./envelope\"\n\n// --- Internal event bus ---\n//\n// Typed pub/sub singleton. The router and registry emit lifecycle events\n// instead of calling log / SSE / analytics inline; subscribers attach\n// once at boot and observe without coupling to the hot path.\n//\n// Why a bus:\n// - Adding a new side-effect (audit log, dashboard SSE, plugin hook)\n// used to require editing router.ts or registry.ts. Now: subscribe.\n// - The Phase 1 traceRoute() function logs to a single sink; with the\n// bus, it's also published to anyone listening (dashboard, sqlite\n// writer, plugin). Same call site, more receivers.\n// - Plugin loader (Move 3) can subscribe; SQLite writer (Move 2) can\n// subscribe; nothing else changes.\n//\n// Why a singleton:\n// - The router + registry are themselves singletons inside the daemon\n// process. Passing the bus through every constructor would touch\n// every tier-A file for no benefit. The bus is a process-global,\n// same as `console` or the logger.\n//\n// Backward compat:\n// - Every existing log call stays put. The bus is ADDITIVE — emits\n// happen alongside existing logs, not in place of them. Future\n// commits can migrate inline `this.log(...)` → bus subscriber that\n// formats and logs.\n\n/**\n * Lifecycle events the daemon emits. Keep this list narrow and stable\n * — every event becomes a public contract once a subscriber depends on\n * it. Add events sparingly and never remove or rename without a major\n * version bump.\n */\nexport interface AgentXEvents {\n /** Inbound message arrived at the router and was matched to an agent.\n * Fired AFTER the routing pipeline produces a `match` decision. Drops\n * fire `message:dropped` instead. */\n \"message:matched\": {\n channel: string\n chatId: string\n msgId: string\n accountId?: string\n agentId: string\n decidingStage: string\n at: string // ISO\n }\n\n /** Inbound message dropped by the routing pipeline. */\n \"message:dropped\": {\n channel: string\n chatId: string\n msgId: string\n accountId?: string\n decidingStage: string\n reason: string\n at: string\n }\n\n /** A message waits behind a turn the agent is already running in the\n * same chat. It runs later as its own turn (request status, #383). */\n \"task:queued\": {\n agentId: string\n channel: string\n chatId: string\n at: string\n sender?: { name?: string; id?: string; username?: string }\n /** See task:started. */\n humanRoot?: boolean\n /** The person who sent it (people, #384), so the member page can count\n * the messages ahead of theirs (#443). */\n person?: string | null\n /** The first 200 characters of the message. */\n messagePreview?: string\n /** When it joined the line (ms). */\n queuedAt?: number\n }\n\n /** The line of one chat was handed over: every message queued up to\n * `flushedAt` now runs as a turn of its own, or as one combined turn\n * (#443). Fires before those turns start. */\n \"task:queue-flushed\": {\n agentId: string\n channel: string\n chatId: string\n /** When the queue handed its messages over (ms). */\n flushedAt: number\n /** How many messages it handed over. */\n count: number\n at: string\n }\n\n /** The turn a queued message was handed to has ended, or the hand-over\n * failed. A request still queued from before `flushedAt` never started\n * (request status, #383). */\n \"task:queue-ended\": {\n agentId: string\n channel: string\n chatId: string\n /** When the queue handed its messages over (ms). */\n flushedAt: number\n at: string\n }\n\n /** A task is about to be dispatched to the agent runtime.\n *\n * taskId is the ULID for the per-execution trace\n * (src/storage/traces.ts). Emitters generate it BEFORE firing this\n * event so it can also be threaded into the runtime call chain;\n * the SQLite subscriber uses it to open the trace row, and the\n * streaming parser uses it to record per-step tool_use/tool_result\n * rows. Optional for backward compatibility — emitters that haven't\n * been updated still produce a valid event and the subscriber\n * falls back to allocating its own id. */\n \"task:started\": {\n agentId: string\n channel: string\n chatId: string\n messagePreview: string // first ~200 chars\n /** Full untruncated message — used by SQLite subscriber to populate\n * task_traces.original_message so `agentx trace replay <taskId>` can\n * re-fire the exact original task. Optional for back-compat with\n * older emitters that only carried the preview. */\n fullMessage?: string\n /** What the person typed, when the message wraps it in a prompt of\n * the caller's own (the dashboard's Ask an agent, #401). */\n requestText?: string\n at: string\n taskId?: string\n /** Resume after restart (#103): how to re-enter the run (JSON), its\n * attempt number, and the cut-off run it continues. */\n resumeOrigin?: string | null\n resumeAttempt?: number\n resumedFrom?: string\n /** Who sent the message, when the channel knows (requests, #356). */\n sender?: { name?: string; id?: string; username?: string }\n /** The known person who started this chain (people, #384). Absent for\n * an unknown sender and for turns software starts. */\n person?: { id: string; role?: \"owner\" | \"member\" | \"client\" | \"guest\" }\n /** True for the turn that talks to the person: not a delegated hop,\n * not a callback (a2a/initiator isHumanFacingTurn). */\n humanRoot?: boolean\n /** True for the turn the daemon starts to hand a request back to its\n * agent (requests/tracker pickupContext). */\n pickup?: boolean\n /** True when the daemon marked the turn as the owner's on one of this\n * node's own surfaces (requests/operator): it started the turn itself,\n * or the /task caller showed the operator key (#393). */\n operator?: boolean\n /** When the task is due (ms), when the caller gave one in the run's\n * context as `dueAt` (watchers, #1030). */\n dueAt?: number\n }\n\n /** A single step inside an in-flight task — typically a tool call or\n * tool result inside the Claude streaming response. Producer is the\n * per-event callback inside registry.execute's streaming path; the\n * SQLite subscriber persists each step into task_trace_steps under\n * the task's ULID. inputSummary / outputSummary are caller-byte-\n * capped (typically 8KB) so a runaway tool result can't blow up the\n * bus payload or DB row size.\n *\n * Conventional `name` values: \"tool_use\" | \"tool_result\" |\n * \"llm_message\" | \"session_rotation\" | \"error\" | \"preflight\". Free-\n * form strings are accepted to let future capture sites extend\n * without a schema bump. */\n \"task:step\": {\n taskId: string\n agentId: string\n name: string\n action?: string\n status?: \"ok\" | \"error\" | \"in-flight\"\n inputSummary?: string\n outputSummary?: string\n error?: string\n ms?: number\n at: string\n }\n\n /** Task finished — success or failure. Includes usage so subscribers\n * can write to billing, sqlite, or analytics without re-querying.\n *\n * Tier-2 fields hold the portion of THIS task's tokens that fell into\n * the \"above-threshold\" pricing tier (decided at record time by\n * TokenTracker; see usage_daily v5 in src/storage/sqlite.ts). They are\n * optional and additive — emitters that don't compute the split simply\n * omit them and subscribers treat the missing values as 0. */\n \"task:completed\": {\n /** Trace ULID — when present, lets subscribers finalize the exact\n * trace row that the matching task:started opened, eliminating the\n * ambiguity when two tasks for the same (agent, channel, chatId)\n * overlap. Optional for backward compatibility; subscribers fall\n * back to a pending-map lookup when absent. */\n taskId?: string\n agentId: string\n channel: string\n chatId: string\n durationMs: number\n error?: string\n /** The kind of error, when the runtime named one (\"cancelled\", …). */\n errorKind?: string\n /** A daemon shutdown stopped the run. Its trace stays in flight so the\n * next boot can resume it. */\n interrupted?: boolean\n /** Ended by a stop signal (agents/signals, #857): status `stopped`. */\n stopped?: boolean\n inputTokens?: number\n outputTokens?: number\n cacheReadTokens?: number\n cacheCreateTokens?: number\n tier2InputTokens?: number\n tier2OutputTokens?: number\n tier2CacheReadTokens?: number\n tier2CacheCreateTokens?: number\n /** Whether this turn continued a provider session, and which one. The\n * resume decision happens after task:started (staleness, rotation,\n * planner), so it can only be reported here. */\n resumed?: boolean\n resumeSessionId?: string\n /** Request-gate experiment arm when the gate was active. */\n jevArm?: \"treatment\" | \"holdout\"\n /** Runtime-reported turn count, when the runtime reports one. */\n numTurns?: number\n /** Lessons injected into this turn's prompt (memory, procedures, wiki). */\n injectedContext?: { memory: string[]; procedures: string[]; wiki: boolean }\n /** Agent's final reply text — recorded into task_traces.final_response\n * so `replay --diff` can show original vs current output side-by-side.\n * Optional for back-compat. */\n finalResponse?: string\n /** Model the upstream API actually billed (claude-opus-4-7, gpt-5.5, …)\n * so per-agent / per-tier traffic is priced at its real rate instead\n * of the daemon-wide default. Producers prefer `response.billedModel`\n * (parsed from the runtime's reply) and fall back to the agent's\n * configured `model`. Subscribers fall back to the constant default\n * passed to attachSqliteSubscribers when this is absent. */\n billedModel?: string\n at: string\n }\n\n /** Claude session rotation fired (stale / max-turns / tier-2 / continuity / cost). */\n \"session:rotated\": {\n /** Trace ULID of the in-flight task at rotation time, when known.\n * When set, the rotation is recorded as a step on that trace. */\n taskId?: string\n agentId: string\n channel: string\n chatId: string\n reason: \"stale\" | \"max-turns\" | \"tier-2\" | \"continuity\" | \"cost\"\n lastTurnInputTokens?: number\n at: string\n }\n}\n\ntype EventName = keyof AgentXEvents\n\n/** How many peer event keys ingest() remembers for dedup. Far above the\n * catch-up read size (see peer-feed.ts), so a repeat read of a peer's\n * buffer after a reconnect is still recognised. */\nconst SEEN_MAX = 20_000\ntype Listener<E extends EventName> = (payload: AgentXEvents[E]) => void\n\n/** Input to publish(). id, at, node and rootId are filled in when absent;\n * rootId comes from the current root context (see envelope.ts). `data`\n * is delivered to subscribers alongside the envelope but is never kept in\n * the ring buffer; the daemon's /events adapter uses it to keep its wire\n * shape. */\nexport interface PublishInput {\n kind: string\n type: string\n summary: string\n agentId?: string\n ref?: string\n id?: string\n at?: string\n rootId?: string\n parentId?: string\n data?: Record<string, unknown>\n}\n\nexport type EnvelopeListener = (e: EventEnvelope, data?: Record<string, unknown>) => void\n\n/** The one in-process bus. Typed lifecycle events keep their names and\n * payloads for `on` subscribers; every event, typed or not, is also\n * published as an EventEnvelope to `subscribe` listeners and the ring. */\nexport class TypedEventBus {\n private inner = new EventEmitter()\n private envelopeListeners = new Set<EnvelopeListener>()\n private ring = new EventRing()\n private node = \"local\"\n /** Envelopes taken in from mesh peers, keyed `${node}:${id}`, so the\n * live stream and a catch-up read never deliver one event twice. Map\n * insertion order makes the oldest key the first to drop. */\n private seen = new Map<string, true>()\n\n constructor() {\n // Default listener cap is 10 — easy to hit when several subsystems\n // subscribe (dashboard SSE, sqlite writer, audit log, drift detector).\n // 50 is generous without masking real leaks.\n this.inner.setMaxListeners(50)\n }\n\n /** Set the publishing node name and ring size (daemon boot / reload). */\n configure(opts: { node?: string; ringSize?: number }): this {\n if (opts.node) this.node = opts.node\n if (opts.ringSize) this.ring.resize(opts.ringSize)\n return this\n }\n\n on<E extends EventName>(event: E, listener: Listener<E>): this {\n this.inner.on(event, listener as (...args: unknown[]) => void)\n return this\n }\n\n off<E extends EventName>(event: E, listener: Listener<E>): this {\n this.inner.off(event, listener as (...args: unknown[]) => void)\n return this\n }\n\n emit<E extends EventName>(event: E, payload: AgentXEvents[E]): boolean {\n const delivered = this.inner.emit(event, payload)\n try { this.publish(lifecycleEnvelope(event, payload)) } catch { /* observability never breaks the emitter */ }\n return delivered\n }\n\n /** The single publish API. Returns the envelope as delivered. */\n publish(input: PublishInput): EventEnvelope {\n const root = currentRoot()\n const id = input.id ?? newEventId()\n const envelope: EventEnvelope = {\n id,\n rootId: input.rootId ?? root?.rootId ?? id,\n node: this.node,\n kind: input.kind,\n type: input.type,\n at: input.at ?? new Date().toISOString(),\n summary: capSummary(input.summary),\n }\n const parentId = input.parentId ?? root?.parentId\n if (parentId) envelope.parentId = parentId\n if (input.agentId) envelope.agentId = input.agentId\n if (input.ref) envelope.ref = input.ref\n this.ring.push(envelope)\n for (const fn of this.envelopeListeners) {\n try { fn(envelope, input.data) } catch { /* a bad listener must not break peers */ }\n }\n return envelope\n }\n\n /** Name this bus publishes under. An envelope whose `node` differs came\n * from a mesh peer (see ingest). */\n get nodeName(): string {\n return this.node\n }\n\n /** True for envelopes this node published itself. */\n isLocal(e: EventEnvelope): boolean {\n return e.node === this.node\n }\n\n /** Take in an envelope published on a mesh peer. It keeps its id, node\n * and root, lands in the ring and reaches `subscribe` listeners, but\n * never the typed `on` listeners: the peer records its own traces.\n * Returns false for an event already taken in, or one that claims to\n * come from this node (an echo). */\n ingest(input: EventEnvelope): boolean {\n if (!input.node || input.node === this.node) return false\n const key = `${input.node}:${input.id}`\n if (this.seen.has(key)) return false\n this.seen.set(key, true)\n if (this.seen.size > SEEN_MAX) this.seen.delete(this.seen.keys().next().value as string)\n const envelope: EventEnvelope = {\n id: input.id,\n rootId: input.rootId || input.id,\n node: input.node,\n kind: input.kind,\n type: input.type,\n at: input.at,\n summary: capSummary(input.summary ?? \"\"),\n }\n if (input.parentId) envelope.parentId = input.parentId\n if (input.agentId) envelope.agentId = input.agentId\n if (input.ref) envelope.ref = input.ref\n this.ring.push(envelope)\n for (const fn of this.envelopeListeners) {\n try { fn(envelope) } catch { /* a bad listener must not break peers */ }\n }\n return true\n }\n\n /** Whether an event id is still in this node's ring buffer. */\n hasRecent(id: string): boolean {\n return this.ring.has(id)\n }\n\n /** Receive every envelope. Returns an unsubscribe function. */\n subscribe(fn: EnvelopeListener): () => void {\n this.envelopeListeners.add(fn)\n return () => { this.envelopeListeners.delete(fn) }\n }\n\n recent(q?: RecentQuery): EventEnvelope[] {\n return this.ring.recent(q)\n }\n\n listenerCount<E extends EventName>(event: E): number {\n return this.inner.listenerCount(event)\n }\n\n /** Test/dev helper — drop every subscriber and the ring. */\n removeAllListeners(): this {\n this.inner.removeAllListeners()\n this.envelopeListeners.clear()\n this.ring.clear()\n this.seen.clear()\n return this\n }\n}\n\n/** Map a typed lifecycle event to envelope fields. Summaries name what\n * happened, never the prompt or the answer; `ref` is the trace id. */\nfunction lifecycleEnvelope<E extends EventName>(event: E, payload: AgentXEvents[E]): PublishInput {\n const p = payload as AgentXEvents[EventName] & Record<string, any>\n const base = { type: event, at: p.at, agentId: p.agentId, ref: p.taskId }\n switch (event) {\n case \"message:matched\":\n return { ...base, kind: \"message\", ref: p.msgId, summary: `${p.channel} ${p.chatId} → ${p.agentId} (${p.decidingStage})` }\n case \"message:dropped\":\n return { ...base, kind: \"message\", ref: p.msgId, summary: `${p.channel} ${p.chatId} dropped at ${p.decidingStage}: ${p.reason}` }\n case \"task:queued\":\n return { ...base, kind: \"agent\", summary: `queued on ${p.channel} ${p.chatId}` }\n case \"task:queue-flushed\":\n return { ...base, kind: \"agent\", summary: `${p.count} queued message(s) handed over on ${p.channel} ${p.chatId}` }\n case \"task:queue-ended\":\n return { ...base, kind: \"agent\", summary: `queued turn ended on ${p.channel} ${p.chatId}` }\n case \"task:started\":\n return { ...base, kind: \"agent\", summary: `started on ${p.channel} ${p.chatId}` }\n case \"task:step\":\n return { ...base, kind: \"agent\", summary: [p.name, p.action, p.status].filter(Boolean).join(\" \") }\n case \"task:completed\":\n return {\n ...base, kind: \"agent\",\n summary: p.stopped ? `stopped after ${p.durationMs}ms: ${p.error ?? \"stop signal\"}`\n : p.error ? `failed after ${p.durationMs}ms: ${p.error}` : `completed in ${p.durationMs}ms`,\n }\n case \"session:rotated\":\n return { ...base, kind: \"agent\", summary: `session rotated (${p.reason})` }\n default:\n return { ...base, kind: \"agent\", summary: String(event) }\n }\n}\n\nlet _instance: TypedEventBus | undefined\nexport function getEventBus(): TypedEventBus {\n if (!_instance) _instance = new TypedEventBus()\n return _instance\n}\n","import { newEventId } from \"@/intent/ulid\"\nimport {\n DEFAULT_ATTACH_OPTIONS,\n type AttachOptions,\n type AttachSession,\n type DeliveryMode,\n type InboxItem,\n} from \"./types\"\nimport type { WatchCursor, WatchState } from \"./watch\"\n\n// --- The attach registry ---\n//\n// Holds every live Claude Code session that has registered itself, which\n// agent identities each answers for, and the messages offered to them.\n//\n// The one piece of real logic here is the item state machine. Everything\n// else is bookkeeping. Two actors race on every item:\n//\n// - the dispatcher, which offered the message and is counting down a\n// claim deadline before it gives up and spawns a provider instead\n// - the session's Stop hook, which may drain the item at any moment\n//\n// Both transitions are guarded by a state check on a single-threaded event\n// loop, so \"compare state, then mutate\" is atomic without a lock. The\n// invariant that matters: an item leaves `pending` exactly once. Whichever\n// of claim() / expire() wins, the loser observes a non-pending state and\n// returns false. That is what makes it safe to put attach mode in front of\n// production channel traffic — a message is answered by the session or by a\n// spawned agent, never by both, never by neither.\n//\n// Timeouts are swept rather than scheduled per item. A single interval in\n// the daemon calls sweep(); tests call it directly with an explicit clock.\n// No dangling setTimeout handles, and deadline behavior is testable without\n// fake timers.\n\n/** Resolution handed back to whoever offered the message. */\nexport type OfferOutcome =\n /** A session answered it. Use this text as the agent's reply. */\n | { kind: \"answered\"; text: string; sessionId: string }\n /** Nobody claimed it in time (or the session died). Spawn a provider. */\n | { kind: \"expired\"; reason: string }\n\ninterface Waiter {\n resolve: (o: OfferOutcome) => void\n}\n\n/** What a session answered for, remembered across daemon restarts and idle\n * spells (#193). Only `detach` or the session actually ending forgets it. */\nexport interface SavedBinding {\n agentIds: string[]\n mode: DeliveryMode\n /** A watching session's filter and cursor (#167); agentIds is then empty. */\n watch?: WatchState\n cwd?: string\n savedAt: number\n}\n\n/** Where bindings persist. The daemon uses a JSON file; tests pass memory. */\nexport interface BindingStore {\n load(): Record<string, SavedBinding>\n save(all: Record<string, SavedBinding>): void\n}\n\n/** A binding nobody has come back for in a week is not coming back. */\nconst SAVED_BINDING_TTL_MS = 7 * 24 * 60 * 60_000\n\nexport interface OfferInput {\n agentId: string\n text: string\n channel: string\n chatId: string\n sender: string\n}\n\nexport class AttachRegistry {\n private sessions = new Map<string, AttachSession>()\n private items = new Map<string, InboxItem>()\n private waiters = new Map<string, Waiter>()\n private saved = new Map<string, SavedBinding>()\n private opts: AttachOptions\n\n constructor(opts: Partial<AttachOptions> = {}, private store?: BindingStore) {\n this.opts = { ...DEFAULT_ATTACH_OPTIONS, ...opts }\n let loaded: Record<string, SavedBinding> = {}\n try { loaded = store?.load() ?? {} } catch { loaded = {} }\n const cutoff = Date.now() - SAVED_BINDING_TTL_MS\n for (const [sessionId, b] of Object.entries(loaded)) {\n if (b && Array.isArray(b.agentIds) && (b.agentIds.length > 0 || b.watch) && b.savedAt >= cutoff) this.saved.set(sessionId, b)\n }\n }\n\n options(): AttachOptions {\n return this.opts\n }\n\n // --- sessions ---\n\n /** Register (or refresh) a session. Idempotent: every hook event calls\n * this, so a daemon restart self-heals on the next Stop/prompt without\n * the human doing anything. Bindings from a previous registration of the\n * same session id survive — SessionStart fires again on resume/compact,\n * and losing the binding there would silently detach a bound session. */\n register(\n sessionId: string,\n info: { cwd?: string; model?: string; now?: number } = {},\n ): AttachSession {\n const now = info.now ?? Date.now()\n const existing = this.sessions.get(sessionId)\n if (existing) {\n existing.lastSeenAt = now\n if (info.cwd) existing.cwd = info.cwd\n if (info.model) existing.model = info.model\n return existing\n }\n // A session coming back after a daemon restart or an idle drop gets its\n // saved identities back, minus any another live session has taken since.\n const saved = this.saved.get(sessionId)\n const session: AttachSession = {\n sessionId,\n cwd: info.cwd ?? saved?.cwd ?? \"\",\n agentIds: saved ? saved.agentIds.filter((a) => !this.sessionFor(a)) : [],\n mode: saved?.mode ?? \"notify\",\n lastSeenAt: now,\n registeredAt: now,\n model: info.model,\n drainedThisTurn: 0,\n }\n if (saved?.watch) session.watch = { ...saved.watch }\n this.sessions.set(sessionId, session)\n if (saved) this.remember(session, now)\n return session\n }\n\n /** Mark activity without creating a session. Returns undefined when the\n * session is unknown, which is the common case: most Claude Code sessions\n * on this machine never attach, and their hook events must stay cheap. */\n touch(sessionId: string, now: number = Date.now()): AttachSession | undefined {\n const s = this.sessions.get(sessionId)\n if (s) {\n s.lastSeenAt = now\n return s\n }\n // Prompt and Stop hooks only touch. Without this, a bound session that was\n // already open when the daemon restarted would never be seen again.\n return this.saved.has(sessionId) ? this.register(sessionId, { now }) : undefined\n }\n\n get(sessionId: string): AttachSession | undefined {\n return this.sessions.get(sessionId)\n }\n\n list(): AttachSession[] {\n return [...this.sessions.values()].sort((a, b) => b.lastSeenAt - a.lastSeenAt)\n }\n\n /** Bind an agent identity to a session. One agent maps to at most one\n * session — a later bind steals it, which is what an operator moving\n * between terminals expects. */\n bind(sessionId: string, agentId: string, mode?: DeliveryMode): AttachSession | undefined {\n const session = this.sessions.get(sessionId)\n if (!session) return undefined\n for (const other of this.sessions.values()) {\n if (other.sessionId === sessionId) continue\n other.agentIds = other.agentIds.filter((a) => a !== agentId)\n }\n for (const [otherId, b] of this.saved) {\n if (otherId !== sessionId) b.agentIds = b.agentIds.filter((a) => a !== agentId)\n }\n if (!session.agentIds.includes(agentId)) session.agentIds.push(agentId)\n if (mode) session.mode = mode\n // Answering and watching are exclusive: a session that takes an identity\n // stops watching (#167).\n session.watch = undefined\n this.remember(session)\n return session\n }\n\n unbind(sessionId: string, agentId?: string): AttachSession | undefined {\n const session = this.sessions.get(sessionId)\n if (!session) return undefined\n session.agentIds = agentId ? session.agentIds.filter((a) => a !== agentId) : []\n // A full detach also ends watching; releasing one identity does not.\n if (!agentId) session.watch = undefined\n this.remember(session)\n return session\n }\n\n /** Make a session a watcher (#167): it drops every identity it held, so\n * nothing is ever offered to it, and from now on it only gets an event\n * digest on each prompt. Work already queued for it falls back to\n * spawning straight away instead of waiting out the claim deadline. */\n watch(\n sessionId: string,\n state: Omit<WatchState, \"startedAt\">,\n now: number = Date.now(),\n ): AttachSession | undefined {\n const session = this.sessions.get(sessionId)\n if (!session) return undefined\n session.agentIds = []\n session.awaitingAnswer = undefined\n this.expireItemsOf(sessionId, now, \"session switched to watch mode\")\n session.watch = { subscriptions: state.subscriptions, cursor: state.cursor, startedAt: now }\n this.remember(session, now)\n return session\n }\n\n /** Move a watcher's cursor past the events it was just shown. */\n advanceWatch(sessionId: string, cursor: WatchCursor): void {\n const session = this.sessions.get(sessionId)\n if (!session?.watch) return\n if (session.watch.cursor.id === cursor.id && session.watch.cursor.at === cursor.at) return\n session.watch.cursor = cursor\n this.remember(session)\n }\n\n setMode(sessionId: string, mode: DeliveryMode): AttachSession | undefined {\n const session = this.sessions.get(sessionId)\n if (!session) return undefined\n session.mode = mode\n this.remember(session)\n return session\n }\n\n /** Which session, if any, currently answers for this agent. */\n sessionFor(agentId: string): AttachSession | undefined {\n for (const s of this.sessions.values()) {\n if (s.agentIds.includes(agentId)) return s\n }\n return undefined\n }\n\n /** Drop a session and release everything it was holding. Queued work is\n * expired, not dropped, so the dispatcher falls back to spawning. */\n deregister(sessionId: string, now: number = Date.now(), opts: { forget?: boolean } = {}): void {\n this.sessions.delete(sessionId)\n // Idle drops keep the saved binding so the session gets it back when it\n // returns; only a real end (SessionEnd) forgets it.\n if (opts.forget && this.saved.delete(sessionId)) this.persist()\n this.expireItemsOf(sessionId, now, \"session ended\")\n }\n\n private expireItemsOf(sessionId: string, now: number, reason: string): void {\n for (const item of this.items.values()) {\n if (item.sessionId !== sessionId) continue\n if (item.state === \"pending\" || item.state === \"claimed\") {\n this.settle(item, \"expired\", now, reason)\n }\n }\n }\n\n // --- items ---\n\n /**\n * Offer a message to whichever session owns this agent. Resolves when the\n * session answers, or when the claim deadline passes and the caller should\n * fall back to spawning a provider.\n *\n * Returns null synchronously when no session is bound — the hot path for\n * every normal dispatch, so it must stay allocation-free.\n */\n offer(input: OfferInput, now: number = Date.now()): Promise<OfferOutcome> | null {\n const session = this.sessionFor(input.agentId)\n if (!session) return null\n if (now - session.lastSeenAt > this.opts.staleSessionMs) {\n // The session stopped reporting. Treat it as gone rather than routing\n // real traffic into a terminal nobody is watching.\n this.deregister(session.sessionId, now)\n return null\n }\n\n const item: InboxItem = {\n id: newEventId(now),\n agentId: input.agentId,\n sessionId: session.sessionId,\n state: \"pending\",\n text: clip(input.text, this.opts.maxItemChars),\n channel: input.channel,\n chatId: input.chatId,\n sender: input.sender,\n createdAt: now,\n }\n this.items.set(item.id, item)\n\n return new Promise<OfferOutcome>((resolve) => {\n this.waiters.set(item.id, { resolve })\n })\n }\n\n /** Items still waiting on a session, oldest first. */\n pending(sessionId: string): InboxItem[] {\n return [...this.items.values()]\n .filter((i) => i.sessionId === sessionId && i.state === \"pending\")\n .sort((a, b) => a.createdAt - b.createdAt)\n }\n\n pendingCount(sessionId: string): number {\n let n = 0\n for (const i of this.items.values()) {\n if (i.sessionId === sessionId && i.state === \"pending\") n++\n }\n return n\n }\n\n /**\n * Take the next pending item for a session. The atomic half of the race:\n * an item observed as `pending` is flipped to `claimed` before any await\n * point, so a concurrent expire() finds it already gone.\n *\n * Only one item may be outstanding per session — the reply-capture path\n * reads Claude Code's `last_assistant_message`, and that is only\n * unambiguous if exactly one question is in flight.\n */\n claim(sessionId: string, now: number = Date.now()): InboxItem | undefined {\n const session = this.sessions.get(sessionId)\n if (!session) return undefined\n if (session.awaitingAnswer) return undefined\n\n const next = this.pending(sessionId)[0]\n if (!next || next.state !== \"pending\") return undefined\n\n next.state = \"claimed\"\n next.claimedAt = now\n session.awaitingAnswer = next.id\n session.lastSeenAt = now\n session.drainedThisTurn++\n return next\n }\n\n /** Record a session's reply and resolve the dispatcher's promise. */\n answer(sessionId: string, text: string, now: number = Date.now()): InboxItem | undefined {\n const session = this.sessions.get(sessionId)\n if (!session?.awaitingAnswer) return undefined\n const item = this.items.get(session.awaitingAnswer)\n session.awaitingAnswer = undefined\n if (!item || item.state !== \"claimed\") return undefined\n\n item.answer = text\n this.settle(item, \"answered\", now)\n return item\n }\n\n /** Give an item back without answering it — e.g. the human said \"not now\".\n * Returns it to `pending` so the claim deadline can still be met by a\n * later drain, or expire normally. */\n release(sessionId: string, now: number = Date.now()): InboxItem | undefined {\n const session = this.sessions.get(sessionId)\n if (!session?.awaitingAnswer) return undefined\n const item = this.items.get(session.awaitingAnswer)\n session.awaitingAnswer = undefined\n if (!item || item.state !== \"claimed\") return undefined\n item.state = \"pending\"\n item.claimedAt = undefined\n session.lastSeenAt = now\n return item\n }\n\n /** The other atomic half. Returns false when the item already left\n * `pending`/`claimed`, meaning a session got there first. */\n expire(itemId: string, reason: string, now: number = Date.now()): boolean {\n const item = this.items.get(itemId)\n if (!item) return false\n if (item.state !== \"pending\" && item.state !== \"claimed\") return false\n const session = this.sessions.get(item.sessionId)\n if (session?.awaitingAnswer === item.id) session.awaitingAnswer = undefined\n this.settle(item, \"expired\", now, reason)\n return true\n }\n\n /**\n * Advance deadlines. Called on an interval by the daemon and directly by\n * tests. An item gets `claimTimeoutMs` to be claimed, and — because a\n * claimed item is a session's active question — the same budget again to\n * be answered, after which we assume the human walked away.\n */\n sweep(now: number = Date.now()): number {\n let expired = 0\n for (const item of this.items.values()) {\n if (item.state === \"pending\" && now - item.createdAt >= this.opts.claimTimeoutMs) {\n if (this.expire(item.id, \"not claimed in time\", now)) expired++\n } else if (\n item.state === \"claimed\" &&\n now - (item.claimedAt ?? item.createdAt) >= this.opts.claimTimeoutMs\n ) {\n if (this.expire(item.id, \"claimed but never answered\", now)) expired++\n }\n }\n for (const session of this.sessions.values()) {\n if (now - session.lastSeenAt > this.opts.staleSessionMs) {\n this.deregister(session.sessionId, now)\n }\n }\n return expired\n }\n\n /** Reset the per-turn drain counter — the session actually stopped. */\n endTurn(sessionId: string): void {\n const s = this.sessions.get(sessionId)\n if (s) s.drainedThisTurn = 0\n }\n\n /** Identities a session will get back when it next reports (inspection). */\n savedBindings(): Record<string, SavedBinding> {\n return Object.fromEntries(this.saved)\n }\n\n private remember(session: AttachSession, now: number = Date.now()): void {\n if (session.agentIds.length === 0 && !session.watch) this.saved.delete(session.sessionId)\n else {\n const b: SavedBinding = { agentIds: [...session.agentIds], mode: session.mode, cwd: session.cwd || undefined, savedAt: now }\n if (session.watch) b.watch = { ...session.watch }\n this.saved.set(session.sessionId, b)\n }\n for (const [id, b] of this.saved) if (b.agentIds.length === 0 && !b.watch) this.saved.delete(id)\n this.persist()\n }\n\n private persist(): void {\n try { this.store?.save(Object.fromEntries(this.saved)) } catch { /* bindings stay in memory */ }\n }\n\n /** Test/inspection helper. */\n item(id: string): InboxItem | undefined {\n return this.items.get(id)\n }\n\n /** Drop settled items older than an hour so a long-lived daemon doesn't\n * accumulate them. In-flight items are never touched. */\n prune(now: number = Date.now(), olderThanMs = 3_600_000): number {\n let n = 0\n for (const [id, item] of this.items) {\n const settled = item.state === \"answered\" || item.state === \"expired\"\n if (settled && now - (item.settledAt ?? item.createdAt) > olderThanMs) {\n this.items.delete(id)\n n++\n }\n }\n return n\n }\n\n /** Single exit point for both terminal states, so the waiter is always\n * resolved exactly once and never leaks. */\n private settle(item: InboxItem, state: \"answered\" | \"expired\", now: number, reason?: string): void {\n item.state = state\n item.settledAt = now\n const waiter = this.waiters.get(item.id)\n if (!waiter) return\n this.waiters.delete(item.id)\n waiter.resolve(\n state === \"answered\"\n ? { kind: \"answered\", text: item.answer ?? \"\", sessionId: item.sessionId }\n : { kind: \"expired\", reason: reason ?? \"expired\" },\n )\n }\n}\n\nfunction clip(s: string, max: number): string {\n if (s.length <= max) return s\n return `${s.slice(0, max)}\\n…[truncated ${s.length - max} chars]`\n}\n","import type { WatchState } from \"./watch\"\n\n// --- Attach mode: wear agentx inside a live Claude Code session ---\n//\n// Normally the daemon OWNS the loop: a message arrives, agentx spawns a\n// `claude` subprocess, feeds it a prompt, collects the result. Attach mode\n// inverts that. A Claude Code session that is already open — the one a human\n// is typing into — registers itself with the daemon and becomes addressable\n// as an agent. Inbound channel traffic is offered to that session instead of\n// spawning a fresh process.\n//\n// The session cannot be pushed to: its stdin belongs to the human. So the\n// daemon never initiates. It queues, and Claude Code's own hooks pull:\n//\n// SessionStart -> register the session (id, cwd, model)\n// UserPromptSubmit -> piggyback a \"N pending\" note onto the human's turn\n// Stop -> the drain point. Depending on the session's delivery\n// mode we stay silent, mention the backlog, or return\n// {decision:\"block\", reason:<msg>} which makes Claude\n// Code continue the conversation with that message as\n// input. That is the whole trick.\n// SessionEnd -> deregister; anything still queued falls back to spawn\n//\n// Everything here is in-memory on purpose. Attach state is a property of live\n// processes, not of the world: if the daemon restarts, the next hook event\n// from any session re-registers it. There is nothing worth persisting and\n// therefore no schema migration.\n\n/** How aggressively a session's Stop hook drains its inbox. */\nexport type DeliveryMode =\n /** Never volunteer anything. The human drains with `/inbox`. */\n | \"manual\"\n /** Mention the backlog in a systemMessage; don't take the turn. Default. */\n | \"notify\"\n /** Take the turn: block the stop and answer until the inbox is empty. */\n | \"auto\"\n\nexport const DELIVERY_MODES: readonly DeliveryMode[] = [\"manual\", \"notify\", \"auto\"] as const\n\nexport function isDeliveryMode(v: unknown): v is DeliveryMode {\n return typeof v === \"string\" && (DELIVERY_MODES as readonly string[]).includes(v)\n}\n\n/**\n * Lifecycle of one offered message.\n *\n * pending ──claim()──► claimed ──answer()──► answered\n * │ │\n * └───expire()────► expired ◄──expire()──┘\n *\n * `expired` is terminal and is the reason attach mode is safe to put in front\n * of production traffic: the dispatcher only waits a bounded time for a live\n * session to pick a message up, then atomically expires it and falls back to\n * spawning a provider the normal way. A session that drains late finds\n * nothing, so a message is never answered twice — and never dropped.\n */\nexport type ItemState = \"pending\" | \"claimed\" | \"answered\" | \"expired\"\n\nexport interface InboxItem {\n id: string\n /** Agent identity this was addressed to (the binding that captured it). */\n agentId: string\n /** Session currently responsible for it. */\n sessionId: string\n state: ItemState\n /** The message text as the channel delivered it. */\n text: string\n /** Where it came from — used for the \"[telegram/@bob]\" prefix on drain. */\n channel: string\n chatId: string\n sender: string\n createdAt: number\n claimedAt?: number\n settledAt?: number\n /** Set once the session produces a reply. */\n answer?: string\n}\n\nexport interface AttachSession {\n /** Claude Code's own session id (`CLAUDE_CODE_SESSION_ID` / hook stdin). */\n sessionId: string\n cwd: string\n /** Agent identities this session answers for. Usually one. */\n agentIds: string[]\n mode: DeliveryMode\n /** Last time any hook event was seen — drives stale-session reaping. */\n lastSeenAt: number\n registeredAt: number\n /** Model string when Claude Code reported one on SessionStart. */\n model?: string\n /** Item currently handed to the session and awaiting its reply. At most one\n * — a session answers serially, so the next Stop's `last_assistant_message`\n * is unambiguously the answer to this item. */\n awaitingAnswer?: string\n /** Items drained in the current Stop-hook chain, reset when the session\n * actually stops. Bounds the auto-drain loop. */\n drainedThisTurn: number\n /** Set when the session watches instead of answering (#167). A watcher\n * holds no identity, so it is never offered a message; it only gets an\n * event digest on each prompt. See watch.ts. */\n watch?: WatchState\n}\n\n/** Tunables. Deliberately not config-file-backed yet — these are runtime\n * ergonomics, and every one of them has a defensible default. Promote to\n * agentx.json only if a real deployment needs to differ. */\nexport interface AttachOptions {\n /** How long the dispatcher waits for a live session to claim a message\n * before giving up and spawning a provider instead. */\n claimTimeoutMs: number\n /** How long after its last hook event a session is considered gone. Claude\n * Code fires no heartbeat, so a session the human abandoned mid-turn would\n * otherwise hold its bindings forever. */\n staleSessionMs: number\n /** Max items one Stop-hook chain will drain before letting the session\n * stop. Prevents a flood of queued messages from hijacking a whole turn. */\n maxDrainPerTurn: number\n /** Longest single message injected into a session's context. */\n maxItemChars: number\n}\n\nexport const DEFAULT_ATTACH_OPTIONS: AttachOptions = {\n claimTimeoutMs: 90_000,\n staleSessionMs: 15 * 60_000,\n maxDrainPerTurn: 5,\n maxItemChars: 4_000,\n}\n\n/** What the Stop hook should hand back to Claude Code. Mirrors the documented\n * hook output contract; `service.ts` serializes it. */\nexport interface StopDecision {\n /** Present only in `auto` mode when we want the session to keep going. */\n decision?: \"block\"\n reason?: string\n systemMessage?: string\n suppressOutput?: boolean\n}\n","import { z } from \"zod\"\nimport { eventSubscriptionSchema } from \"@/daemon/config\"\nimport type { EventEnvelope } from \"@/events/envelope\"\nimport { formatEventLine, matchesFilters, type SubscriptionInput } from \"@/events/subscriptions\"\n\n// --- Watch mode: stay aware of the mesh without answering for it (#167) ---\n//\n// `agentx attach as <agent>` makes a session answer for an agent. A watching\n// session answers for nobody. It holds no identity, so the dispatcher never\n// offers it a message and the Stop hook never takes its turn. The one thing\n// it gets is a short digest on UserPromptSubmit: the events since its last\n// turn that match its subscription, capped so a busy mesh cannot flood the\n// human's context.\n//\n// This module is the pure half: the default filter, the cursor that says\n// where the last digest stopped, and the rendering. The registry stores the\n// state; service.ts reads the event ring and calls in here.\n\n/** What a watcher sees when it names no filter: failures, completions,\n * approvals waiting, peers down. Mapped to the envelope kinds and types the\n * daemon actually publishes (src/events/bus.ts, src/daemon/event-bus.ts):\n *\n * task:completed an agent turn ended; the summary says \"failed after …\"\n * or \"completed in …\", so one type covers both\n * failed/timeout/ workflow run phases (kind \"run\"); no other publisher\n * completed uses these types\n * paused + match a run stopped at a checkpoint, i.e. waiting on a person\n * lost a mesh peer stopped answering\n */\nexport const DEFAULT_WATCH_SUBSCRIPTIONS: SubscriptionInput[] = [\n { kinds: [\"task:completed\"] },\n { kinds: [\"failed\", \"timeout\", \"completed\"] },\n { kinds: [\"paused\"], match: \"checkpoint\" },\n { kinds: [\"lost\"] },\n]\n\n/** Validate a watcher's filter with the same schema agent subscriptions use\n * (#165). Absent means the default filter. */\nexport function parseWatchSubscriptions(\n raw: unknown,\n): { ok: true; subscriptions: SubscriptionInput[] } | { ok: false; error: string } {\n if (raw === undefined || raw === null) return { ok: true, subscriptions: DEFAULT_WATCH_SUBSCRIPTIONS }\n const parsed = z.array(eventSubscriptionSchema).min(1).safeParse(raw)\n if (!parsed.success) {\n const issue = parsed.error.issues[0]\n return { ok: false, error: `invalid subscriptions: ${issue?.path.join(\".\") || \"value\"} ${issue?.message ?? \"\"}`.trim() }\n }\n return { ok: true, subscriptions: parsed.data }\n}\n\n/** Hard caps on one injected digest. */\nexport const WATCH_DIGEST_MAX_LINES = 15\nexport const WATCH_DIGEST_MAX_CHARS = 1_500\n/** Longest single event line, so one long summary cannot crowd out the rest. */\nconst LINE_MAX = 200\n\n/** Where the last digest stopped. `id` is the newest event the session was\n * shown or skipped past; `at` is its time, used when the id has left the\n * ring (daemon restart, buffer wrap). No id means \"since `at`, inclusive\":\n * the ring was empty when watching began. */\nexport interface WatchCursor {\n id?: string\n at: string\n}\n\nexport interface WatchState {\n subscriptions: SubscriptionInput[]\n cursor: WatchCursor\n startedAt: number\n}\n\n/** A cursor pointing at the newest event in `events`, or at `now` when\n * there are none. A new watcher starts here, not at the start of the ring. */\nexport function cursorAtEnd(events: EventEnvelope[], now: number = Date.now()): WatchCursor {\n const last = events[events.length - 1]\n return last ? { id: last.id, at: last.at } : { at: new Date(now).toISOString() }\n}\n\n/** Events strictly after the cursor, oldest first. */\nexport function eventsAfter(events: EventEnvelope[], cursor: WatchCursor): EventEnvelope[] {\n if (cursor.id) {\n const idx = events.findIndex((e) => e.id === cursor.id)\n if (idx >= 0) return events.slice(idx + 1)\n }\n const t = Date.parse(cursor.at)\n if (Number.isNaN(t)) return events\n return cursor.id\n ? events.filter((e) => Date.parse(e.at) > t)\n : events.filter((e) => Date.parse(e.at) >= t)\n}\n\nexport interface WatchDigest {\n /** The context block, or undefined when nothing matched. */\n text?: string\n /** Where the next digest starts: the newest event looked at, matched or not. */\n cursor: WatchCursor\n matched: number\n shown: number\n}\n\n/** Build the digest for one prompt. Pure: the caller stores the cursor. */\nexport function watchDigest(\n state: WatchState,\n events: EventEnvelope[],\n caps: { maxLines?: number; maxChars?: number } = {},\n): WatchDigest {\n const fresh = eventsAfter(events, state.cursor)\n const cursor = fresh.length ? cursorAtEnd(fresh) : state.cursor\n const matched = fresh.filter((e) => state.subscriptions.some((s) => matchesFilters(s, e)))\n if (matched.length === 0) return { cursor, matched: 0, shown: 0 }\n const since = state.cursor.id ?? state.cursor.at\n const { text, shown } = renderWatchDigest(matched, since, caps)\n return { text, cursor, matched: matched.length, shown }\n}\n\n/**\n * Render matched events (oldest first) under the caps. Keeps the newest\n * events that fit and ends with \"+N more\" and the command that lists them\n * all. Both caps count the whole block, header and trailer included.\n */\nexport function renderWatchDigest(\n matched: EventEnvelope[],\n since: string,\n caps: { maxLines?: number; maxChars?: number } = {},\n): { text: string; shown: number } {\n const maxLines = Math.max(3, caps.maxLines ?? WATCH_DIGEST_MAX_LINES)\n const maxChars = Math.max(200, caps.maxChars ?? WATCH_DIGEST_MAX_CHARS)\n const lines = matched.map((e) => clipLine(`- ${formatEventLine(e)}`))\n const total = lines.length\n\n for (let shown = Math.min(total, maxLines - 1); shown >= 0; shown--) {\n const more = total - shown\n const header = more\n ? `[agentx watch] ${total} events since your last turn, newest ${shown} shown:`\n : `[agentx watch] ${total} event${total === 1 ? \"\" : \"s\"} since your last turn:`\n const out = [header, ...lines.slice(total - shown)]\n if (more) out.push(`+${more} more — \\`agentx events --since ${since}\\``)\n const text = out.join(\"\\n\")\n if (out.length <= maxLines && text.length <= maxChars) return { text, shown }\n }\n // Unreachable with sane caps: zero events plus header and trailer fit.\n return { text: `[agentx watch] ${total} events since your last turn — \\`agentx events --since ${since}\\``, shown: 0 }\n}\n\nfunction clipLine(s: string): string {\n return s.length <= LINE_MAX ? s : `${s.slice(0, LINE_MAX - 1)}…`\n}\n","export { AttachRegistry, type OfferOutcome, type OfferInput, type BindingStore, type SavedBinding } from \"./registry\"\nexport {\n DEFAULT_ATTACH_OPTIONS,\n DELIVERY_MODES,\n isDeliveryMode,\n type AttachOptions,\n type AttachSession,\n type DeliveryMode,\n type InboxItem,\n type ItemState,\n type StopDecision,\n} from \"./types\"\nexport {\n DEFAULT_WATCH_SUBSCRIPTIONS,\n cursorAtEnd,\n parseWatchSubscriptions,\n type WatchCursor,\n type WatchState,\n} from \"./watch\"\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { dirname, join } from \"node:path\"\nimport { AttachRegistry, type BindingStore, type SavedBinding } from \"./registry\"\n\n/** #193 — bindings survive daemon restarts. Atomic write, owner-only. */\nexport function fileBindingStore(path: string): BindingStore {\n return {\n load: () => (existsSync(path) ? (JSON.parse(readFileSync(path, \"utf-8\")) as Record<string, SavedBinding>) : {}),\n save: (all) => {\n mkdirSync(dirname(path), { recursive: true })\n const tmp = `${path}.tmp`\n writeFileSync(tmp, JSON.stringify(all, null, 2), { mode: 0o600 })\n renameSync(tmp, path)\n },\n }\n}\n\nfunction defaultBindingStore(): BindingStore | undefined {\n // Test runs never touch the real ~/.agentx.\n if (process.env.VITEST) return undefined\n return fileBindingStore(process.env.AGENTX_ATTACH_BINDINGS_FILE ?? join(homedir(), \".agentx\", \"attach-bindings.json\"))\n}\n\n// Process-global registry. The daemon owns one; the CLI and MCP tools reach\n// it over loopback HTTP rather than importing it, exactly like the guard.\nlet _registry: AttachRegistry | undefined\n\nexport function getAttachRegistry(): AttachRegistry {\n if (!_registry) _registry = new AttachRegistry({}, defaultBindingStore())\n return _registry\n}\n\n/** Test-only: drop the global so each suite starts clean. */\nexport function resetAttachRegistry(): void {\n _registry = undefined\n}\n","// --- Claude CLI overage eligibility ---\n//\n// When Anthropic disables overage (\"extra usage\" / pay-as-you-go beyond the\n// Max allotment) at the org level, every request that would spill past the\n// regular Max allotment fails with:\n// API Error: 400 invalid_request_error \"You're out of extra usage\"\n//\n// Claude CLI caches the state in ~/.claude.json:\n// cachedExtraUsageDisabledReason: \"org_level_disabled_until…\"\n// overageCreditGrantCache: { \"<id>\": { info: { available: false, … } } }\n//\n// Agents with a warm Claude session can still succeed because the replay is\n// largely cache_read and fits under the regular allotment. Agents without a\n// warm session pay full cache_create on first call, spill into overage, and\n// fail — creating a vicious cycle where the agent never establishes a warm\n// session in the first place (observed on pm-umbrella 2026-04-23).\n//\n// This module reads the cached state so callers can short-circuit doomed\n// cold dispatches and surface a crisp operator-facing error instead of\n// silently burning Claude CLI subprocesses to reproduce the same symptom.\n\nimport { readFileSync } from \"fs\"\nimport { homedir } from \"os\"\nimport { join } from \"path\"\n\nexport interface OverageStatus {\n /** True when Anthropic's overage pool is usable; false when org-disabled. */\n available: boolean\n /** Raw reason string from .claude.json when disabled (e.g. \"org_level_disabled_until\"). */\n reason?: string\n /** Wall-clock ms when this value was computed. */\n checkedAt: number\n /** True when .claude.json was missing or unparseable — treat as \"unknown, assume OK\". */\n unknown?: boolean\n}\n\nexport interface OverageGateResult {\n abort: true\n reason: string\n message: string\n raw: OverageStatus\n}\n\nlet cached: OverageStatus | null = null\nconst TTL_MS = 60_000\n\n/** Clear the in-memory cache. Exposed for tests and for a manual /reload path. */\nexport function clearOverageStatusCache(): void { cached = null }\n\nfunction readClaudeJson(): Record<string, unknown> | null {\n try {\n const path = join(homedir(), \".claude.json\")\n const raw = readFileSync(path, \"utf8\")\n const parsed = JSON.parse(raw)\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, unknown>) : null\n } catch {\n return null\n }\n}\n\n/**\n * Pure function — given the parsed contents of ~/.claude.json (or null if the\n * file is missing), return the overage status. Split out from the IO path so\n * tests can exercise the decision logic without staging a filesystem.\n *\n * Block only on `cachedExtraUsageDisabledReason === \"org_level_disabled_until\"`\n * — that's what claude.ai writes when an operator has explicitly turned\n * overage off at the account level. Other values (most commonly\n * `out_of_credits`) are the CLI's latest-error snapshot from a failed API\n * call and may recover on the next rotation; blocking on them deadlocks the\n * fleet after a single transient rejection. For those, let the request\n * through — the real Anthropic API response will be authoritative, surfaced\n * via error-map's `overage_disabled` friendly classification.\n *\n * The grant-cache `info.available === false` signal was previously also used\n * as a block condition but suffered the same stale-after-failure problem\n * (the CLI writes it once, never refreshes on small calls), so it has been\n * dropped from the gate. If you need a hard block, set the reason to\n * `org_level_disabled_until` in ~/.claude.json manually.\n */\nexport function parseOverageStatus(\n data: Record<string, unknown> | null,\n now: number = Date.now(),\n): OverageStatus {\n if (!data) return { available: true, checkedAt: now, unknown: true }\n const disabledReason = typeof data.cachedExtraUsageDisabledReason === \"string\"\n ? (data.cachedExtraUsageDisabledReason as string)\n : undefined\n const unavailable = disabledReason === \"org_level_disabled_until\"\n return {\n available: !unavailable,\n reason: disabledReason,\n checkedAt: now,\n }\n}\n\n/**\n * Read the Claude CLI-cached overage state with a short TTL. Does not hit\n * the network — purely reads the CLI's own cache file that it updates after\n * each billing roundtrip.\n */\nexport function getOverageStatus(forceRefresh = false): OverageStatus {\n const now = Date.now()\n if (!forceRefresh && cached && now - cached.checkedAt < TTL_MS) return cached\n cached = parseOverageStatus(readClaudeJson(), now)\n return cached\n}\n\n/**\n * Heuristic gate for cold claude-code dispatches. When overage is\n * unavailable, a fresh call (no claudeSessionId to --resume into) will pay\n * full cache-create and overwhelmingly likely fail with \"out of extra\n * usage\". Short-circuiting saves the subprocess and surfaces a crisp,\n * operator-actionable error.\n *\n * Returns `null` when the dispatch can proceed. Returns an abort struct\n * when the caller should skip the dispatch and surface the message.\n */\nexport function preflightOverageGate(hasWarmSession: boolean): OverageGateResult | null {\n const status = getOverageStatus()\n if (status.available) return null\n if (hasWarmSession) return null // warm session replays via cache_read → stays in regular allotment\n return {\n abort: true,\n reason: status.reason || \"overage_disabled\",\n message:\n \"Claude Max-plan overage is disabled at the org level and this agent has no warm Claude session to cache against. \" +\n \"A cold dispatch would pay a fresh cache-create that spills past the regular allotment, producing 'out of extra usage'. \" +\n \"Re-enable overage at claude.ai/settings/usage, OR wait until a peer agent has established a warm session this agent can inherit before retrying.\",\n raw: status,\n }\n}\n","// --- Claude subscription dispatch gate ---\n//\n// All claude-code-tier agents share one Claude subscription (one OAuth), so\n// their usage pools together. Anthropic enforces rolling windows (5 hours,\n// 7 days) on that pool, measured by utilization, not by message count.\n// Claude Code reports the state of those windows on every turn as a\n// `rate_limit_event` in its stream-json output:\n//\n// { type: \"rate_limit_event\",\n// rate_limit_info: { status: \"allowed\" | \"allowed_warning\" | \"rejected\",\n// resetsAt: <unix seconds>, rateLimitType: \"five_hour\" | … } }\n//\n// That event is the source of truth here. When Claude Code says a window is\n// rejected, cold dispatches (no warm session) are held until the reported\n// reset time, so scheduled jobs don't burn subprocesses reproducing the same\n// refusal. Warm sessions always pass: they replay from prompt cache and a\n// real refusal comes straight back from Claude as the task error.\n//\n// One exception: on an account with extra usage switched on, Claude Code\n// keeps serving requests after a window is used up and still reports it as\n// rejected, with `isUsingOverage: true` (and `overageStatus` \"allowed\" or\n// \"allowed_warning\"). That is not a refusal, so it does not hold anything.\n//\n// Each event carries one status for the request plus the one window that\n// matters most, so an `allowed` event means Claude served the request\n// whichever window it names: it lifts the holds of the other windows too.\n// A model window (\"seven_day_opus\", \"seven_day_sonnet\") only concerns that\n// model: it holds, and is lifted by, runs on that model alone. A run with\n// no model set is held by every window, because we can't tell which it uses.\n//\n// The dispatch counters (last hour / last 5h) stay for observability and for\n// operators who want a hard local ceiling on top of the plan. Local caps are\n// opt-in: with no cap configured the counters never gate anything. Before\n// this, a default local cap sized for a \"225 messages per 5h\" guess held back\n// every cron and bot-to-bot call on a busy morning while Anthropic was still\n// accepting requests.\n//\n// recordClaudeCodeDispatch() — call when a dispatch is about to fire\n// recordRateLimitEvent(ev) — call for every stream-json event; ignores others\n// getClaudeCodeUsage() — counters + the last provider signal\n// liftProviderHolds() — operator override: drop the active holds\n// preflightQuotaGate(…) — short-circuit for cold dispatches\n\nexport interface DispatchBudget {\n /** Optional hard hourly ceiling on new claude-code runs. Unset or 0 = off. */\n maxPerHour?: number\n /** Optional hard rolling-5-hour ceiling. Unset or 0 = off. */\n maxPer5h?: number\n /** Warn when usage >= warnRatio × a configured cap (default 0.8). */\n warnRatio?: number\n}\n\nconst DEFAULT_BUDGET: Required<Pick<DispatchBudget, \"warnRatio\">> & DispatchBudget = {\n warnRatio: 0.8,\n}\n\nlet budget: DispatchBudget = { ...DEFAULT_BUDGET }\n\n// Ring buffer of dispatch timestamps, newest last. We only ever need the\n// last 5h, so prune anything older on record.\nconst FIVE_H_MS = 5 * 60 * 60 * 1000\nconst ONE_H_MS = 60 * 60 * 1000\nconst timestamps: number[] = []\n\nexport function setDispatchBudget(next: DispatchBudget | undefined): void {\n budget = { ...DEFAULT_BUDGET, ...(next || {}) }\n}\n\nexport function getDispatchBudget(): DispatchBudget { return { ...budget } }\n\nexport function clearDispatchHistory(): void {\n timestamps.length = 0\n providerSignals.clear()\n}\n\nfunction prune(now: number): void {\n const cutoff = now - FIVE_H_MS\n let i = 0\n while (i < timestamps.length && timestamps[i] < cutoff) i++\n if (i > 0) timestamps.splice(0, i)\n}\n\nexport function recordClaudeCodeDispatch(now: number = Date.now()): void {\n prune(now)\n timestamps.push(now)\n}\n\n// --- Provider signal (Claude Code rate_limit_event) ---\n\nexport type RateLimitStatus = \"allowed\" | \"allowed_warning\" | \"rejected\"\n\nexport interface RateLimitSignal {\n status: RateLimitStatus\n /** Window name as Claude Code reports it (\"five_hour\", \"seven_day\", …). */\n window: string\n /** Wall-clock ms when the window resets, when reported. */\n resetsAt?: number\n /** 0..1 share of the window used, when reported. */\n utilization?: number\n /** True when the window is used up but extra usage is still serving requests. */\n usingOverage?: boolean\n /** Wall-clock ms when this signal was observed. */\n seenAt: number\n}\n\n/** Latest signal per window. A newer event for the same window replaces it. */\nconst providerSignals = new Map<string, RateLimitSignal>()\n\n/** A rejected signal with no reset time is trusted for this long. */\nconst REJECTED_FALLBACK_HOLD_MS = 15 * 60 * 1000\n\nfunction asNumber(v: unknown): number | undefined {\n if (typeof v === \"number\" && Number.isFinite(v)) return v\n if (typeof v === \"string\" && v.trim() !== \"\" && Number.isFinite(Number(v))) return Number(v)\n return undefined\n}\n\n/** Claude Code reports resetsAt in unix seconds; accept ms too. */\nfunction asEpochMs(v: unknown): number | undefined {\n const n = asNumber(v)\n if (n === undefined || n <= 0) return undefined\n return n < 1e12 ? n * 1000 : n\n}\n\n/**\n * Parse a stream-json event. Returns the signal when the event is a\n * rate_limit_event with a recognisable status, otherwise null. Field names\n * follow Claude Code's camelCase; snake_case variants are accepted so a\n * CLI update doesn't silently blind the gate.\n */\nexport function parseRateLimitEvent(event: unknown, now: number = Date.now()): RateLimitSignal | null {\n if (!event || typeof event !== \"object\") return null\n const e = event as Record<string, unknown>\n if (e.type !== \"rate_limit_event\") return null\n const info = (e.rate_limit_info ?? e.rateLimitInfo) as Record<string, unknown> | undefined\n if (!info || typeof info !== \"object\") return null\n const status = info.status\n if (status !== \"allowed\" && status !== \"allowed_warning\" && status !== \"rejected\") return null\n const window = String(info.rateLimitType ?? info.rate_limit_type ?? \"unknown\")\n const resetsAt = asEpochMs(info.resetsAt ?? info.resets_at)\n const utilization = asNumber(info.utilization)\n const overageStatus = info.overageStatus ?? info.overage_status\n const usingOverage = status === \"rejected\" && (\n (info.isUsingOverage ?? info.is_using_overage) === true\n || overageStatus === \"allowed\" || overageStatus === \"allowed_warning\"\n )\n return { status, window, resetsAt, utilization, ...(usingOverage && { usingOverage }), seenAt: now }\n}\n\n/** \"seven_day_opus\" → \"opus\". Null for the windows shared by every model. */\nfunction windowModel(window: string): string | null {\n return /_(opus|sonnet|haiku)$/.exec(window)?.[1] ?? null\n}\n\n/** Whether a window concerns a run on `model`. Unknown model: every window does. */\nfunction concerns(window: string, model: string | undefined): boolean {\n const family = windowModel(window)\n return !family || !model || model.toLowerCase().includes(family)\n}\n\n/**\n * Record the provider's view of the plan window. Non rate-limit events are\n * ignored. `model` is the model of the run that produced the event; a served\n * request lifts the holds of the other windows that concern that model.\n */\nexport function recordRateLimitEvent(event: unknown, now: number = Date.now(), model?: string): RateLimitSignal | null {\n const signal = parseRateLimitEvent(event, now)\n if (!signal) return null\n if (signal.status !== \"rejected\") {\n for (const [window, s] of providerSignals) {\n if (s.status !== \"rejected\" || window === signal.window) continue\n if (!windowModel(window) || (model && concerns(window, model))) providerSignals.delete(window)\n }\n }\n providerSignals.set(signal.window, signal)\n return signal\n}\n\n/** The rejected windows that still hold at `now`. Extra usage still serving is not a hold. */\nexport function activeProviderHolds(now: number = Date.now()): RateLimitSignal[] {\n return [...providerSignals.values()].filter((s) => {\n if (s.status !== \"rejected\" || s.usingOverage) return false\n return (s.resetsAt ?? s.seenAt + REJECTED_FALLBACK_HOLD_MS) > now\n })\n}\n\n/** The window that holds a cold run on `model` at `now`, if any. */\nexport function activeProviderHold(now: number = Date.now(), model?: string): RateLimitSignal | null {\n return activeProviderHolds(now).find((s) => concerns(s.window, model)) ?? null\n}\n\n/**\n * Operator override: forget the active holds, so the next cold dispatch asks\n * Claude again. If the window is still used up, that run's refusal puts the\n * hold back. Returns the holds that were lifted.\n */\nexport function liftProviderHolds(now: number = Date.now()): RateLimitSignal[] {\n const lifted = activeProviderHolds(now)\n for (const s of lifted) providerSignals.delete(s.window)\n return lifted\n}\n\nexport function getProviderSignals(): RateLimitSignal[] {\n return [...providerSignals.values()].sort((a, b) => b.seenAt - a.seenAt)\n}\n\n// --- Usage view ---\n\nexport interface ClaudeCodeUsage {\n lastHour: number\n last5h: number\n maxPerHour?: number\n maxPer5h?: number\n hourlyRatio?: number // usage/max in [0,1] when max is set\n fiveHourRatio?: number // usage/max in [0,1] when max is set\n /** Latest signal per window from Claude Code, newest first. */\n provider: RateLimitSignal[]\n}\n\nfunction capOn(v: number | undefined): v is number { return typeof v === \"number\" && v > 0 }\n\nexport function getClaudeCodeUsage(now: number = Date.now()): ClaudeCodeUsage {\n prune(now)\n const hourCutoff = now - ONE_H_MS\n let lastHour = 0\n for (let i = timestamps.length - 1; i >= 0; i--) {\n if (timestamps[i] >= hourCutoff) lastHour++\n else break\n }\n const last5h = timestamps.length\n const out: ClaudeCodeUsage = {\n lastHour,\n last5h,\n maxPerHour: capOn(budget.maxPerHour) ? budget.maxPerHour : undefined,\n maxPer5h: capOn(budget.maxPer5h) ? budget.maxPer5h : undefined,\n provider: getProviderSignals(),\n }\n if (out.maxPerHour) out.hourlyRatio = lastHour / out.maxPerHour\n if (out.maxPer5h) out.fiveHourRatio = last5h / out.maxPer5h\n return out\n}\n\n// --- Gate ---\n\nexport interface QuotaGateAbort {\n abort: true\n reason: \"provider_rate_limit\" | \"hourly_cap\" | \"five_hour_cap\"\n message: string\n usage: ClaudeCodeUsage\n}\n\nfunction windowLabel(window: string): string {\n return window.replace(/_/g, \" \")\n}\n\nfunction resetLabel(resetsAt: number | undefined, now: number): string {\n if (!resetsAt) return \"shortly\"\n const mins = Math.max(1, Math.round((resetsAt - now) / 60_000))\n return `in about ${mins} min (${new Date(resetsAt).toISOString()})`\n}\n\n/**\n * Returns null when dispatch is allowed; returns an abort struct when the\n * provider reports the plan window as rejected, or when usage is past a\n * configured local cap. Warm sessions are always allowed through — they\n * replay via cache_read and are cheap — so only cold dispatches get gated,\n * same philosophy as preflightOverageGate.\n *\n * The function DOES NOT mutate state. Call `recordClaudeCodeDispatch()`\n * separately once the caller commits to the dispatch.\n */\nexport function preflightQuotaGate(hasWarmSession: boolean, now: number = Date.now(), model?: string): QuotaGateAbort | null {\n const usage = getClaudeCodeUsage(now)\n if (hasWarmSession) return null\n const hold = activeProviderHold(now, model)\n if (hold) {\n return {\n abort: true,\n reason: \"provider_rate_limit\",\n message:\n `Claude plan limit reached: Claude Code reports the ${windowLabel(hold.window)} window as rejected. ` +\n `Cold dispatches are held until it resets ${resetLabel(hold.resetsAt, now)}; open conversations still go through. ` +\n `To try again now, run: agentx usage plan --lift`,\n usage,\n }\n }\n if (usage.maxPerHour && usage.lastHour >= usage.maxPerHour) {\n return {\n abort: true,\n reason: \"hourly_cap\",\n message:\n `Local dispatch cap reached: ${usage.lastHour}/${usage.maxPerHour} claude-code runs in the last hour. ` +\n `Cold dispatches are held; open conversations still go through. ` +\n `Raise or remove session.maxClaudeCodeDispatchesPerHour in agentx.json (applies on save).`,\n usage,\n }\n }\n if (usage.maxPer5h && usage.last5h >= usage.maxPer5h) {\n return {\n abort: true,\n reason: \"five_hour_cap\",\n message:\n `Local dispatch cap reached: ${usage.last5h}/${usage.maxPer5h} claude-code runs in the last 5h. ` +\n `Cold dispatches are held; open conversations still go through. ` +\n `Raise or remove session.maxClaudeCodeDispatchesPer5h in agentx.json (applies on save).`,\n usage,\n }\n }\n return null\n}\n\n/**\n * Returns a warning string when the provider reports a window near its\n * limit, or when usage has crossed warnRatio of a configured local cap.\n * Otherwise null. Suppression of repeats is a caller concern.\n */\nexport function warnIfNearingCap(now: number = Date.now()): string | null {\n const usage = getClaudeCodeUsage(now)\n const nearing = usage.provider.find((s) => s.status === \"allowed_warning\")\n if (nearing) {\n const pct = nearing.utilization !== undefined ? ` (${Math.round(nearing.utilization * 100)}% used)` : \"\"\n return `claude plan ${windowLabel(nearing.window)} window nearing its limit${pct}, resets ${resetLabel(nearing.resetsAt, now)}`\n }\n const ratio = budget.warnRatio ?? 0.8\n if (usage.hourlyRatio !== undefined && usage.hourlyRatio >= ratio && usage.hourlyRatio < 1) {\n return `claude-code hourly usage ${usage.lastHour}/${usage.maxPerHour} (${Math.round(usage.hourlyRatio * 100)}% of local cap)`\n }\n if (usage.fiveHourRatio !== undefined && usage.fiveHourRatio >= ratio && usage.fiveHourRatio < 1) {\n return `claude-code 5h usage ${usage.last5h}/${usage.maxPer5h} (${Math.round(usage.fiveHourRatio * 100)}% of local cap)`\n }\n return null\n}\n","// --- Prompt-size drift tracker ---\n//\n// Passive per-agent/channel monitor for the bytes AgentX controls before\n// dispatch (historyContext + systemPromptAppend + task.message). Exposes:\n//\n// recordPromptSize(key, bytes, parts) — called from the dispatch path\n// getPromptSizeReport() — snapshot for /health or ops\n// warnIfPromptGrowing(key, bytes) — returns a warning string when\n// the current sample is >= 1.5x\n// the baseline for its key\n//\n// Baseline is an EWMA (exponential weighted moving average) over the last\n// ~20 samples per key. First few samples just seed the baseline; we only\n// start flagging growth once enough samples have accumulated.\n//\n// This is NOT wired to an alerting backend — it emits via the daemon log.\n// Route downstream as you need (journal-watch, tail+grep, etc).\n\nexport interface PromptSizeParts {\n history: number\n sysPrompt: number\n message: number\n}\n\nexport interface PromptSizeSample {\n key: string\n timestamp: number\n total: number\n parts: PromptSizeParts\n}\n\nexport interface PromptSizeStats {\n key: string\n samples: number\n /** EWMA of total bytes. */\n baseline: number\n /** Most recent total bytes. */\n current: number\n /** Highest observed bytes since process start. */\n peak: number\n lastAt: number\n}\n\nconst ALPHA = 0.2 // EWMA smoothing — current sample weight\nconst WARN_MULTIPLIER = 1.5 // flag when current >= 1.5x baseline\nconst MIN_SAMPLES = 5 // don't warn before this many samples\nconst RING_SIZE = 100 // per-key most-recent-samples kept in memory\n\nconst stats = new Map<string, PromptSizeStats & { ring: PromptSizeSample[] }>()\n\n/** Build a key for the tracker. Scoped per agent+channel+chatId because size\n * varies meaningfully across channels (a gitlab issue reply has a different\n * prompt shape than a telegram DM). */\nexport function promptSizeKey(agentId: string, channel: string, chatId?: string): string {\n return chatId ? `${agentId}:${channel}:${chatId}` : `${agentId}:${channel}`\n}\n\n/** Record a sample. Idempotent; callers may invoke on every dispatch. */\nexport function recordPromptSize(key: string, total: number, parts: PromptSizeParts, now: number = Date.now()): void {\n let s = stats.get(key)\n if (!s) {\n s = {\n key,\n samples: 0,\n baseline: total, // seed with first sample so the first warn comparison is a no-op\n current: total,\n peak: total,\n lastAt: now,\n ring: [],\n }\n stats.set(key, s)\n }\n s.samples += 1\n s.baseline = ALPHA * total + (1 - ALPHA) * s.baseline\n s.current = total\n s.peak = Math.max(s.peak, total)\n s.lastAt = now\n s.ring.push({ key, timestamp: now, total, parts })\n if (s.ring.length > RING_SIZE) s.ring.shift()\n}\n\n/** Returns a warning string if the latest sample is >= 1.5x baseline and we\n * have enough history to trust the baseline; otherwise null. */\nexport function warnIfPromptGrowing(key: string): string | null {\n const s = stats.get(key)\n if (!s) return null\n if (s.samples < MIN_SAMPLES) return null\n const threshold = s.baseline * WARN_MULTIPLIER\n if (s.current < threshold) return null\n const ratio = s.current / s.baseline\n return `prompt-size drift for ${key}: ${s.current} bytes is ${ratio.toFixed(2)}x baseline (${Math.round(s.baseline)} bytes over ${s.samples} samples, peak ${s.peak})`\n}\n\n/** Snapshot of all tracked keys — suitable for a /health or /metrics endpoint. */\nexport function getPromptSizeReport(): Array<Omit<PromptSizeStats, \"key\"> & { key: string }> {\n return Array.from(stats.values()).map(({ ring, ...rest }) => rest)\n}\n\n/** Test / diagnostic reset. */\nexport function clearPromptSizeStats(): void { stats.clear() }\n","import { existsSync, readFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { agentxToolServer, type McpServerConfig, type McpServerMap } from \"./agent-mcp\"\n\n// --- Session profiles (#615) ---\n//\n// A session's first turn used to carry the same start whatever started it:\n// every MCP server the user has, every user-level skill and plugin, the\n// global CLAUDE.md, plus the landscape, chat history and cross-chat\n// context AgentX pushes into the prompt. A GitHub label event or a one-line\n// agent-to-agent reply paid all of it. The `lean` profile gives such\n// sessions only what the task needs up front and leaves the rest a tool\n// call away:\n//\n// claude flags --strict-mcp-config --mcp-config {agentx (+ named)}\n// --setting-sources project,local\n// prompt no landscape / history / cross-chat; one line names the\n// agentx MCP tools that fetch each on demand\n// system prompt the project CLAUDE.md is not appended when Claude Code\n// loads it from the workspace itself\n//\n// Chat channels (telegram, whatsapp, voice, dashboard …) keep the `full`\n// start; only the channels listed in `session.profileByChannel`, or in\n// DEFAULT_LEAN_CHANNELS when unlisted, are lean.\n//\n// A full claude-code session gets one flag of its own (#668): `--mcp-config`\n// with this install's agentx tool server, on top of whatever the user and\n// the workspace load. Without it the tools (agentx_approval among them)\n// depended on the workspace .mcp.json being both written and approved,\n// which a node with no user-level MCP servers never had.\n\nexport type SessionProfileName = \"full\" | \"lean\"\n\nexport type SettingSource = \"user\" | \"project\" | \"local\"\n\nexport interface LeanProfileConfig {\n mcpServers: string[]\n settingSources: SettingSource[]\n contextOnDemand: boolean\n /** Built-in Claude Code tools a lean session gets (`--tools`). Empty:\n * every built-in tool, as today. The built-in tool schemas are about\n * 14k tokens of the first turn, so this is the lever that gets a lean\n * start under 20k; an agent that lacks a tool it needs fails mid-task,\n * so lists stay opt-in. ToolSearch is always added to a list, so the\n * agentx MCP tools stay deferred (see withToolSearch). */\n tools: string[]\n /** Per-channel tool lists; a non-empty entry wins over `tools`. */\n toolsByChannel: Record<string, string[]>\n /** The `agentx_` MCP tools a lean session lists (#699). Empty: every\n * agentx tool, as today. While ToolSearch is available (always, since\n * #700) Claude Code defers them and a short list saves only their\n * names, about 300 tokens; it saves about 5.5k only where tool search\n * is off. A tool left off is not findable at all, so this is opt-in. */\n agentxTools: string[]\n /** Per-channel agentx tool lists; a non-empty entry wins over\n * `agentxTools`. */\n agentxToolsByChannel: Record<string, string[]>\n}\n\nexport interface SessionProfileConfig {\n profileByChannel: Record<string, SessionProfileName>\n lean: LeanProfileConfig\n}\n\n/** Channels that are lean unless `session.profileByChannel` says otherwise. */\nexport const DEFAULT_LEAN_CHANNELS: readonly string[] = [\"github\", \"a2a\", \"workflow\", \"cron\"]\n\n/** Tiers whose sessions can reach the agentx MCP tools, so a lean start\n * can hand them context on demand. Other tiers always start full. */\nconst LEAN_CAPABLE_TIERS: readonly string[] = [\"claude-code\", \"codex-cli\"]\n\nexport const DEFAULT_LEAN: LeanProfileConfig = {\n mcpServers: [\"agentx\"],\n settingSources: [\"project\", \"local\"],\n contextOnDemand: true,\n tools: [],\n toolsByChannel: {},\n agentxTools: [],\n agentxToolsByChannel: {},\n}\n\n/** The agentx tool server reads its tool list from this variable. */\nexport const AGENTX_MCP_TOOLS_ENV = \"AGENTX_MCP_TOOLS\"\n\n/** agentx tools a shortened list always keeps: AgentX's own prompts ask\n * for them by name, whatever started the session — request follow-ups\n * (requests/sweep.ts, requests/tracker.ts), approval cards, the event\n * digest and wake text (events/subscriptions.ts, events/wake.ts),\n * queued attachments (attach/service.ts) and workflow steps and nudges\n * (workflows/nodes/follow-up.ts). A tool left off the list is\n * not even findable through ToolSearch, so a prompt naming it would\n * send the agent after a tool it cannot call. The test scans src/ for\n * prompts naming any other tool. */\nexport const AGENTX_CORE_TOOLS: readonly string[] = [\"agentx_approval\", \"agentx_request\", \"agentx_events\", \"agentx_attach_next\", \"agentx_workflow\"]\n\n/** The tools the `[Context on demand]` line names; kept whenever a lean\n * start relies on that line. */\nexport const AGENTX_ON_DEMAND_TOOLS: readonly string[] = [\"agentx_agents\", \"agentx_recent\", \"agentx_wiki_query\"]\n\n/** The built-in tools a lean session on `channel` gets: the channel's own\n * list when one is set, else the shared `tools` list. Empty means every\n * built-in tool (no `--tools` flag). An all-tools-off list is never\n * produced: `--tools \"\"` is not a saving, it makes Claude Code load every\n * MCP tool schema instead. */\nexport function leanTools(lean: LeanProfileConfig, channel?: string): string[] {\n const own = channel ? lean.toolsByChannel?.[channel] : undefined\n const list = own && own.length ? own : (lean.tools ?? [])\n return Array.from(new Set(list.map((t) => t.trim()).filter(Boolean)))\n}\n\n/** The agentx MCP tools a lean session on `channel` lists: the channel's\n * own list when one is set, else the shared `agentxTools` list, plus the\n * tools daemon prompts name (AGENTX_CORE_TOOLS, and the on-demand context\n * tools when `contextOnDemand` is on). Empty means every agentx tool. */\nexport function leanAgentxTools(lean: LeanProfileConfig, channel?: string): string[] {\n const own = channel ? lean.agentxToolsByChannel?.[channel] : undefined\n const list = (own && own.length ? own : (lean.agentxTools ?? [])).map((t) => t.trim()).filter(Boolean)\n if (!list.length) return []\n const kept = [...AGENTX_CORE_TOOLS, ...(lean.contextOnDemand ? AGENTX_ON_DEMAND_TOOLS : []), ...list]\n return Array.from(new Set(kept))\n}\n\n/** `server` told to list only `tools`. Only a stdio server can be told:\n * an http entry is returned as it is and lists every tool. Claude Code\n * merges a stanza's `env` over its own environment (checked on 2.1.291:\n * the server still gets PATH and the daemon's variables), so adding the\n * one variable does not cut the server off from the rest. */\nexport function withAgentxToolList(server: McpServerConfig, tools: string[]): McpServerConfig {\n if (!tools.length || server.type === \"http\") return server\n return { ...server, env: { ...(server.env ?? {}), [AGENTX_MCP_TOOLS_ENV]: tools.join(\",\") } }\n}\n\n/** The profile a channel's sessions start with. Listed channels win;\n * unlisted ones fall back to DEFAULT_LEAN_CHANNELS. */\nexport function resolveSessionProfile(\n config: Partial<SessionProfileConfig> | undefined,\n channel: string,\n tier: string,\n): SessionProfileName {\n if (!LEAN_CAPABLE_TIERS.includes(tier)) return \"full\"\n const listed = config?.profileByChannel?.[channel]\n if (listed) return listed\n return DEFAULT_LEAN_CHANNELS.includes(channel) ? \"lean\" : \"full\"\n}\n\n/** The lean settings with every default filled in. */\nexport function leanConfig(config: Partial<SessionProfileConfig> | undefined): LeanProfileConfig {\n return { ...DEFAULT_LEAN, ...(config?.lean ?? {}) }\n}\n\n/** Whether a lean session's Claude Code reads the workspace itself, so the\n * project CLAUDE.md must not also be appended to the system prompt. */\nexport function leanLoadsWorkspace(lean: LeanProfileConfig): boolean {\n return lean.settingSources.includes(\"project\")\n}\n\n/** The MCP servers a lean session loads: this install's `agentx` tool\n * server plus the workspace .mcp.json entries named in `lean.mcpServers`.\n * An operator-owned .mcp.json is read the same way; only the names decide. */\nexport function leanMcpServers(\n workspace: string,\n lean: LeanProfileConfig,\n agentx: McpServerConfig = agentxToolServer(),\n channel?: string,\n): McpServerMap {\n const declared = readWorkspaceMcp(workspace)\n const kept: McpServerMap = {}\n for (const name of lean.mcpServers) {\n if (declared[name]) kept[name] = declared[name]\n }\n kept.agentx = withAgentxToolList(kept.agentx ?? declared.agentx ?? agentx, leanAgentxTools(lean, channel))\n return kept\n}\n\nfunction readWorkspaceMcp(workspace: string): McpServerMap {\n const file = resolve(workspace, \".mcp.json\")\n if (!existsSync(file)) return {}\n try {\n const parsed = JSON.parse(readFileSync(file, \"utf-8\"))\n const servers = parsed?.mcpServers\n return servers && typeof servers === \"object\" ? servers as McpServerMap : {}\n } catch {\n return {}\n }\n}\n\n/** Flags appended to a lean `claude` spawn. `--strict-mcp-config` makes\n * `--mcp-config` the only MCP source (no user-level connectors); the\n * setting sources drop everything the operator's home directory would\n * otherwise add. Resumed sessions carry them too: the flags describe the\n * process, not the turn. */\nexport function leanClaudeArgs(\n workspace: string,\n lean: LeanProfileConfig,\n agentx: McpServerConfig = agentxToolServer(),\n channel?: string,\n): string[] {\n const mcp = leanMcpServers(workspace, lean, agentx, channel)\n const args = [\n \"--strict-mcp-config\",\n \"--mcp-config\", JSON.stringify({ mcpServers: mcp }),\n \"--setting-sources\", lean.settingSources.join(\",\"),\n ]\n // Only ever next to --strict-mcp-config above: measured on its own,\n // `--tools` tripled the first turn by loading every MCP schema (#615).\n const tools = leanTools(lean, channel)\n if (tools.length) args.push(\"--tools\", withToolSearch(tools).join(\",\"))\n return args\n}\n\n/** Claude Code tool that loads a deferred tool's description on demand. */\nexport const TOOL_SEARCH = \"ToolSearch\"\n\n/** A `--tools` list with ToolSearch kept in it. Claude Code defers MCP\n * tools (names only in the prompt) only while ToolSearch is available; a\n * list without it loads every agentx tool description in full, about 9k\n * tokens on Claude Code 2.1.291, which cancelled what the short list\n * saved (#615). */\nexport function withToolSearch(tools: string[]): string[] {\n return tools.includes(TOOL_SEARCH) ? tools : [...tools, TOOL_SEARCH]\n}\n\n/** Flags appended to a full `claude` spawn: this install's agentx tool\n * server, added to the user-level and workspace MCP servers rather than\n * replacing them (no `--strict-mcp-config`). An `agentx` entry the\n * operator declared in the workspace .mcp.json wins, as in lean sessions.\n * Resumed sessions carry it too: the flag describes the process. */\nexport function fullClaudeArgs(\n workspace: string,\n agentx: McpServerConfig = agentxToolServer(),\n): string[] {\n const declared = readWorkspaceMcp(workspace)\n return [\"--mcp-config\", JSON.stringify({ mcpServers: { agentx: declared.agentx ?? agentx } })]\n}\n\n/** The one line a lean prompt carries in place of the landscape, the chat\n * history and the cross-chat context. Names the tool for each so the\n * agent fetches only what the task turns out to need. */\nexport function onDemandContextNote(channel: string, chatId: string): string {\n return [\n \"[Context on demand]\",\n `Not pushed into this prompt, fetch with agentx MCP tools when the task needs them: ` +\n `the agent landscape (agentx_agents with landscape=true), ` +\n `earlier messages on this chat (agentx_recent with channel=\"${channel}\", chatId=\"${chatId}\"), ` +\n `today's other chats of yours (agentx_recent without chatId), ` +\n `and stored knowledge (agentx_wiki_query).`,\n ].join(\"\\n\")\n}\n\n/** What a lean start changes, for logs and the context benchmark. The\n * tool list is the one passed to `--tools`, ToolSearch included. */\nexport function describeProfile(profile: SessionProfileName, lean: LeanProfileConfig, channel?: string): string {\n if (profile === \"full\") return \"full\"\n const parts = [\n `mcp=${lean.mcpServers.join(\"+\") || \"agentx\"}`,\n `settings=${lean.settingSources.join(\",\") || \"none\"}`,\n lean.contextOnDemand ? \"context=on-demand\" : \"context=pushed\",\n ]\n const tools = leanTools(lean, channel)\n if (tools.length) parts.push(`tools=${withToolSearch(tools).join(\"+\")}`)\n const agentxTools = leanAgentxTools(lean, channel)\n if (agentxTools.length) parts.push(`agentx-tools=${agentxTools.length}`)\n return `lean (${parts.join(\" \")})`\n}\n","import { existsSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- Agent MCP config: per-agent .mcp.json sync ---\n//\n// Centralizes which MCP servers each agent's Claude Code session loads.\n// Without this, operators hand-write `<workspace>/.mcp.json` per agent —\n// painful to keep in sync across 22 workspaces, easy to drift.\n//\n// Ownership model: agentx-managed files carry a top-level\n// `\"_agentxManaged\": true` marker. The Claude Code MCP loader ignores\n// unknown top-level fields, so the marker is invisible to the runtime\n// but lets us safely round-trip without clobbering operator edits.\n//\n// - file missing → write fresh (we own it)\n// - file present + marker is true → overwrite (we own it)\n// - file present + marker missing/false→ skip (operator owns it)\n//\n// Operator can opt out by either deleting the marker line or removing\n// the file entirely after editing — agentx will respect the choice on\n// the next boot.\n\nconst MARKER = \"_agentxManaged\"\n\n/** stdio MCP server — spawned as a child process by Claude Code (and\n * by agentx for orchestrator-tier agents). */\nexport interface McpStdioServerConfig {\n type?: \"stdio\"\n command: string\n args?: string[]\n env?: Record<string, string>\n}\n\n/** HTTP MCP server (Streamable HTTP transport). Claude Code reads this\n * natively when written into .mcp.json. agentx's orchestrator-tier\n * uses src/agent/mcp-client.ts:HttpTransport for the same shape. */\nexport interface McpHttpServerConfig {\n type: \"http\"\n url: string\n headers?: Record<string, string>\n}\n\nexport type McpServerConfig = McpStdioServerConfig | McpHttpServerConfig\n\nexport type McpServerMap = Record<string, McpServerConfig>\n\n/** This install's own MCP server (`agentx serve --stdio`), as a stanza an\n * agent's session can load: the node binary and CLI file this daemon runs\n * from, pointed at this install folder so the server finds the daemon. */\nexport function agentxToolServer(\n proc: { execPath: string; argv: string[]; cwd: () => string } = process,\n): McpStdioServerConfig {\n const cli = proc.argv[1] ? resolve(proc.argv[1]) : \"\"\n const viaNode = /\\.[cm]?js$/.test(cli)\n return viaNode\n ? { type: \"stdio\", command: proc.execPath, args: [cli, \"serve\", \"--stdio\", \"--cwd\", proc.cwd()] }\n : { type: \"stdio\", command: \"agentx\", args: [\"serve\", \"--stdio\", \"--cwd\", proc.cwd()] }\n}\n\n/** `mcp` with this install's tool server added as `agentx`, unless the\n * operator declared their own `agentx` entry or `server` is null. */\nexport function withAgentXToolServer(mcp: McpServerMap, server: McpServerConfig | null): McpServerMap {\n if (!server || mcp.agentx) return mcp\n return { ...mcp, agentx: server }\n}\n\nexport type SyncResult =\n | \"installed\" // wrote a fresh .mcp.json (no prior file)\n | \"updated\" // overwrote an agentx-managed file\n | \"removed\" // deleted an agentx-managed file (config now empty)\n | \"skipped-operator-owned\" // file exists without our marker — left alone\n | \"noop\" // no config + no file — nothing to do\n\nexport function syncMcpToWorkspace(workspacePath: string, mcp: McpServerMap): SyncResult {\n const mcpPath = resolve(workspacePath, \".mcp.json\")\n const fileExists = existsSync(mcpPath)\n const parsed = fileExists ? tryParseJson(mcpPath) : null\n // Three states for an existing file:\n // parsed === null && fileExists → unparseable; treat as operator-\n // owned so we don't clobber a\n // half-saved edit.\n // parsed && marker !== true → operator-owned, skip.\n // parsed && marker === true → ours, safe to rewrite/remove.\n const isManaged = parsed !== null && parsed[MARKER] === true\n if (fileExists && !isManaged) return \"skipped-operator-owned\"\n\n const hasConfig = Object.keys(mcp).length > 0\n if (!hasConfig && !fileExists) return \"noop\"\n if (!hasConfig && isManaged) {\n unlinkSync(mcpPath)\n return \"removed\"\n }\n\n const next = { [MARKER]: true, mcpServers: mcp }\n writeFileSync(mcpPath, JSON.stringify(next, null, 2) + \"\\n\")\n return fileExists ? \"updated\" : \"installed\"\n}\n\nfunction tryParseJson(path: string): any | null {\n try { return JSON.parse(readFileSync(path, \"utf-8\")) } catch { return null }\n}\n","import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve, relative } from \"path\"\nimport { procedureMetaSchema, type Procedure, type ProcedureMeta } from \"./types\"\n\n// --- ProcedureStore ---\n// Plain-file persistence (same convention as .agentx/wiki/ and .agentx/graph/).\n// One markdown file per procedure. Frontmatter = yaml-ish name:value lines\n// (arrays are JSON). This intentionally avoids pulling in a yaml lib — the\n// surface is small and the wiki store already uses the same hand-parser.\n\nexport interface ProcedureStoreOptions {\n baseDir?: string\n}\n\nfunction parseFrontmatter(raw: string): { meta: Record<string, any>; body: string } {\n const m = raw.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/)\n if (!m) return { meta: {}, body: raw }\n const meta: Record<string, any> = {}\n for (const line of m[1].split(\"\\n\")) {\n const kv = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*):\\s*(.*)$/)\n if (!kv) continue\n const key = kv[1]\n let val = kv[2].trim()\n if (val.startsWith(\"[\") && val.endsWith(\"]\")) {\n try { meta[key] = JSON.parse(val); continue } catch { /* fallthrough */ }\n meta[key] = val.slice(1, -1).split(\",\").map(s => s.trim().replace(/^\"(.*)\"$/, \"$1\")).filter(Boolean)\n continue\n }\n if (val.startsWith('\"') && val.endsWith('\"')) val = val.slice(1, -1)\n meta[key] = val\n }\n return { meta, body: m[2] }\n}\n\nfunction serializeFrontmatter(meta: ProcedureMeta): string {\n const lines: string[] = [\"---\"]\n lines.push(`id: ${meta.id}`)\n lines.push(`title: \"${meta.title.replace(/\"/g, '\\\\\"')}\"`)\n lines.push(`trigger: \"${meta.trigger.replace(/\"/g, '\\\\\"')}\"`)\n if (meta.inputs?.length) lines.push(`inputs: ${JSON.stringify(meta.inputs)}`)\n if (meta.expected) lines.push(`expected: \"${meta.expected.replace(/\"/g, '\\\\\"')}\"`)\n if (meta.kpis?.length) lines.push(`kpis: ${JSON.stringify(meta.kpis)}`)\n if (meta.owner) lines.push(`owner: ${meta.owner}`)\n if (meta.tags?.length) lines.push(`tags: ${JSON.stringify(meta.tags)}`)\n lines.push(`status: ${meta.status}`)\n if (meta.source) lines.push(`source: \"${meta.source.replace(/\"/g, '\\\\\"')}\"`)\n if (meta.evidence?.length) lines.push(`evidence: ${JSON.stringify(meta.evidence)}`)\n if (meta.related?.length) lines.push(`related: ${JSON.stringify(meta.related)}`)\n if (meta.created) lines.push(`created: ${meta.created}`)\n if (meta.updated) lines.push(`updated: ${meta.updated}`)\n lines.push(\"---\")\n return lines.join(\"\\n\")\n}\n\nexport class ProcedureStore {\n readonly baseDir: string\n\n constructor(opts: ProcedureStoreOptions = {}) {\n this.baseDir = opts.baseDir ?? resolve(process.cwd(), \".agentx/procedures\")\n mkdirSync(this.baseDir, { recursive: true })\n }\n\n private pathFor(id: string): string {\n return resolve(this.baseDir, `${id}.md`)\n }\n\n private get draftsDir(): string {\n return resolve(this.baseDir, \"_drafts\")\n }\n\n private draftPathFor(id: string): string {\n return resolve(this.draftsDir, `${id}.md`)\n }\n\n private parseFile(full: string): Procedure | null {\n const raw = readFileSync(full, \"utf-8\")\n const { meta, body } = parseFrontmatter(raw)\n const parsed = procedureMetaSchema.safeParse({\n ...meta,\n inputs: meta.inputs || [],\n kpis: meta.kpis || [],\n tags: meta.tags || [],\n related: meta.related || [],\n evidence: meta.evidence || [],\n })\n if (!parsed.success) return null\n return { meta: parsed.data, body: body.trim(), path: relative(this.baseDir, full) }\n }\n\n private listDir(dir: string): Procedure[] {\n if (!existsSync(dir)) return []\n const out: Procedure[] = []\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\") || entry.name.startsWith(\"_\")) continue\n const proc = this.parseFile(resolve(dir, entry.name))\n if (proc) out.push(proc)\n }\n return out.sort((a, b) => a.meta.id.localeCompare(b.meta.id))\n }\n\n list(): Procedure[] {\n return this.listDir(this.baseDir)\n }\n\n listDrafts(): Procedure[] {\n return this.listDir(this.draftsDir)\n }\n\n get(id: string): Procedure | null {\n const p = this.pathFor(id)\n if (!existsSync(p)) return null\n return this.parseFile(p)\n }\n\n getDraft(id: string): Procedure | null {\n const p = this.draftPathFor(id)\n if (!existsSync(p)) return null\n return this.parseFile(p)\n }\n\n add(meta: ProcedureMeta, body: string): Procedure {\n const parsed = procedureMetaSchema.parse({\n ...meta,\n created: meta.created || new Date().toISOString().slice(0, 10),\n updated: new Date().toISOString().slice(0, 10),\n })\n const p = this.pathFor(parsed.id)\n if (existsSync(p)) throw new Error(`Procedure already exists: ${parsed.id}`)\n writeFileSync(p, serializeFrontmatter(parsed) + \"\\n\\n\" + body.trim() + \"\\n\")\n return { meta: parsed, body: body.trim(), path: relative(this.baseDir, p) }\n }\n\n update(id: string, patch: Partial<ProcedureMeta>, newBody?: string): Procedure {\n const existing = this.get(id)\n if (!existing) throw new Error(`Procedure not found: ${id}`)\n const merged = procedureMetaSchema.parse({\n ...existing.meta,\n ...patch,\n id: existing.meta.id, // immutable\n updated: new Date().toISOString().slice(0, 10),\n })\n const body = newBody !== undefined ? newBody : existing.body\n writeFileSync(this.pathFor(id), serializeFrontmatter(merged) + \"\\n\\n\" + body.trim() + \"\\n\")\n return { meta: merged, body: body.trim(), path: existing.path }\n }\n\n addDraft(meta: ProcedureMeta, body: string): Procedure {\n const parsed = procedureMetaSchema.parse({\n ...meta,\n status: \"draft\",\n created: meta.created || new Date().toISOString().slice(0, 10),\n updated: new Date().toISOString().slice(0, 10),\n })\n if (this.get(parsed.id)) throw new Error(`Procedure already exists: ${parsed.id}`)\n const p = this.draftPathFor(parsed.id)\n if (existsSync(p)) throw new Error(`Draft already exists: ${parsed.id}`)\n mkdirSync(this.draftsDir, { recursive: true })\n writeFileSync(p, serializeFrontmatter(parsed) + \"\\n\\n\" + body.trim() + \"\\n\")\n return { meta: parsed, body: body.trim(), path: relative(this.baseDir, p) }\n }\n\n /** Move a draft into the active set (status: active). */\n promoteDraft(id: string): Procedure {\n const draft = this.getDraft(id)\n if (!draft) throw new Error(`Draft not found: ${id}`)\n if (this.get(id)) throw new Error(`Procedure already exists: ${id}`)\n const merged = procedureMetaSchema.parse({\n ...draft.meta,\n status: \"active\",\n updated: new Date().toISOString().slice(0, 10),\n })\n writeFileSync(this.pathFor(id), serializeFrontmatter(merged) + \"\\n\\n\" + draft.body + \"\\n\")\n unlinkSync(this.draftPathFor(id))\n return { meta: merged, body: draft.body, path: relative(this.baseDir, this.pathFor(id)) }\n }\n\n /** Park a draft under _drafts/_rejected/ so the miner never re-drafts it. */\n rejectDraft(id: string): void {\n const p = this.draftPathFor(id)\n if (!existsSync(p)) throw new Error(`Draft not found: ${id}`)\n const rejectedDir = resolve(this.draftsDir, \"_rejected\")\n mkdirSync(rejectedDir, { recursive: true })\n renameSync(p, resolve(rejectedDir, `${id}.md`))\n }\n\n isRejected(id: string): boolean {\n return existsSync(resolve(this.draftsDir, \"_rejected\", `${id}.md`))\n }\n\n deprecate(id: string): Procedure {\n return this.update(id, { status: \"deprecated\" })\n }\n}\n","import { z } from \"zod\"\n\n// --- Procedures (foundation) ---\n//\n// Karpathy-style SOPs as first-class artifacts. Each procedure is a small\n// typed doc with a trigger, inputs, ordered steps, expected output, and KPIs.\n// v1 ships definition + list/add/show — the \"delta extraction\" flow\n// (agents emit one-line deltas when they run a procedure, cheap O(runs)\n// instead of O(entries × articles)) is deferred.\n//\n// Shape on disk (.agentx/procedures/):\n// <slug>.md — frontmatter (id/title/trigger/inputs/expected/kpis)\n// + markdown body (steps + notes)\n// _runs/<run-id>.json — future: one per procedure run, holds deltas\n\nexport const procedureMetaSchema = z.object({\n id: z.string().regex(/^[a-z0-9][a-z0-9_-]*$/, \"procedure id must be lower-kebab\"),\n title: z.string().min(1),\n trigger: z.string().min(1, \"procedures must describe when they apply\"),\n inputs: z.array(z.string()).default([]),\n expected: z.string().optional(),\n kpis: z.array(z.string()).default([]),\n owner: z.string().optional(),\n tags: z.array(z.string()).default([]),\n /**\n * Single lifecycle axis (deliberately — workflows' status/state split proved\n * confusing). draft = mined/unreviewed (lives in _drafts/), active = the\n * matcher/injector will surface it, deprecated = kept but never surfaced.\n */\n status: z.enum([\"draft\", \"active\", \"deprecated\"]).default(\"active\"),\n /** Provenance: \"mined:<clusterKey>\" for extracted procedures, \"manual\" otherwise. */\n source: z.string().optional(),\n /** Trace task ids (ULIDs) the miner distilled this procedure from. */\n evidence: z.array(z.string()).default([]),\n created: z.string().optional(),\n updated: z.string().optional(),\n /** Related procedures (ids) or wiki articles (titles). Drives the graph. */\n related: z.array(z.string()).default([]),\n})\n\nexport type ProcedureMeta = z.infer<typeof procedureMetaSchema>\n\nexport interface Procedure {\n meta: ProcedureMeta\n /** Markdown body — typically a \"## Steps\" section and optional notes. */\n body: string\n /** Relative path within the procedures root, e.g. \"deploy-peer.md\". */\n path: string\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\nimport { z } from \"zod\"\n\n// --- Candidate ledger ---\n// Recurrence counting for the procedure miner. A \"candidate\" is a recurring\n// activity pattern (cluster key) that hasn't yet earned procedure status.\n// Plain JSON file (.agentx/procedures/_candidates.json), same idempotency\n// idea as the wiki promotion ledger: a scan watermark for cheap incremental\n// runs plus per-candidate task-id sets so re-scans never double count.\n\nexport const candidateSchema = z.object({\n /** Cluster key: `<channel>:<intentShape>:<actionSkeleton>`. */\n key: z.string(),\n count: z.number().int().min(0).default(0),\n firstSeen: z.string(),\n lastSeen: z.string(),\n /** Trace task ids already counted for this key (dedupes re-scans). */\n taskIds: z.array(z.string()).default([]),\n /** One representative user message, for dry-run review + LLM sampling. */\n sampleMessage: z.string().default(\"\"),\n /**\n * counting — accumulating occurrences\n * drafted — a procedure draft was written; stop counting\n * promoted — the draft became an active procedure\n * rejected — operator rejected the draft; never re-draft this key\n */\n status: z.enum([\"counting\", \"drafted\", \"promoted\", \"rejected\"]).default(\"counting\"),\n /** Procedure id once drafted. */\n procedureId: z.string().optional(),\n})\n\nexport type Candidate = z.infer<typeof candidateSchema>\n\nexport const candidateLedgerSchema = z.object({\n /** ISO timestamp: traces started before this have already been scanned. */\n minedThrough: z.string().optional(),\n candidates: z.array(candidateSchema).default([]),\n})\n\nexport type CandidateLedger = z.infer<typeof candidateLedgerSchema>\n\nexport function candidatesPath(baseDir: string): string {\n return resolve(baseDir, \"_candidates.json\")\n}\n\nexport function readCandidates(baseDir: string): CandidateLedger {\n const p = candidatesPath(baseDir)\n if (!existsSync(p)) return { candidates: [] }\n try {\n const parsed = candidateLedgerSchema.safeParse(JSON.parse(readFileSync(p, \"utf-8\")))\n if (parsed.success) return parsed.data\n } catch { /* corrupt ledger — start fresh rather than crash the miner */ }\n return { candidates: [] }\n}\n\nexport function writeCandidates(baseDir: string, ledger: CandidateLedger): void {\n const p = candidatesPath(baseDir)\n mkdirSync(dirname(p), { recursive: true })\n writeFileSync(p, JSON.stringify(ledger, null, 2) + \"\\n\")\n}\n\n/**\n * Record one episode against a cluster key. Returns the candidate.\n * Episodes already counted (same taskId) are ignored, so re-scanning an\n * overlapping window is safe.\n */\nexport function upsertEpisode(\n ledger: CandidateLedger,\n key: string,\n taskId: string,\n timestamp: string,\n sampleMessage: string,\n): Candidate {\n let cand = ledger.candidates.find((c) => c.key === key)\n if (!cand) {\n cand = {\n key,\n count: 0,\n firstSeen: timestamp,\n lastSeen: timestamp,\n taskIds: [],\n sampleMessage,\n status: \"counting\",\n }\n ledger.candidates.push(cand)\n }\n if (cand.taskIds.includes(taskId)) return cand\n cand.taskIds.push(taskId)\n cand.count += 1\n if (timestamp < cand.firstSeen) cand.firstSeen = timestamp\n if (timestamp > cand.lastSeen) cand.lastSeen = timestamp\n if (!cand.sampleMessage) cand.sampleMessage = sampleMessage\n return cand\n}\n\n/** Candidates that have recurred enough to be distilled into a draft. */\nexport function readyCandidates(ledger: CandidateLedger, minOccurrences: number): Candidate[] {\n return ledger.candidates\n .filter((c) => c.status === \"counting\" && c.count >= minOccurrences)\n .sort((a, b) => b.count - a.count)\n}\n\nexport function markCandidate(\n ledger: CandidateLedger,\n key: string,\n status: Candidate[\"status\"],\n procedureId?: string,\n): void {\n const cand = ledger.candidates.find((c) => c.key === key)\n if (!cand) return\n cand.status = status\n if (procedureId) cand.procedureId = procedureId\n}\n","import type { Procedure } from \"./types\"\n\n// --- Procedure matching ---\n// Cheap keyword-overlap scoring so an incoming task can be checked against\n// known procedures without an LLM call (same posture as workflows/matcher).\n// Only `active` procedures are ever surfaced — drafts await review and\n// deprecated ones are history.\n\nexport interface ProcedureMatch {\n procedure: Procedure\n score: number\n reasons: string[]\n}\n\nfunction words(text: string): Set<string> {\n return new Set(\n text.toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \" \")\n .split(/\\s+/)\n .filter((w) => w.length > 3),\n )\n}\n\nfunction overlap(a: Set<string>, b: Set<string>): number {\n if (a.size === 0 || b.size === 0) return 0\n let n = 0\n for (const w of a) if (b.has(w)) n++\n return n / Math.max(a.size, b.size)\n}\n\nexport function matchProcedures(\n message: string,\n procedures: Procedure[],\n opts: { limit?: number; minScore?: number } = {},\n): ProcedureMatch[] {\n const limit = Math.max(1, opts.limit ?? 3)\n const minScore = opts.minScore ?? 0.2\n const messageWords = words(message)\n const matches: ProcedureMatch[] = []\n\n for (const p of procedures) {\n if (p.meta.status !== \"active\") continue\n const reasons: string[] = []\n // Trigger text is the primary signal — it literally describes when the\n // procedure applies. Title/tags/inputs corroborate.\n const triggerScore = overlap(messageWords, words(p.meta.trigger))\n if (triggerScore > 0) reasons.push(\"trigger\")\n const metaText = [p.meta.id, p.meta.title, ...p.meta.tags, ...p.meta.inputs].join(\" \")\n const metaScore = overlap(messageWords, words(metaText))\n if (metaScore > 0) reasons.push(\"title/tags\")\n const score = Math.min(1, triggerScore * 0.7 + metaScore * 0.5)\n if (score >= minScore) matches.push({ procedure: p, score, reasons })\n }\n\n return matches.sort((a, b) => b.score - a.score).slice(0, limit)\n}\n\n/** Render matched procedures as an instruction block for agent context.\n * Kept terse — this rides inside a token-budgeted context layer. */\nexport function renderProcedureContext(matches: ProcedureMatch[]): string {\n const sections = matches.map((m) => {\n const p = m.procedure\n const lines = [`[Known procedure: ${p.meta.title}]`, p.meta.trigger]\n if (p.meta.inputs.length) lines.push(`Inputs: ${p.meta.inputs.join(\", \")}`)\n lines.push(p.body)\n if (p.meta.expected) lines.push(`Done when: ${p.meta.expected}`)\n lines.push(`(details: agentx procedure show ${p.meta.id})`)\n return lines.join(\"\\n\")\n })\n return [\n \"The user's request matches an established procedure. Follow its steps unless the user asks otherwise — they encode the known-good path.\",\n ...sections,\n ].join(\"\\n\\n\")\n}\n","import { askSeat, decisionsRuntime, getSeatMode } from \"@/decisions/seat\"\nimport { TURN_PROGRESS_SEAT, turnProgressQuestions, turnProgressState, type TurnStep } from \"@/decisions/seats/turn-progress\"\nimport {\n APPROVAL_BINDING_SEAT,\n TURN_HANDOFF_SEAT,\n approvalBindingQuestions,\n approvalBindingState,\n endsWithQuestion,\n isBareApproval,\n isShortReply,\n turnHandoffQuestions,\n turnHandoffState,\n} from \"@/decisions/seats/turn-handoff\"\n\n// Wiring for the three turn seats (see src/decisions/seats/turn-*.ts).\n// Every call here is fire-and-forget: a seat must never add latency to a\n// turn or fail one. All three are shadow seats, so nothing here changes\n// what the agent does.\n\nconst CHECKPOINT_MS = 5 * 60_000\nconst REPEAT_THRESHOLD = 3\nconst RECENT_STEPS = 12\nconst HANDOFF_LABEL_WINDOW_MS = 24 * 60 * 60_000\n\n/** Channels with no person on the other end: nobody to hand a turn back to. */\nconst AUTOMATED_CHANNELS = new Set([\"cron\", \"heartbeat\", \"bench\", \"a2a\", \"business\"])\n\nexport interface TurnWatch {\n observe(event: any): void\n stop(): void\n}\n\nconst NOOP_WATCH: TurnWatch = { observe() {}, stop() {} }\n\nexport function startTurnWatch(opts: {\n agent: string\n request: string\n taskId: string\n budgetMinutes: number\n checkpointMs?: number\n now?: () => number\n}): TurnWatch {\n if (getSeatMode(TURN_PROGRESS_SEAT) === \"off\") return NOOP_WATCH\n const now = opts.now ?? Date.now\n const started = now()\n const steps: TurnStep[] = []\n const byToolUseId = new Map<string, TurnStep>()\n const calls = new Map<string, number>()\n const askedSignatures = new Set<string>()\n let stepsAtLastAsk = 0\n let inFlight = false\n\n const repeated = () => {\n let best: { call: string; times: number } | undefined\n for (const [call, times] of calls) if (times > 1 && (!best || times > best.times)) best = { call, times }\n return best\n }\n\n const ask = (trigger: \"checkpoint\" | \"repetition\") => {\n if (inFlight) return\n inFlight = true\n stepsAtLastAsk = steps.length\n const state = turnProgressState({\n agent: opts.agent,\n request: opts.request,\n elapsedMinutes: (now() - started) / 60_000,\n budgetMinutes: opts.budgetMinutes,\n steps: steps.slice(-RECENT_STEPS),\n repeated: repeated(),\n trigger,\n })\n void askSeat(TURN_PROGRESS_SEAT, state, turnProgressQuestions, {\n links: [{ kind: \"task\", id: opts.taskId }],\n features: { agent: opts.agent, trigger },\n }).finally(() => { inFlight = false })\n }\n\n const timer = setInterval(() => {\n if (steps.length > stepsAtLastAsk) ask(\"checkpoint\")\n }, opts.checkpointMs ?? CHECKPOINT_MS)\n timer.unref?.()\n\n return {\n observe(event: any) {\n const blocks = Array.isArray(event?.message?.content) ? event.message.content : []\n if (event?.type === \"assistant\") {\n for (const block of blocks) {\n if (block?.type !== \"tool_use\") continue\n const input = block.input ? JSON.stringify(block.input) : \"\"\n const step: TurnStep = { tool: block.name || \"tool\", input }\n steps.push(step)\n if (typeof block.id === \"string\") byToolUseId.set(block.id, step)\n const signature = `${step.tool}(${input})`\n const times = (calls.get(signature) ?? 0) + 1\n calls.set(signature, times)\n if (times >= REPEAT_THRESHOLD && !askedSignatures.has(signature)) {\n askedSignatures.add(signature)\n ask(\"repetition\")\n }\n }\n } else if (event?.type === \"user\") {\n for (const block of blocks) {\n if (block?.type !== \"tool_result\") continue\n const step = byToolUseId.get(block.tool_use_id)\n if (!step) continue\n step.result = Array.isArray(block.content)\n ? block.content.map((b: any) => (typeof b?.text === \"string\" ? b.text : \"\")).join(\"\")\n : typeof block.content === \"string\" ? block.content : \"\"\n if (block.is_error) step.error = true\n }\n }\n },\n stop() { clearInterval(timer) },\n }\n}\n\n/** turn-handoff calls awaiting the user's next message, by conversation. */\nconst pendingHandoffs = new Map<string, { callId: string; at: number }>()\nconst conversationKey = (agent: string, channel: string, chatId: string) => `${agent}\\u0000${channel}\\u0000${chatId}`\n\nexport function onAgentReply(opts: {\n agent: string\n channel: string\n chatId: string\n request: string\n reply: string\n taskId: string\n}): void {\n if (AUTOMATED_CHANNELS.has(opts.channel) || !endsWithQuestion(opts.reply)) return\n const key = conversationKey(opts.agent, opts.channel, opts.chatId)\n void askSeat(TURN_HANDOFF_SEAT, turnHandoffState(opts), turnHandoffQuestions, {\n links: [{ kind: \"task\", id: opts.taskId }],\n features: { agent: opts.agent, channel: opts.channel },\n }).then(result => {\n if (result?.callId) pendingHandoffs.set(key, { callId: result.callId, at: Date.now() })\n })\n}\n\nexport function onUserMessage(opts: {\n agent: string\n channel: string\n chatId: string\n message: string\n /** The agent's last reply in this conversation, before this message. */\n previousReply?: string\n taskId: string\n}): void {\n if (AUTOMATED_CHANNELS.has(opts.channel)) return\n const key = conversationKey(opts.agent, opts.channel, opts.chatId)\n\n // Outcome label for the handoff question the agent ended on.\n const pending = pendingHandoffs.get(key)\n if (pending) {\n pendingHandoffs.delete(key)\n const store = decisionsRuntime().store\n if (store && Date.now() - pending.at < HANDOFF_LABEL_WINDOW_MS) {\n try {\n store.label(pending.callId, \"userReply\", isBareApproval(opts.message) ? \"approved\" : \"redirected\", {\n kind: \"outcome\",\n labeledBy: \"next-user-message\",\n })\n } catch { /* observability never breaks the caller */ }\n }\n }\n\n if (!opts.previousReply || !isShortReply(opts.message) || !endsWithQuestion(opts.previousReply)) return\n void askSeat(\n APPROVAL_BINDING_SEAT,\n approvalBindingState({ agent: opts.agent, channel: opts.channel, message: opts.message, previousReply: opts.previousReply }),\n approvalBindingQuestions,\n { links: [{ kind: \"task\", id: opts.taskId }], features: { agent: opts.agent, channel: opts.channel } },\n )\n}\n\nexport function resetTurnSeatsForTesting(): void {\n pendingHandoffs.clear()\n}\n","import { choice } from \"../questions\"\nimport type { AnswersFor, ChoiceAnswer, StateValue } from \"../types\"\n\n// The turn-progress seat: is a long agent turn getting somewhere?\n//\n// A turn has one hard cap, the agent's maxExecutionMinutes. It is the\n// safety net and stays authoritative: it works when a decision backend is\n// slow or down. What the cap cannot tell apart is a long turn doing real\n// work (an end-to-end run, a big refactor) from one repeating the same\n// failing step. Both look identical to a timer, and both end the same way\n// at the cap: the work so far is lost and the user gets an error.\n//\n// The trigger is deliberately mechanical and cheap:\n// - a checkpoint every few minutes of a running turn, and\n// - immediately, once per signature, when one tool call repeats\n// several times in a turn (the clearest looping signal there is).\n//\n// Shadow only for now. Every row is linked to its task, so the eventual\n// promotion (extend a progressing turn, stop a looping one early) can be\n// judged against how those turns actually ended.\n\nexport const TURN_PROGRESS_SEAT = \"turn-progress\"\n\nexport const turnProgressQuestions = {\n progress: choice(\n {\n progressing:\n \"Recent steps move the task forward: new files, commands or checks, results that differ from earlier ones, or a long step whose output shows it advancing.\",\n looping:\n \"The agent repeats the same or near-identical step (same command, same check, same edit retried with small tweaks) and gets the same failure or result each time.\",\n blocked:\n \"Progress depends on something the agent cannot change from here: missing access or credentials, a service that is down, or a decision only the user can make.\",\n },\n \"How the agent's current turn is going, judged from its most recent steps.\",\n ),\n}\n\nexport type TurnProgressAnswers = AnswersFor<typeof turnProgressQuestions>\n\nexport interface TurnStep {\n tool: string\n input: string\n result?: string\n error?: boolean\n}\n\nexport interface TurnProgressInput {\n agent: string\n request: string\n elapsedMinutes: number\n budgetMinutes: number\n steps: TurnStep[]\n /** Most repeated tool call in the turn, when it repeats at all. */\n repeated?: { call: string; times: number }\n trigger: \"checkpoint\" | \"repetition\"\n}\n\nconst clip = (text: string, max: number) => (text.length > max ? `${text.slice(0, max)}…` : text)\n\nexport function turnProgressState(input: TurnProgressInput): StateValue {\n return {\n agent: input.agent,\n request: clip(input.request, 1500),\n elapsedMinutes: Math.round(input.elapsedMinutes),\n budgetMinutes: input.budgetMinutes,\n trigger: input.trigger,\n recentSteps: input.steps.map(s => ({\n tool: s.tool,\n input: clip(s.input, 300),\n ...(s.result !== undefined ? { result: clip(s.result, 300) } : {}),\n ...(s.error ? { failed: true } : {}),\n })),\n ...(input.repeated ? { mostRepeatedCall: input.repeated } : {}),\n }\n}\n\nexport function turnProgressVerdict(answers: TurnProgressAnswers): string | null {\n return (answers.progress as ChoiceAnswer)?.choice ?? null\n}\n","import { choice, noul } from \"../questions\"\nimport type { AnswersFor, StateValue } from \"../types\"\n\n// Two seats around the moment an agent hands the conversation back.\n//\n// turn-handoff: the agent ended its turn by asking the user something.\n// Some of those questions are the point (a public change, money, a third\n// party). Others ask permission for the next local step of the very task\n// the user asked for, and cost a full round trip for a \"yes go ahead\".\n// Measured over two weeks of task history: 7% of turns ended in a question,\n// and 17% of those got a bare approval back.\n//\n// approval-binding: the user's message is short and the agent's previous\n// reply was a question. \"yes go ahead\" means nothing on its own; routing\n// and context selection judged it as a trivial acknowledgement and moved an\n// approved coding turn to the cheap model. Binding the approval to what it\n// approves is the fix this seat measures.\n//\n// Both are shadow only. turn-handoff gets a free outcome label: the user's\n// next message on the same conversation either approves or redirects.\n\nexport const TURN_HANDOFF_SEAT = \"turn-handoff\"\nexport const APPROVAL_BINDING_SEAT = \"approval-binding\"\n\nexport const turnHandoffQuestions = {\n nextStep: choice(\n {\n proceed:\n \"The question asks permission for a local, reversible next step that is already inside what the user asked for, such as writing or testing code in the working copy, reading, or running checks.\",\n ask_outward:\n \"The next step changes something outside the working copy or is hard to undo: pushing, publishing, merging, deploying, deleting, spending money, or messaging someone other than the user.\",\n ask_ambiguous:\n \"The agent cannot tell what the user wants: several different goals are plausible, or the user must choose between options with different results.\",\n },\n \"What the agent should do with the question it ended its turn on.\",\n ),\n}\n\nexport const approvalBindingQuestions = {\n approves: noul(\n \"The user's message approves the plan or step the agent proposed at the end of its previous reply.\",\n {\n true: 'The message accepts the proposal, including terse replies such as \"yes\", \"go ahead\" or \"ok do it\", possibly with small additions that do not change the plan.',\n false: \"The message declines, changes the plan, asks something new, or answers a different question than the one the agent asked.\",\n },\n ),\n}\n\nexport type TurnHandoffAnswers = AnswersFor<typeof turnHandoffQuestions>\nexport type ApprovalBindingAnswers = AnswersFor<typeof approvalBindingQuestions>\n\nconst clip = (text: string, max: number) => (text.length > max ? `${text.slice(0, max)}…` : text)\nconst tail = (text: string, max: number) => (text.length > max ? `…${text.slice(-max)}` : text)\n\nconst QUESTION_TAIL = /(should i|want me to|shall i|do you want|which (one|option|task)|let me know)[^\\n]*$/i\n\n/** The reply hands the turn back with a question. */\nexport function endsWithQuestion(reply: string): boolean {\n const text = reply.trim()\n if (!text) return false\n if (/\\?\\s*(\\*\\*)?\\s*$/.test(text)) return true\n return QUESTION_TAIL.test(text.split(\"\\n\").slice(-3).join(\" \"))\n}\n\nconst APPROVAL = /^\\s*(yes|yep|yeah|ok(ay)?|sure|go( ahead)?|proceed|do it|please do|confirmed?|approved?|go-ahead|let'?s go|oui|نعم)\\b/i\n\n/** A short message that reads as a plain approval. Conservative on purpose:\n * it is used as the outcome label for turn-handoff. */\nexport function isBareApproval(message: string): boolean {\n return message.trim().length <= 160 && APPROVAL.test(message)\n}\n\n/** A message short enough that it only makes sense against the agent's\n * previous reply. */\nexport function isShortReply(message: string): boolean {\n return message.trim().length <= 160\n}\n\nexport function turnHandoffState(input: { agent: string; channel: string; request: string; reply: string }): StateValue {\n return {\n agent: input.agent,\n channel: input.channel,\n request: clip(input.request, 1500),\n agentReplyEnding: tail(input.reply, 1200),\n }\n}\n\nexport function approvalBindingState(input: { agent: string; channel: string; message: string; previousReply: string }): StateValue {\n return {\n agent: input.agent,\n channel: input.channel,\n userMessage: input.message,\n agentPreviousReplyEnding: tail(input.previousReply, 1200),\n }\n}\n","// A task's due time, as the caller gave it in the run's context (#1030).\n// Its own file so the registry can read it without loading the watchers.\n\n/** `context.dueAt` as epoch ms: a number of ms, or an ISO date. */\nexport function dueAtOf(ctx: Record<string, unknown> | undefined | null): number | undefined {\n const v = ctx?.dueAt\n if (typeof v === \"number\" && Number.isFinite(v) && v > 0) return v\n if (typeof v === \"string\" && v.trim()) {\n const t = Date.parse(v)\n if (Number.isFinite(t)) return t\n }\n return undefined\n}\n","/**\n * Settle with `work`, or reject `graceMs` after `signal` aborts — whichever\n * comes first. A run step that ignores cancellation (a hung fetch, a lock\n * that never frees) must not hold the run's slot forever: the rejection\n * lets the run's own cleanup fire even though `work` never settles.\n *\n * With `graceMs` 0 an already-aborted signal rejects at once. A step that\n * owns a subprocess gets a grace so its own abort handling can reap the\n * child before the slot is handed to the next run.\n */\nexport function untilAborted<T>(work: Promise<T>, signal: AbortSignal, graceMs = 0): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined\n const onAbort = () => {\n timer = setTimeout(() => reject(abortReason(signal)), graceMs)\n }\n const done = () => {\n signal.removeEventListener(\"abort\", onAbort)\n if (timer) clearTimeout(timer)\n }\n work.then(\n (value) => { done(); resolve(value) },\n (error) => { done(); reject(error) },\n )\n if (signal.aborted) onAbort()\n else signal.addEventListener(\"abort\", onAbort, { once: true })\n })\n}\n\nexport function abortReason(signal: AbortSignal): Error {\n const reason = signal.reason\n if (reason instanceof Error) return reason\n return new Error(typeof reason === \"string\" ? reason : \"task cancelled\")\n}\n\n/** Thrown by `withBudget` when a pre-spawn step runs past its own limit.\n * The step's caller decides what the run does without that step. */\nexport class StepBudgetExceeded extends Error {\n constructor(readonly step: string, readonly budgetMs: number) {\n super(`step \"${step}\" exceeded its ${Math.round(budgetMs / 1000)}s budget`)\n this.name = \"StepBudgetExceeded\"\n }\n}\n\n/**\n * Race `work` against a per-step limit. The run's pre-spawn deadline\n * (minutes) was the only bound on preparation steps, so a step whose own\n * timeout failed to fire, such as a request that never settled, held the\n * whole run until that deadline and the message was dropped. The work\n * keeps running in the background when it loses; nothing waits for it.\n */\nexport function withBudget<T>(work: Promise<T>, budgetMs: number, step: string): Promise<T> {\n if (!(budgetMs > 0)) return work\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new StepBudgetExceeded(step, budgetMs)), budgetMs)\n timer.unref?.()\n work.then(\n (value) => { clearTimeout(timer); resolve(value) },\n (error) => { clearTimeout(timer); reject(error) },\n )\n })\n}\n","import { execFileSync, spawn as nodeSpawn, type ChildProcess } from \"child_process\"\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { dirname } from \"path\"\n\n// --- Claude cloud sessions (#622) ---\n//\n// An agent can run a GitHub coding task as a Claude cloud session instead of\n// a local `claude -p` run. The cloud session works on a clone of the\n// repository in a remote VM, draws on the account's cloud session credit,\n// and delivers its result as a pull request — not as a text reply.\n//\n// Off by default. Both the agent (`agents.<id>.cloudSessions.enabled`) and\n// the channel (`channels.github.cloudSessions`) must opt in. Only tasks that\n// arrive from the GitHub channel for an issue or pull request of a repository\n// this node has a checkout of are sent to the cloud; everything else runs\n// locally as before. A launch that fails falls back to a local run and logs\n// the reason. While a cloud session is open for an issue, no local run\n// starts for that issue: follow-up comments are forwarded to the session.\n//\n// `claude --cloud` needs a TTY (it prints the new session's id and URL, then\n// exits), so it is spawned through a small python `pty.spawn` wrapper.\n// `script(1)` fails with tcgetattr on some systems; python's pty module is on\n// every macOS and nearly every Linux install.\n\n/** Per-agent settings, `agents.<id>.cloudSessions` (daemon/config.ts). */\nexport interface CloudSessionsAgentSettings {\n enabled: boolean\n /** Launches per calendar day across the agent's repositories; 0 = no cap. */\n maxPerDay: number\n /** How long a launched session counts as open, blocking local runs for its issue. */\n openHours: number\n /** How long `claude --cloud` may take to print the session before the launch is given up. */\n launchTimeoutSeconds: number\n}\n\nexport const CLOUD_SESSIONS_DEFAULTS: CloudSessionsAgentSettings = {\n enabled: false,\n maxPerDay: 0,\n openHours: 24,\n launchTimeoutSeconds: 120,\n}\n\n/** A session this node launched, kept until it is no longer open. */\nexport interface OpenCloudSession {\n /** \"owner/repo\" as GitHub spells it. */\n repo: string\n /** Issue or pull request number. */\n number: number\n kind: \"issue\" | \"pull\"\n sessionId: string\n url: string\n agentId: string\n /** Epoch ms of the launch. */\n startedAt: number\n /** The checkout the session was launched from; follow-ups run there too. */\n cwd?: string\n /** Epoch ms of the last follow-up forwarded to it. */\n lastMessageAt?: number\n /** Trace id of the run that launched it (task_traces.task_id). */\n traceId?: string\n}\n\n// --- GitHub repository detection ---\n\n/** \"owner/repo\" from a git remote URL on github.com, or undefined for any\n * other host or shape. Accepts https://, ssh://, git@ and bare host forms. */\nexport function parseGitHubRemote(url: string | undefined): string | undefined {\n if (!url) return undefined\n const trimmed = url.trim()\n const m = trimmed.match(/^(?:(?:https?|ssh|git)(?::\\/\\/)(?:[^@\\/]+@)?|git@|)(?:www\\.)?github\\.com[\\/:]([^\\/\\s]+)\\/([^\\/\\s]+?)(?:\\.git)?\\/?$/i)\n if (!m) return undefined\n return `${m[1]}/${m[2]}`\n}\n\n/** The `origin` remote of a git checkout, or undefined when the directory is\n * not one (or git is missing). Synchronous: one git call, milliseconds. */\nexport function originRemoteOf(dir: string): string | undefined {\n try {\n if (!dir || !existsSync(dir)) return undefined\n return execFileSync(\"git\", [\"-C\", dir, \"remote\", \"get-url\", \"origin\"], {\n encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"], timeout: 5_000,\n }).trim() || undefined\n } catch {\n return undefined\n }\n}\n\n/** The GitHub repository and issue a channel chat id names:\n * \"owner/repo:issue:123\" or \"owner/repo:pull:456\" (channels/github.ts). */\nexport function parseGitHubChatId(chatId: string | undefined): { repo: string; kind: \"issue\" | \"pull\"; number: number } | undefined {\n if (!chatId) return undefined\n const parts = chatId.split(\":\")\n if (parts.length < 3) return undefined\n const number = Number(parts[parts.length - 1])\n const kind = parts[parts.length - 2]\n const repo = parts.slice(0, -2).join(\":\")\n if (!Number.isInteger(number) || number <= 0) return undefined\n if (kind !== \"issue\" && kind !== \"pull\") return undefined\n if (!/^[^\\/\\s]+\\/[^\\/\\s]+$/.test(repo)) return undefined\n return { repo, kind, number }\n}\n\n// --- Routing decision ---\n\nexport interface CloudRouteInput {\n agent: {\n tier: string\n workspace: string\n cloudSessions?: Partial<CloudSessionsAgentSettings>\n }\n /** `channels.<channel>.cloudSessions` for the task's channel. */\n channelEnabled: boolean\n context?: { channel?: string; chatId?: string; runbookPath?: string }\n /** The origin remote of a directory; injected so the decision is testable. */\n remoteOf?: (dir: string) => string | undefined\n /** A session already open for this issue, when the store has one. */\n openSession?: OpenCloudSession\n /** Launches this agent made today, for the daily cap. */\n launchesToday?: number\n}\n\nexport type CloudRoute =\n | { route: \"cloud\"; repo: string; kind: \"issue\" | \"pull\"; number: number; cwd: string }\n | { route: \"follow-up\"; session: OpenCloudSession }\n | { route: \"local\"; reason: string }\n\n/** Where a task runs. Pure apart from `remoteOf`; every \"local\" answer\n * carries the reason, which the registry logs and records on the trace. */\nexport function decideCloudRoute(input: CloudRouteInput): CloudRoute {\n const settings = { ...CLOUD_SESSIONS_DEFAULTS, ...(input.agent.cloudSessions ?? {}) }\n if (!settings.enabled) return { route: \"local\", reason: \"cloud sessions are off for this agent\" }\n const channel = input.context?.channel\n if (channel !== \"github\") return { route: \"local\", reason: `channel \"${channel || \"api\"}\" does not send tasks to the cloud (GitHub only)` }\n if (!input.channelEnabled) return { route: \"local\", reason: \"cloud sessions are off for the github channel\" }\n if (input.agent.tier !== \"claude-code\") return { route: \"local\", reason: `agent tier \"${input.agent.tier}\" cannot run cloud sessions (claude-code only)` }\n const target = parseGitHubChatId(input.context?.chatId)\n if (!target) return { route: \"local\", reason: `task is not for a GitHub issue or pull request (chat \"${input.context?.chatId || \"\"}\")` }\n if (input.openSession) return { route: \"follow-up\", session: input.openSession }\n if (settings.maxPerDay > 0 && (input.launchesToday ?? 0) >= settings.maxPerDay) {\n return { route: \"local\", reason: `daily cap of ${settings.maxPerDay} cloud launches reached` }\n }\n const remoteOf = input.remoteOf ?? originRemoteOf\n const candidates = [input.context?.runbookPath, input.agent.workspace].filter((d): d is string => !!d)\n const wanted = target.repo.toLowerCase()\n for (const dir of candidates) {\n const repo = parseGitHubRemote(remoteOf(dir))\n if (repo && repo.toLowerCase() === wanted) return { route: \"cloud\", repo: target.repo, kind: target.kind, number: target.number, cwd: dir }\n }\n return { route: \"local\", reason: `no checkout of ${target.repo} on this node (looked in ${candidates.join(\", \") || \"nothing\"}); set the project rule's runbook path to a clone whose origin is github.com/${target.repo}` }\n}\n\n// --- Open-session store ---\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n/** Entries older than this are dropped on load. Keeps daily counts whole. */\nconst KEEP_MS = 7 * DAY_MS\n\n/** Sessions this node launched, in a small JSON file (default\n * .agentx/cloud-sessions.json). Survives a daemon restart, so an open\n * session still blocks a local run after one. */\nexport class CloudSessionStore {\n private sessions: OpenCloudSession[] = []\n private loaded = false\n\n constructor(private readonly file: string) {}\n\n private load(): void {\n if (this.loaded) return\n this.loaded = true\n try {\n const raw = JSON.parse(readFileSync(this.file, \"utf8\"))\n const list = Array.isArray(raw?.sessions) ? raw.sessions : []\n this.sessions = list.filter((s: any) => s && typeof s.repo === \"string\" && typeof s.sessionId === \"string\" && Number.isFinite(s.startedAt))\n } catch {\n this.sessions = []\n }\n }\n\n private save(): void {\n const now = Date.now()\n this.sessions = this.sessions.filter((s) => now - s.startedAt < KEEP_MS)\n try {\n mkdirSync(dirname(this.file), { recursive: true })\n const tmp = `${this.file}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify({ sessions: this.sessions }, null, 2))\n renameSync(tmp, this.file)\n } catch { /* best effort: the daemon keeps the in-memory copy */ }\n }\n\n /** The open session for an issue, if one was launched less than\n * `openHours` ago. */\n open(repo: string, number: number, openHours: number, now = Date.now()): OpenCloudSession | undefined {\n this.load()\n const key = repo.toLowerCase()\n return this.sessions.find((s) => s.repo.toLowerCase() === key && s.number === number && now - s.startedAt < openHours * 60 * 60 * 1000)\n }\n\n record(session: OpenCloudSession): void {\n this.load()\n this.sessions = this.sessions.filter((s) => !(s.repo.toLowerCase() === session.repo.toLowerCase() && s.number === session.number))\n this.sessions.push(session)\n this.save()\n }\n\n /** Note a follow-up forwarded to the session. */\n touch(sessionId: string, now = Date.now()): void {\n this.load()\n const s = this.sessions.find((x) => x.sessionId === sessionId)\n if (!s) return\n s.lastMessageAt = now\n this.save()\n }\n\n /** Forget the session for an issue, so the next task runs normally. */\n close(repo: string, number: number): boolean {\n this.load()\n const before = this.sessions.length\n this.sessions = this.sessions.filter((s) => !(s.repo.toLowerCase() === repo.toLowerCase() && s.number === number))\n if (this.sessions.length === before) return false\n this.save()\n return true\n }\n\n /** Launches by an agent since local midnight. */\n launchesToday(agentId: string, now = Date.now()): number {\n this.load()\n const start = new Date(now)\n start.setHours(0, 0, 0, 0)\n return this.sessions.filter((s) => s.agentId === agentId && s.startedAt >= start.getTime() && s.startedAt <= now).length\n }\n\n all(): OpenCloudSession[] {\n this.load()\n return [...this.sessions]\n }\n}\n\n// --- Launch through a pty ---\n\n/** Runs argv under a pseudo-terminal and exits with the child's status. */\nexport const PTY_WRAPPER = [\n \"import os, pty, sys\",\n \"status = pty.spawn(sys.argv[1:])\",\n \"sys.exit(os.waitstatus_to_exitcode(status) if isinstance(status, int) else 0)\",\n].join(\"\\n\")\n\nexport type SpawnFn = (command: string, args: string[], options: { cwd: string; env: NodeJS.ProcessEnv; detached: boolean; stdio: [\"ignore\", \"pipe\", \"pipe\"] }) => ChildProcess\n\nexport interface CloudLaunchOptions {\n cwd: string\n prompt: string\n env: NodeJS.ProcessEnv\n timeoutMs: number\n /** Forward `prompt` to this open session instead of creating one. */\n sessionId?: string\n spawn?: SpawnFn\n python?: string\n}\n\nexport type CloudLaunchResult =\n | { ok: true; sessionId: string; url: string; output: string }\n | { ok: false; reason: string; output: string }\n\n/** The `claude` command line for a launch or a follow-up. */\nexport function cloudClaudeArgs(prompt: string, sessionId?: string): string[] {\n return sessionId ? [\"-p\", prompt, \"--cloud\", sessionId] : [\"--cloud\", prompt]\n}\n\nconst ANSI_RE = /\\x1b\\[[0-9;?]*[ -\\/]*[@-~]|\\x1b\\][^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)|\\x1b[()][AB012]|\\r/g\n\nexport function stripTerminalCodes(text: string): string {\n return text.replace(ANSI_RE, \"\")\n}\n\n/** The session id and URL `claude --cloud` printed. The id is taken from\n * the URL's last segment when it is not printed on its own. */\nexport function parseCloudLaunchOutput(raw: string): { sessionId?: string; url?: string } {\n const text = stripTerminalCodes(raw)\n const url = text.match(/https:\\/\\/claude\\.ai\\/code\\/[A-Za-z0-9_\\-]+(?:\\/[A-Za-z0-9_\\-]+)*/)?.[0]\n let sessionId = text.match(/\\bsession_[A-Za-z0-9]{6,}\\b/)?.[0]\n if (!sessionId && url) {\n const last = url.split(\"/\").filter(Boolean).pop()\n if (last && last !== \"code\") sessionId = last\n }\n return { sessionId, url }\n}\n\n/** Starts a cloud session (or forwards a follow-up to one) and returns its\n * id and URL. Never throws: every failure is a `reason` for the fallback. */\nexport async function launchCloudSession(opts: CloudLaunchOptions): Promise<CloudLaunchResult> {\n const spawn: SpawnFn = opts.spawn ?? ((cmd, args, o) => nodeSpawn(cmd, args, o))\n const python = opts.python ?? \"python3\"\n const args = [\"-c\", PTY_WRAPPER, \"claude\", ...cloudClaudeArgs(opts.prompt, opts.sessionId)]\n const env: NodeJS.ProcessEnv = { ...opts.env, NO_COLOR: \"1\", FORCE_COLOR: \"0\" }\n let output = \"\"\n const append = (chunk: Buffer | string) => {\n output += chunk.toString()\n if (output.length > 64_000) output = output.slice(-64_000)\n }\n const tail = () => stripTerminalCodes(output).trim().split(\"\\n\").slice(-5).join(\"\\n\").slice(-600)\n\n return new Promise<CloudLaunchResult>((resolve) => {\n let settled = false\n const done = (result: CloudLaunchResult) => { if (!settled) { settled = true; resolve(result) } }\n let proc: ChildProcess\n try {\n proc = spawn(python, args, { cwd: opts.cwd, env, detached: true, stdio: [\"ignore\", \"pipe\", \"pipe\"] })\n } catch (e: any) {\n done({ ok: false, reason: `could not start ${python}: ${e?.message || e}`, output })\n return\n }\n const killTree = (signal: NodeJS.Signals) => {\n try { if (proc.pid) process.kill(-proc.pid, signal) } catch { try { proc.kill(signal) } catch { /* gone */ } }\n }\n const timer = setTimeout(() => {\n killTree(\"SIGTERM\")\n setTimeout(() => killTree(\"SIGKILL\"), 3_000).unref?.()\n done({ ok: false, reason: `claude --cloud did not print a session within ${Math.round(opts.timeoutMs / 1000)}s${tail() ? `; last output: ${tail()}` : \"\"}`, output })\n }, opts.timeoutMs)\n timer.unref?.()\n proc.stdout?.on(\"data\", append)\n proc.stderr?.on(\"data\", append)\n proc.on(\"error\", (e: any) => {\n clearTimeout(timer)\n const missing = e?.code === \"ENOENT\"\n done({ ok: false, reason: missing ? `${python} is not installed (needed to give claude --cloud a terminal)` : `launch failed: ${e?.message || e}`, output })\n })\n proc.on(\"close\", (code) => {\n clearTimeout(timer)\n if (settled) return\n const parsed = parseCloudLaunchOutput(output)\n if (opts.sessionId) {\n if (code === 0) { done({ ok: true, sessionId: opts.sessionId, url: parsed.url || \"\", output }); return }\n done({ ok: false, reason: `claude -p --cloud exited with code ${code ?? \"unknown\"}${tail() ? `: ${tail()}` : \"\"}`, output })\n return\n }\n if (parsed.sessionId && parsed.url) { done({ ok: true, sessionId: parsed.sessionId, url: parsed.url, output }); return }\n if (code !== 0) { done({ ok: false, reason: `claude --cloud exited with code ${code ?? \"unknown\"}${tail() ? `: ${tail()}` : \"\"}`, output }); return }\n done({ ok: false, reason: `claude --cloud printed no session id or URL${tail() ? `; last output: ${tail()}` : \"\"}`, output })\n })\n })\n}\n\n// --- Prompt and reply text ---\n\n/** Linux caps one argv string at 128 KiB; stay well under it. */\nconst MAX_CLOUD_PROMPT_CHARS = 100_000\n\n/** What the cloud session is asked to do. The session reads the repository's\n * own CLAUDE.md; the agent's local system prompt is not sent. */\nexport function buildCloudPrompt(message: string, target: { repo: string; kind: \"issue\" | \"pull\"; number: number }): string {\n const what = target.kind === \"pull\" ? \"pull request\" : \"issue\"\n const footer = `\\n\\n---\\nThis task comes from GitHub ${what} #${target.number} in ${target.repo}. Work in this repository and open or update a pull request that references #${target.number}.`\n const room = MAX_CLOUD_PROMPT_CHARS - footer.length\n const body = message.length > room ? `${message.slice(0, room)}\\n[message cut at ${room} characters]` : message\n return `${body}${footer}`\n}\n\nexport function cloudSessionReply(session: { sessionId: string; url: string }, kind: \"issue\" | \"pull\", openHours: number): string {\n const what = kind === \"pull\" ? \"pull request\" : \"issue\"\n return [\n `Started a Claude cloud session for this ${what}. The result arrives as a pull request.`,\n `Session: \\`${session.sessionId}\\``,\n session.url,\n `Comments here are forwarded to that session for the next ${openHours} hours.`,\n ].join(\"\\n\")\n}\n\nexport function cloudFollowUpReply(session: { sessionId: string; url: string }): string {\n return `Forwarded to the open Claude cloud session \\`${session.sessionId}\\` (${session.url}).`\n}\n\n// --- Orchestration: cloud first, local on failure ---\n\nexport interface CloudDispatchResult {\n content: string\n error?: string\n cloudSession: { id: string; url: string; followUp?: boolean }\n}\n\nexport interface CloudDispatchDeps {\n route: CloudRoute\n agentId: string\n message: string\n settings: CloudSessionsAgentSettings\n store: Pick<CloudSessionStore, \"record\" | \"touch\">\n launch: (opts: { cwd: string; prompt: string; sessionId?: string }) => Promise<CloudLaunchResult>\n log: (line: string) => void\n /** Recorded on the task trace (task:step \"cloud_session\"). */\n trace: (step: { action: \"launched\" | \"follow-up\" | \"fallback\" | \"skipped\"; status: \"ok\" | \"error\"; summary: string }) => void\n traceId?: string\n now?: () => number\n}\n\n/** Runs the task in the cloud when the route says so. Returns null when the\n * task must run locally: the route said so, or the launch failed (logged and\n * traced with the reason). A follow-up that cannot be forwarded is an error\n * response, never a local run: the session stays the one working on the issue. */\nexport async function dispatchCloudSession(deps: CloudDispatchDeps): Promise<CloudDispatchResult | null> {\n const { route } = deps\n const now = deps.now ?? Date.now\n if (route.route === \"local\") {\n deps.trace({ action: \"skipped\", status: \"ok\", summary: route.reason })\n return null\n }\n if (route.route === \"follow-up\") {\n const s = route.session\n const result = await deps.launch({ cwd: s.cwd || process.cwd(), prompt: deps.message, sessionId: s.sessionId })\n if (result.ok) {\n deps.store.touch(s.sessionId, now())\n deps.log(`[${deps.agentId}] forwarded to open cloud session ${s.sessionId} for ${s.repo}#${s.number}`)\n deps.trace({ action: \"follow-up\", status: \"ok\", summary: JSON.stringify({ sessionId: s.sessionId, url: s.url }) })\n return { content: cloudFollowUpReply(s), cloudSession: { id: s.sessionId, url: s.url, followUp: true } }\n }\n deps.log(`[${deps.agentId}] forwarding to cloud session ${s.sessionId} failed: ${result.reason}`)\n deps.trace({ action: \"follow-up\", status: \"error\", summary: result.reason })\n return {\n content: \"\",\n error: `Claude cloud session \\`${s.sessionId}\\` is still open for this ${s.kind === \"pull\" ? \"pull request\" : \"issue\"} (${s.url}); forwarding this comment to it failed: ${result.reason}. No local run starts while the session is open.`,\n cloudSession: { id: s.sessionId, url: s.url, followUp: true },\n }\n }\n const prompt = buildCloudPrompt(deps.message, route)\n const result = await deps.launch({ cwd: route.cwd, prompt })\n if (!result.ok) {\n deps.log(`[${deps.agentId}] cloud session launch for ${route.repo}#${route.number} failed, running locally: ${result.reason}`)\n deps.trace({ action: \"fallback\", status: \"error\", summary: result.reason })\n return null\n }\n const session: OpenCloudSession = {\n repo: route.repo, number: route.number, kind: route.kind,\n sessionId: result.sessionId, url: result.url, agentId: deps.agentId,\n startedAt: now(), cwd: route.cwd, traceId: deps.traceId,\n }\n deps.store.record(session)\n deps.log(`[${deps.agentId}] cloud session ${result.sessionId} started for ${route.repo}#${route.number}: ${result.url}`)\n deps.trace({ action: \"launched\", status: \"ok\", summary: JSON.stringify({ sessionId: result.sessionId, url: result.url, repo: route.repo, number: route.number }) })\n return { content: cloudSessionReply(session, route.kind, deps.settings.openHours), cloudSession: { id: result.sessionId, url: result.url } }\n}\n"],"mappings":"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","names":["init_seat","init_questions","TOOL_SET_SEAT","TOOL_BUNDLES","BUNDLE_TOOLS","MISS_RISK","toolSetState","input","clip","toolSetQuestions","choice","bundleFor","answers","risk","p","pa","num","pc","pf","labelForTools","toolNames","builtIn","n","isMiss","picked","needed","v","s","max","warnedActive","startToolSetShadow","input","log","mode","getSeatMode","TOOL_SET_SEAT","askSeat","toolSetState","toolSetQuestions","result","answers","bundle","bundleFor","probabilities","labelToolSetRun","pending","run","guess","used","needed","labelForTools","labelSeatCall","p","k","n","isMiss","requestsPerTurn","contextTokens","turnInputTokens","decideResume","input","s","n","warm","later","freshCost","c","resumeCost","describeResume","r","k","x","cache","init_seat","init_questions","SESSION_CONTINUITY_SEAT","sessionContinuityQuestions","noul","continuityState","input","earlier","clip","r","shouldRotateEarly","answers","policy","maxContinuity","continues","needsHistory","value","max","text","execa","spawn","createHash","createInterface","CodexUnavailable","launch","o","Connection","launchProcess","line","msg","p","e","message","method","params","id","resolve","reject","error","CodexProcessPool","capacity","idleMs","connection","start","fingerprint","a","b","entry","reused","idle","c","submitted","content","finalText","threadId","usage","contextTokens","firstOutput","abort","deadline","startup","completed","item","itemTypes","u","cached","codexProcessPool","execFile","StringDecoder","mkdtempSync","readFileSync","rmSync","join","tmpdir","friendlyModelError","raw","source","dateMatch","jsonMatch","parsed","inner","errType","errMessage","statusMatch","status","renderFriendlyError","f","primaryModelFromUsage","modelUsage","best","bestCost","bestTokens","model","raw","u","cost","tokens","_registry","getProcessRegistry","setProcessRegistry","r","processKeyToString","k","staleHandleReason","handle","opts","snap","prevModel","RegistryCapExceeded","message","scope","cap","ProcessRegistry","cfg","handles","h","key","ks","processKeyToString","existing","stale","reason","agentId","n","target","oldest","ts","now","idleFor","workspace","currentHash","e","fromHash","toHash","spawn","randomUUID","existsSync","readFileSync","resolve","readClaudeMdHashSafe","workspace","path","resolve","existsSync","content","readFileSync","readManagedHash","TURN_DEADLINE_MS","KILL_GRACE_MS","TurnDeadlineExceeded","budgetMs","TurnInterrupted","reason","persistentCallerEnv","env","key","ClaudeProcessFactory","opts","ClaudeProcessHandle","factoryOpts","binary","args","log","spawn","claudeBillingEnv","chunk","code","ws","w","err","input","release","myDone","r","prevQueue","turnStart","uuid","randomUUID","line","deadline","timedOut","echoed","init","remaining","evt","text","stillBusy","forced","t","nl","timeoutMs","resolved","idx","resolver","e","usage","sessionId","init_autonomy","init_sqlite","init_ulid","resolve","join","COMMAND_CAP","recordDecision","a","dbPath","db","openDb","id","newEventId","cmd","listDecisions","f","where","params","sql","init_autonomy","MAX_BLOCKS_PER_TASK","BLOCK_TTL_MS","hookPort","blocksByTask","setAutonomyHookPort","port","getAutonomyHookPort","autonomyHookCommand","agentId","level","taskId","q","autonomyClaudeArgs","isRestricted","settings","args","autonomyUnsupported","tier","sweep","now","k","v","recordAutonomyBlock","block","entry","takeAutonomyBlocks","denyOutput","reason","checkAutonomyPayload","payload","opts","tool","command","filePath","parsed","autonomyLevelSchema","decision","evaluateAutonomy","ruleId","verdict","recordDecision","e","init_people","LIMIT_TTL_MS","limitsByTask","sweep","now","k","v","registerPersonLimits","taskId","limits","clearPersonLimits","personLimitHookCommand","port","agentId","q","withPersonLimitHook","args","getAutonomyHookPort","entry","out","at","settings","personLimitsUnsupported","tier","denyOutput","reason","denialOf","tool","input","p","nameMatches","skill","checkPersonLimitPayload","payload","opts","denied","verdict","recordDecision","e","SIGTERM_TIMEOUT_SLACK_MS","buildErrorEnvelope","raw","friendly","friendlyModelError","sample","renderFriendlyError","claudeMissingMessage","codexMissingMessage","openCodeMissingMessage","buildPrompt","agent","task","historyContext","parts","ctx","envLines","isGitLab","isTelegram","peer","handle","role","buildClaudeArgs","prompt","streaming","resumeSessionId","modelOverride","systemPromptAppend","extraArgs","args","model","buildCodexPrompt","buildOpenCodeArgs","extractOpenCodeError","event","value","tomlString","buildCodexAgentxMcpArgs","cli","buildCodexArgs","outputFile","buildRuntimeEnv","env","buildAgentEnv","home","pathParts","p","withCallerEnv","warmProcessChat","context","buildOpenCodeEnv","config","mcp","name","server","extractCodexTextFromEvent","item","content","text","block","firstNumber","values","extractCodexUsage","depth","candidate","nested","usage","extractCodexBilledModel","extractCodexSessionId","key","sessionId","collectCodexRunMetadata","stdout","billedModel","apiError","line","extractCodexErrorMessage","unwrapCodexErrorMessage","msg","trimmed","inner","resolvePermission","configured","skipPermissions","summary","logClaudeSpawn","agentId","via","perm","resume","extractClaudeIsError","data","parseClaudeJsonOutput","primaryModelFromUsage","executeClaudeCode","abortSignal","start","restricted","restrictedClaudeArgs","abortReason","timeoutMs","stderr","exitCode","killed","resolve","childEnv","claudeBillingEnv","proc","execFile","error","onAbort","errMsg","ranMs","apiErrorInStdout","parsed","executeClaudeCodeStreaming","onDelta","onEvent","fullText","earlierText","turnText","emitTurnText","delta","sep","background","BackgroundTasks","heldForBackground","streamBilledModel","streamUsage","streamSessionId","streamContextTokens","streamNumTurns","streamCostUsd","streamApiError","streamTimeoutMs","spawnEnv","execa","markResultSeen","resultSeen","r","cancelled","cancelReason","lineBuffer","decoder","StringDecoder","chunk","lines","u","callContext","resultText","result","settleAfterResult","tail","executeCodexCli","tmp","mkdtempSync","join","tmpdir","metadata","readFileSync","rmSync","executeCodexCliStreaming","stderrText","idleTimedOut","stderrDecoder","idleTimeoutMs","idleTimer","resetIdleTimer","stderrTail","finalText","executeOpenCodeCli","onThinking","failed","cleanupAbort","openCodeProcessPool","OpenCodeServerUnavailable","consumeLine","reasoning","tokens","executeSdk","apiKey","sdk","query","q","message","executeOrchestrator","providerOpts","combined","timedOut","onOperatorAbort","reason","wallClockTimer","timeoutEnvelope","providerName","fullTask","sender","looksUuid","acmeContext","mcpServers","effectiveMcpConfig","hasMcp","baseOpts","generateStream","tokensUsed","inputTokens","outputTokens","errorMsg","trailing","generate","total","POST_RESULT_EXIT_GRACE_MS","exited","timer","graced","res","first","executeClaudeCodePersistent","registry","getProcessRegistry","channel","chatId","wasFreshSpawn","before","e","RegistryCapExceeded","finalError","finalErrorKind","contextTokens","numTurns","streamedText","messageText","emitText","sawEvent","turnBudgetMs","evt","blocks","TurnInterrupted","TurnDeadlineExceeded","restrictedLevel","isRestricted","autonomy","autonomyClaudeArgs","withPersonLimitHook","isGuarded","executeRestrictedTask","level","unsupported","autonomyUnsupported","personLimitsUnsupported","taskId","autonomyBrief","why","registerPersonLimits","response","takeAutonomyBlocks","clearPersonLimits","executeTask","providers","persistent","codexProcessPool","CodexUnavailable","providerCfg","readFileSync","writeFileSync","mkdirSync","existsSync","readdirSync","resolve","MAX_HISTORY_CHARS","MAX_MESSAGES","DEFAULT_STALE_SESSION_MINUTES","DEFAULT_MAX_TURNS_PER_SESSION","DEFAULT_TIER_TWO_THRESHOLD_TOKENS","COMPACTION_MARKER","ROTATION_MEMO_TTL_MS","CROSS_CHAT_HINT_PATTERNS","referencesOtherChat","message","re","LONG_MEMORY_PATTERNS","detectLongMemoryHint","max","p","isDuplicateOfLast","messages","role","name","content","last","priorUserMessage","currentMessage","users","m","tail","priorUserRequests","count","end","bucketLabel","iso","hh","mm","qtr","SessionStore","baseDir","opts","resolve","existsSync","mkdirSync","channel","tokens","fn","agentId","chatId","callerSeeds","session","key","inFlight","adapter","promise","seeds","existingIds","appended","s","day","safe","file","data","readFileSync","prev","prevDay","prevKey","senderName","expectedContent","claudeSessionId","codexSessionId","opencodeSessionId","maxMessages","maxChars","chars","i","msg","lines","currentBucket","bucket","limit","todayStart","defaultAfter","after","before","safeAgentId","allFiles","readdirSync","f","afterDay","beforeDay","dayRe","candidateFiles","collected","totalScanned","queryLower","participantSet","a","b","page","oldestTs","hasMore","sinceISO","sinceDay","candidates","rows","seen","dedupped","r","k","idleMs","currentKey","files","hints","recent","scope","memoryStore","needsCompaction","compactSession","applyCompaction","result","usage","contextTokens","parsed","memo","reason","memos","writeFileSync","rec","age","beforeCount","totalChars","sum","removed","dropped","debug","RateLimiter","maxPerMinute","maxPerHour","agentId","now","timestamps","hourAgo","recent","t","minuteAgo","inMinute","oldest","waitMs","opts","maxWaitMs","deadline","firstWait","r","remaining","sleepMs","resolve","DEFAULT_CONFIG","CHARS_PER_TOKEN","buildAgentContext","input","config","layers","buildLayers","a","b","parts","totalChars","maxChars","layer","layerMaxChars","trimmed","remaining","budget","name","fallback","buildChannelLayer","buildScopeLayer","lines","leafId","leafAxes","kv","k","v","intentTags","extractIntentTags","artifactLines","history","maxTokens","rules","tags","r","meta","fact","message","lower","RESUME_DELIVERY_FLAG","callerAgentOf","origin","m","MAX_ORIGIN_BYTES","serializeOrigin","json","parseOrigin","o","hhmm","ms","staleQueueNote","queuedAt","now","MessageQueue","defaultMode","agentId","channel","chatId","k","mode","callback","q","message","messages","combined","m","key","readFileSync","writeFileSync","appendFileSync","mkdirSync","existsSync","resolve","MAX_PATTERNS","EXTRACTION_MODEL","PATTERN_EXTRACTION_PROMPT","PatternStore","baseDir","resolve","existsSync","mkdirSync","agentId","file","readFileSync","l","p","pattern","entry","appendFileSync","message","limit","patterns","docs","cachePath","index","buildIndexCached","results","scoreAll","s","lines","existing","words","w","pWords","ids","idSet","changed","writeFileSync","a","b","extractPatterns","userMessage","agentResponse","source","store","createProvider","jsonMatch","EventEmitter","SEEN_MAX","TypedEventBus","EventEmitter","EventRing","opts","event","listener","payload","delivered","lifecycleEnvelope","input","root","currentRoot","id","newEventId","envelope","capSummary","parentId","fn","key","q","p","base","_instance","getEventBus","init_ulid","init_ulid","DELIVERY_MODES","isDeliveryMode","v","DEFAULT_ATTACH_OPTIONS","SAVED_BINDING_TTL_MS","AttachRegistry","opts","store","DEFAULT_ATTACH_OPTIONS","loaded","cutoff","sessionId","b","info","now","existing","saved","session","a","agentId","mode","other","otherId","state","cursor","s","reason","item","input","newEventId","clip","resolve","i","n","next","text","itemId","expired","id","olderThanMs","waiter","max","init_config","z","DEFAULT_WATCH_SUBSCRIPTIONS","parseWatchSubscriptions","raw","parsed","z","eventSubscriptionSchema","issue","WATCH_DIGEST_MAX_LINES","WATCH_DIGEST_MAX_CHARS","LINE_MAX","cursorAtEnd","events","now","last","eventsAfter","cursor","idx","e","watchDigest","state","caps","fresh","matched","s","matchesFilters","since","text","shown","renderWatchDigest","maxLines","maxChars","lines","clipLine","formatEventLine","total","more","out","existsSync","mkdirSync","readFileSync","renameSync","writeFileSync","homedir","dirname","join","fileBindingStore","path","existsSync","readFileSync","all","mkdirSync","dirname","tmp","writeFileSync","renameSync","defaultBindingStore","join","homedir","_registry","getAttachRegistry","AttachRegistry","init_autonomy","readFileSync","homedir","join","cached","TTL_MS","readClaudeJson","path","join","homedir","raw","readFileSync","parsed","parseOverageStatus","data","now","disabledReason","getOverageStatus","forceRefresh","cached","TTL_MS","preflightOverageGate","hasWarmSession","status","DEFAULT_BUDGET","budget","FIVE_H_MS","ONE_H_MS","timestamps","setDispatchBudget","next","prune","now","cutoff","FIVE_H_MS","i","timestamps","recordClaudeCodeDispatch","providerSignals","REJECTED_FALLBACK_HOLD_MS","asNumber","v","asEpochMs","n","parseRateLimitEvent","event","e","info","status","window","resetsAt","utilization","overageStatus","usingOverage","windowModel","concerns","model","family","recordRateLimitEvent","signal","s","activeProviderHolds","activeProviderHold","liftProviderHolds","lifted","getProviderSignals","a","b","capOn","getClaudeCodeUsage","hourCutoff","ONE_H_MS","lastHour","last5h","out","budget","windowLabel","resetLabel","preflightQuotaGate","hasWarmSession","usage","hold","warnIfNearingCap","nearing","pct","ratio","stats","promptSizeKey","agentId","channel","chatId","recordPromptSize","key","total","parts","now","s","warnIfPromptGrowing","threshold","ratio","existsSync","readFileSync","resolve","existsSync","readFileSync","unlinkSync","writeFileSync","resolve","MARKER","agentxToolServer","proc","cli","withAgentXToolServer","mcp","server","syncMcpToWorkspace","workspacePath","mcpPath","fileExists","parsed","tryParseJson","isManaged","hasConfig","next","path","DEFAULT_LEAN_CHANNELS","LEAN_CAPABLE_TIERS","DEFAULT_LEAN","AGENTX_MCP_TOOLS_ENV","AGENTX_CORE_TOOLS","AGENTX_ON_DEMAND_TOOLS","leanTools","lean","channel","own","list","t","leanAgentxTools","kept","withAgentxToolList","server","tools","resolveSessionProfile","config","tier","listed","leanConfig","leanLoadsWorkspace","leanMcpServers","workspace","agentx","agentxToolServer","declared","readWorkspaceMcp","name","file","resolve","existsSync","servers","readFileSync","leanClaudeArgs","mcp","args","withToolSearch","TOOL_SEARCH","fullClaudeArgs","onDemandContextNote","chatId","describeProfile","profile","parts","agentxTools","existsSync","mkdirSync","readdirSync","readFileSync","rmSync","writeFileSync","resolve","randomUUID","existsSync","mkdirSync","readFileSync","readdirSync","renameSync","unlinkSync","writeFileSync","resolve","relative","procedureMetaSchema","parseFrontmatter","raw","m","meta","line","kv","key","val","s","serializeFrontmatter","lines","ProcedureStore","opts","resolve","mkdirSync","id","full","readFileSync","body","parsed","procedureMetaSchema","relative","dir","existsSync","out","entry","readdirSync","proc","a","b","p","writeFileSync","patch","newBody","existing","merged","draft","unlinkSync","rejectedDir","renameSync","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","resolve","z","candidateSchema","candidateLedgerSchema","candidatesPath","baseDir","readCandidates","p","parsed","writeCandidates","ledger","upsertEpisode","key","taskId","timestamp","sampleMessage","cand","c","readyCandidates","minOccurrences","a","b","markCandidate","status","procedureId","words","text","w","overlap","a","b","n","matchProcedures","message","procedures","opts","limit","minScore","messageWords","matches","p","reasons","triggerScore","metaText","metaScore","score","renderProcedureContext","m","lines","init_seat","init_questions","TURN_PROGRESS_SEAT","turnProgressQuestions","choice","clip","text","max","turnProgressState","input","s","init_questions","TURN_HANDOFF_SEAT","APPROVAL_BINDING_SEAT","turnHandoffQuestions","choice","approvalBindingQuestions","noul","clip","text","max","tail","QUESTION_TAIL","endsWithQuestion","reply","APPROVAL","isBareApproval","message","isShortReply","turnHandoffState","input","approvalBindingState","CHECKPOINT_MS","REPEAT_THRESHOLD","RECENT_STEPS","HANDOFF_LABEL_WINDOW_MS","AUTOMATED_CHANNELS","NOOP_WATCH","startTurnWatch","opts","getSeatMode","TURN_PROGRESS_SEAT","now","started","steps","byToolUseId","calls","askedSignatures","stepsAtLastAsk","inFlight","repeated","best","call","times","ask","trigger","state","turnProgressState","askSeat","turnProgressQuestions","timer","event","blocks","block","input","step","signature","b","pendingHandoffs","conversationKey","agent","channel","chatId","onAgentReply","endsWithQuestion","key","TURN_HANDOFF_SEAT","turnHandoffState","turnHandoffQuestions","result","onUserMessage","pending","store","decisionsRuntime","isBareApproval","isShortReply","APPROVAL_BINDING_SEAT","approvalBindingState","approvalBindingQuestions","init_initiator","init_tracker","init_operator","dueAtOf","ctx","v","init_people","untilAborted","work","signal","graceMs","resolve","reject","timer","onAbort","abortReason","done","value","error","reason","StepBudgetExceeded","step","budgetMs","withBudget","execFileSync","nodeSpawn","existsSync","mkdirSync","readFileSync","renameSync","writeFileSync","dirname","CLOUD_SESSIONS_DEFAULTS","parseGitHubRemote","url","m","originRemoteOf","dir","parseGitHubChatId","chatId","parts","number","kind","repo","decideCloudRoute","input","settings","channel","target","remoteOf","candidates","d","wanted","DAY_MS","KEEP_MS","CloudSessionStore","file","raw","list","now","s","tmp","openHours","key","session","sessionId","x","before","agentId","start","PTY_WRAPPER","cloudClaudeArgs","prompt","ANSI_RE","stripTerminalCodes","text","parseCloudLaunchOutput","last","launchCloudSession","opts","spawn","cmd","args","o","python","env","output","append","chunk","tail","resolve","settled","done","result","proc","e","killTree","signal","timer","missing","code","parsed","MAX_CLOUD_PROMPT_CHARS","buildCloudPrompt","message","footer","room","cloudSessionReply","cloudFollowUpReply","dispatchCloudSession","deps","route","INTENT_WAIT_BEFORE_MATCH_MS","INTENT_WAIT_BEFORE_ENTRY_MS","STEP_BUDGET_MS","CODE_FIRST_INSTRUCTION","EDIT_TOOL_INSTRUCTION","codingInstructions","tier","isCodingChannelContext","channel","globalRegistry","setGlobalRegistry","registry","getGlobalRegistry","TASK_OUTPUT_BUFFER_MAX","TASK_OUTPUT_TTL_MS","CANCEL_GRACE_MS","PRE_SPAWN_TIMEOUT_SEC","TASK_HISTORY_DIR","TASK_HISTORY_RETENTION_DAYS","clip","s","max","buildEntryMeta","intent","context","meta","buildEventDigest","agentId","subs","shown","more","digestEvents","getEventBus","renderDigest","wikiNotesHint","config","n","host","port","buildWikiContext","agentWiki","opts","articles","byType","a","t","list","listed","catalog","type","titles","cli","wikiDir","TRACE_STEP_SUMMARY_BYTES","withOutbox","workspace","hint","prepareOutbox","clipForTrace","remaining","tallyToolUses","counter","event","blocks","block","name","firstMissingRequiredTool","required","invoked","emitTraceStepsFromStreamEvent","taskId","at","bus","inputJson","content","b","makeStreamEventFormatter","textSeen","codexTextSeen","model","resume","usage","input","output","item","text","delta","label","summary","msg","m","sid","out","c","flag","AgentRegistry","AgentMemory","HandoverStore","log","SessionStore","WikiHub","MemoryStore","PatternStore","RateLimiter","TokenTracker","MessageQueue","setDispatchBudget","GraphStore","resolve","draftAgent","Classifier","id","def","builder","mesh","runs","runner","next","workflows","people","ctx","refused","refusedPerson","personId","detail","days","MemberStore","chatId","o","lower","bestId","bestLen","state","mention","mentionLower","found","task","resumeSessionId","previousMessage","minutesSinceLastTurn","recentRequests","session","priorUserMessage","priorUserRequests","result","askSeat","SESSION_CONTINUITY_SEAT","continuityState","sessionContinuityQuestions","shouldRotateEarly","reason","extractRotationMemo","storeRotationMemo","stored","e","peers","agent","CloudSessionStore","settings","CLOUD_SESSIONS_DEFAULTS","channelCfg","target","parseGitHubChatId","store","decideCloudRoute","route","traceId","started","env","claudeBillingEnv","withCallerEnv","buildAgentEnv","dispatchCloudSession","launchCloudSession","line","during","abortReason","onDelta","onThinking","onEvent","startedAt","response","interruptedBy","stoppedBy","preSpawn","step","status","isQueued","getDefaultLedger","callerOnEvent","refusal","restricted","isRestricted","limits","personLimitsOf","offered","getAttachRegistry","outcome","peerEntry","p","timeoutMs","qChannel","qChatId","warmProcessChat","voiceClaim","start","maxWaitMs","pollIntervalMs","r","claim","mode","queued","pending","senderOf","isHumanFacingTurn","personOfTurn","queuedMarker","rateResult","waitMs","staleNote","staleQueueNote","runningTask","person","abortController","currentRoot","pushToBuffer","chunk","sub","traceTaskId","newEventId","abortEntry","toolUsesByName","recordPipelineStep","stage","agentStartedAt","firstEventSeen","noteFirstEvent","formatter","formatted","recordRateLimitEvent","deadlineMs","deadline","preSpawnMs","preSpawnDeadline","clearPreSpawnDeadline","runTag","timedOutStep","traceStep","work","graceMs","bounded","withBudget","untilAborted","value","err","StepBudgetExceeded","budgeted","fallback","traceOpened","completedEmitted","closeTrace","finalResponse","released","releaseRun","idx","waiters","w","endedAt","record","rawSummary","firstLine","flushedAt","ended","qm","resp","evaluateRequest","selectRequestContext","requestGatePending","requestGateSettled","senderName","isCodexCli","sessionProfile","resolveSessionProfile","lean","leanConfig","leanOnDemand","priorIdleMs","previousReply","onUserMessage","taskStartedAt","serializeOrigin","isPickup","isOperatorTurn","dueAtOf","intentPending","classifier","classifyStartedAt","awaitIntent","maxMs","timer","wfMatching","pendingAutoRun","workflowHint","followUp","WorkflowStore","match","matchWorkflow","workflowHintText","wrapRunStore","requiredFor","shouldWrap","threshold","wrapRunId","randomUUID","workflowRunContext","wrapHintText","relevantMemories","memoryContext","memoryFacts","relevantPatterns","patternContext","skillInjection","loadLocalSkills","getAutoInjectSkills","loaded","denied","skills","nameMatches","lastTokens","turns","gate","decideResume","describeResume","sessionId","compactResult","quality","lost","d","surgeryEnabled","historyCap","wikiContext","sessionHistory","rotationMemo","procedureContext","procedureCandidates","procStore","ProcedureStore","procMatches","matchProcedures","renderProcedureContext","debug","msgs","first","last","fingerprint","referencesBlock","cacheKey","cached","refs","recipes","loadReferences","loadRecipes","rootRefs","rootRecipes","resolved","resolveRecipes","renderReferences","crossChatContext","longMemoryRecall","lmHint","detectLongMemoryHint","recall","lines","ordered","stamp","who","strategy","sessionHistoryOverride","planDebug","plannerSucceeded","requestGate","planContext","plan","soulSessionKey","soulSwitch","detectSoulSwitch","activeSoul","bootstrapFiles","loadBootstrapFiles","bootstrapContextText","buildBootstrapContext","agentMemoryBlock","codeFirstInstruction","richReplyInstruction","richReplyPrompt","claudeLoadsWorkspace","leanLoadsWorkspace","projectClaudeMd","claudeMdPath","existsSync","readFileSync","runbookPath","runbookBlocks","filesToTry","totalBytes","PER_FILE_CAP","TOTAL_CAP","filename","filePath","trimmed","projectRunbook","systemPromptAppend","contextInput","onDemandContextNote","appAttachHint","triage","triageModelFor","cheapModel","cheapModelForEngine","routedModelPending","routeTaskModel","routedModelSettled","selectedContext","historyContext","buildAgentContext","injectedContext","injectedContextOf","agentxContextBytes","sizeParts","sizeKey","promptSizeKey","recordPromptSize","driftWarning","warnIfPromptGrowing","routedModel","claudeArgs","leanClaudeArgs","fullClaudeArgs","describeProfile","toolSetPending","startToolSetShadow","taskWithSystemPrompt","turnWatch","startTurnWatch","unwatchedOnEvent","wrapStarted","wrapReadOnly","use","toolUsesOf","isReadOnlyToolUse","autoRunner","autoRun","continues","continuedRun","startWrap","TASK_WORKFLOW_ID","runDispatchGates","procReg","getProcessRegistry","hasLiveProcess","hasWarmSession","abort","preflightOverageGate","preflightQuotaGate","g","preflightResponse","recordClaudeCodeDispatch","warning","warnIfNearingCap","cloudRoute","gated","tierUsesStreamJson","wrappedOnDelta","fullText","thinkingOpen","wrappedOnThinking","closeThinkingIfOpen","wrappedOnDeltaWithThinkingClose","cloud","executeTask","missing","invokedSummary","labelToolSetRun","split","splitTaskUsageByTier","finalText","onAgentReply","contextSize","cumulative","shouldCaptureEntry","entryId","extractMemories","extractPatterns","billedModel","tChannel","tSessionKey","error","friendly","friendlyModelError","renderFriendlyError","closed","finishWrap","endedWithoutFailing","total","tasks","run","liveStep","day","dir","mkdirSync","file","writeFileSync","hours","now","buckets","windowStart","root","today","yesterday","dayDir","f","readdirSync","tsStr","ts","offsetHours","agentDir","agentPath","outer","files","rec","raw","retentionDays","cutoff","removed","dayEntries","rmSync","limit","_t","_r","safeId","router","getMessageRouter","by","matches","chat","latest","entry","resolveEnd","ids","k","message","sender","originalMessage","isStopped","edited","queuedAt","operatorPerson","replaced","resolveStart","settled","settle","richMessages"]}
|