agentix-cli 0.18.0 → 0.20.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.
Files changed (171) hide show
  1. package/README.md +75 -27
  2. package/dist/{agent-RDVGL2TW.js → agent-57MG4LBM.js} +2 -2
  3. package/dist/{audit-Z47AQFFD.js → audit-JU3HWISB.js} +2 -2
  4. package/dist/{board-dashboard-KX2CNOSY.js → board-dashboard-HXDOYGIG.js} +2237 -69
  5. package/dist/board-dashboard-HXDOYGIG.js.map +1 -0
  6. package/dist/chunk-23AUU4S2.js +102 -0
  7. package/dist/chunk-23AUU4S2.js.map +1 -0
  8. package/dist/chunk-4MREHZP5.js +111 -0
  9. package/dist/chunk-4MREHZP5.js.map +1 -0
  10. package/dist/chunk-4YCH6IZV.js +2 -0
  11. package/dist/chunk-6B74TO2J.js +743 -0
  12. package/dist/chunk-6B74TO2J.js.map +1 -0
  13. package/dist/chunk-7GVXCOLO.js +440 -0
  14. package/dist/chunk-7GVXCOLO.js.map +1 -0
  15. package/dist/chunk-7P5QRX5W.js +108 -0
  16. package/dist/chunk-7P5QRX5W.js.map +1 -0
  17. package/dist/chunk-A6BKSZQA.js +3 -0
  18. package/dist/chunk-A6BKSZQA.js.map +1 -0
  19. package/dist/{chunk-6PPIFDOQ.js → chunk-CNNLREHD.js} +67 -22
  20. package/dist/{chunk-6PPIFDOQ.js.map → chunk-CNNLREHD.js.map} +1 -1
  21. package/dist/{chunk-J4WKMKTP.js → chunk-DLVOHCTA.js} +2 -2
  22. package/dist/chunk-E4ZZILAK.js +15 -0
  23. package/dist/chunk-E4ZZILAK.js.map +1 -0
  24. package/dist/{chunk-TRQSP67X.js → chunk-IKBHHTIT.js} +2 -2
  25. package/dist/chunk-JTJIPNXY.js +2 -0
  26. package/dist/chunk-JTJIPNXY.js.map +1 -0
  27. package/dist/chunk-MBOLXKA2.js +6 -0
  28. package/dist/chunk-MBOLXKA2.js.map +1 -0
  29. package/dist/{inbox-O5UB3YQN.js → chunk-MTRWXRFF.js} +69 -4
  30. package/dist/chunk-MTRWXRFF.js.map +1 -0
  31. package/dist/{processes-7W27G3J3.js → chunk-NOF4HSGK.js} +3 -3
  32. package/dist/{processes-7W27G3J3.js.map → chunk-NOF4HSGK.js.map} +1 -1
  33. package/dist/chunk-QW2OYR3N.js +7 -0
  34. package/dist/chunk-QW2OYR3N.js.map +1 -0
  35. package/dist/{chunk-JXEPJ3YD.js → chunk-TC3BSUP7.js} +5 -5
  36. package/dist/chunk-VWTBHLMX.js +1 -0
  37. package/dist/chunk-WFHIIVLL.js +719 -0
  38. package/dist/chunk-WFHIIVLL.js.map +1 -0
  39. package/dist/chunk-WPPUOTY6.js +6 -0
  40. package/dist/chunk-WPPUOTY6.js.map +1 -0
  41. package/dist/cli.js +1 -1
  42. package/dist/cli.js.map +1 -1
  43. package/dist/{compaction-S5H6DTOR.js → compaction-BTUJJ3LJ.js} +4 -4
  44. package/dist/config-QXWKEQON.js +2 -0
  45. package/dist/config-mutator-53XGR2NT.js +2 -0
  46. package/dist/conflict-detector-D7VIYPDI.js +2 -0
  47. package/dist/conflict-detector-D7VIYPDI.js.map +1 -0
  48. package/dist/{context-planner-2RGHJHSO.js → context-planner-I7ID7XF3.js} +3 -3
  49. package/dist/debug-N3B5NVJU.js +2 -0
  50. package/dist/{discover-OATU7SHI.js → discover-XJXQXXIO.js} +2 -2
  51. package/dist/doctor-SEOUFHSL.js +2 -0
  52. package/dist/{editor-chat-FUBQLMAQ.js → editor-chat-RVLCKKB7.js} +2 -2
  53. package/dist/{heal-YP7QI7D3.js → heal-YEQYWX2L.js} +2 -2
  54. package/dist/inbox-BIN2S5XN.js +2 -0
  55. package/dist/index.d.ts +830 -100
  56. package/dist/index.js +2 -2
  57. package/dist/index.js.map +1 -1
  58. package/dist/{ingest-whatsapp-C3O5UPBX.js → ingest-whatsapp-LZE62INA.js} +2 -2
  59. package/dist/loader-5CA3O5GM.js +2 -0
  60. package/dist/loader-6IHUCPVO.js +2 -0
  61. package/dist/memory-extract-KRCHD3OP.js +2 -0
  62. package/dist/migrate-v2-QO7SLKIM.js +4 -0
  63. package/dist/migrate-v2-QO7SLKIM.js.map +1 -0
  64. package/dist/processes-RJ6RDP32.js +2 -0
  65. package/dist/program-WWCXMRU2.js +2 -0
  66. package/dist/{providers-WCQSKTNP.js → providers-WVCMIW67.js} +2 -2
  67. package/dist/query-HZYYRCLU.js +2 -0
  68. package/dist/recipes-EPLE2SNT.js +2 -0
  69. package/dist/registry-HM4ELSXS.js +2 -0
  70. package/dist/rotation-memo-GMTZPMNE.js +13 -0
  71. package/dist/rotation-memo-GMTZPMNE.js.map +1 -0
  72. package/dist/scheduler-XVFIK4XH.js +2 -0
  73. package/dist/{slack-D27BE37Q.js → slack-6P7ZJ32Y.js} +2 -2
  74. package/dist/store-BAPDZHXE.js +2 -0
  75. package/dist/{subagent-EPOT3QDM.js → subagent-WV7QKQ3L.js} +2 -2
  76. package/dist/{task-store-3NUWLYRY.js → task-store-RHISEU6V.js} +2 -2
  77. package/dist/{telegram-UWV67KWG.js → telegram-PIYDRM46.js} +2 -2
  78. package/dist/timers-5LPZN55J.js +2 -0
  79. package/dist/types-VECTMYTO.js +2 -0
  80. package/dist/types-VECTMYTO.js.map +1 -0
  81. package/dist/types-ZRLHHCGB.js +2 -0
  82. package/dist/types-ZRLHHCGB.js.map +1 -0
  83. package/dist/web/activity-graph.global.js +654 -0
  84. package/dist/web/activity-graph.global.js.map +1 -0
  85. package/dist/web/workflow-editor.global.js +1 -1
  86. package/dist/web/workflow-editor.global.js.map +1 -1
  87. package/dist/{whatsapp-KJ2NRKKQ.js → whatsapp-PFRDMUMT.js} +2 -2
  88. package/dist/{whatsapp-state-3ZVQ6MQD.js → whatsapp-state-XMTCSRXU.js} +2 -2
  89. package/dist/wiki-53W5NLZV.js +2 -0
  90. package/dist/wiki-53W5NLZV.js.map +1 -0
  91. package/dist/workspace-setup-FPGN5VNY.js +2 -0
  92. package/dist/workspace-setup-FPGN5VNY.js.map +1 -0
  93. package/package.json +3 -2
  94. package/dist/board-dashboard-KX2CNOSY.js.map +0 -1
  95. package/dist/chunk-34NG7UDX.js +0 -11
  96. package/dist/chunk-34NG7UDX.js.map +0 -1
  97. package/dist/chunk-ASSJ4Z6Z.js +0 -164
  98. package/dist/chunk-ASSJ4Z6Z.js.map +0 -1
  99. package/dist/chunk-BLUXL6HR.js +0 -854
  100. package/dist/chunk-BLUXL6HR.js.map +0 -1
  101. package/dist/chunk-BTAEYL3P.js +0 -72
  102. package/dist/chunk-BTAEYL3P.js.map +0 -1
  103. package/dist/chunk-DSCMCVTV.js +0 -7
  104. package/dist/chunk-DSCMCVTV.js.map +0 -1
  105. package/dist/chunk-HPDHNIA3.js +0 -2
  106. package/dist/chunk-IM376SD6.js +0 -13
  107. package/dist/chunk-IM376SD6.js.map +0 -1
  108. package/dist/chunk-LEIYC3BF.js +0 -104
  109. package/dist/chunk-LEIYC3BF.js.map +0 -1
  110. package/dist/chunk-LY7JGDV7.js +0 -6
  111. package/dist/chunk-LY7JGDV7.js.map +0 -1
  112. package/dist/chunk-U364GNQX.js +0 -719
  113. package/dist/chunk-U364GNQX.js.map +0 -1
  114. package/dist/chunk-WZEYII2S.js +0 -6
  115. package/dist/chunk-WZEYII2S.js.map +0 -1
  116. package/dist/config-UOHEG4BW.js +0 -2
  117. package/dist/config-mutator-KSEBWKJH.js +0 -2
  118. package/dist/debug-TGAP7E7W.js +0 -2
  119. package/dist/inbox-O5UB3YQN.js.map +0 -1
  120. package/dist/loader-BGWA7IRY.js +0 -2
  121. package/dist/loader-LBHKSXBK.js +0 -2
  122. package/dist/memory-extract-KKPKAOPH.js +0 -2
  123. package/dist/program-2MDELQLX.js +0 -2
  124. package/dist/query-FZAL7AQD.js +0 -2
  125. package/dist/recipes-LZ56TA3X.js +0 -2
  126. package/dist/registry-ONJSILH4.js +0 -2
  127. package/dist/scheduler-XRM2RJIV.js +0 -2
  128. package/dist/store-4UCO56K2.js +0 -2
  129. package/dist/timers-QB4KYNAE.js +0 -2
  130. package/dist/types-ECMEC5BZ.js +0 -2
  131. package/dist/types-TTB6DJUE.js +0 -2
  132. package/dist/usage-dashboard-FQGPPABD.js +0 -803
  133. package/dist/usage-dashboard-FQGPPABD.js.map +0 -1
  134. package/dist/wiki-OUYKM7MB.js +0 -2
  135. package/dist/workspace-setup-6GDBEORT.js +0 -2
  136. /package/dist/{agent-RDVGL2TW.js.map → agent-57MG4LBM.js.map} +0 -0
  137. /package/dist/{audit-Z47AQFFD.js.map → audit-JU3HWISB.js.map} +0 -0
  138. /package/dist/{chunk-HPDHNIA3.js.map → chunk-4YCH6IZV.js.map} +0 -0
  139. /package/dist/{chunk-J4WKMKTP.js.map → chunk-DLVOHCTA.js.map} +0 -0
  140. /package/dist/{chunk-TRQSP67X.js.map → chunk-IKBHHTIT.js.map} +0 -0
  141. /package/dist/{chunk-JXEPJ3YD.js.map → chunk-TC3BSUP7.js.map} +0 -0
  142. /package/dist/{config-UOHEG4BW.js.map → chunk-VWTBHLMX.js.map} +0 -0
  143. /package/dist/{compaction-S5H6DTOR.js.map → compaction-BTUJJ3LJ.js.map} +0 -0
  144. /package/dist/{config-mutator-KSEBWKJH.js.map → config-QXWKEQON.js.map} +0 -0
  145. /package/dist/{debug-TGAP7E7W.js.map → config-mutator-53XGR2NT.js.map} +0 -0
  146. /package/dist/{context-planner-2RGHJHSO.js.map → context-planner-I7ID7XF3.js.map} +0 -0
  147. /package/dist/{heal-YP7QI7D3.js.map → debug-N3B5NVJU.js.map} +0 -0
  148. /package/dist/{discover-OATU7SHI.js.map → discover-XJXQXXIO.js.map} +0 -0
  149. /package/dist/{loader-BGWA7IRY.js.map → doctor-SEOUFHSL.js.map} +0 -0
  150. /package/dist/{editor-chat-FUBQLMAQ.js.map → editor-chat-RVLCKKB7.js.map} +0 -0
  151. /package/dist/{loader-LBHKSXBK.js.map → heal-YEQYWX2L.js.map} +0 -0
  152. /package/dist/{memory-extract-KKPKAOPH.js.map → inbox-BIN2S5XN.js.map} +0 -0
  153. /package/dist/{ingest-whatsapp-C3O5UPBX.js.map → ingest-whatsapp-LZE62INA.js.map} +0 -0
  154. /package/dist/{program-2MDELQLX.js.map → loader-5CA3O5GM.js.map} +0 -0
  155. /package/dist/{providers-WCQSKTNP.js.map → loader-6IHUCPVO.js.map} +0 -0
  156. /package/dist/{query-FZAL7AQD.js.map → memory-extract-KRCHD3OP.js.map} +0 -0
  157. /package/dist/{recipes-LZ56TA3X.js.map → processes-RJ6RDP32.js.map} +0 -0
  158. /package/dist/{registry-ONJSILH4.js.map → program-WWCXMRU2.js.map} +0 -0
  159. /package/dist/{scheduler-XRM2RJIV.js.map → providers-WVCMIW67.js.map} +0 -0
  160. /package/dist/{store-4UCO56K2.js.map → query-HZYYRCLU.js.map} +0 -0
  161. /package/dist/{task-store-3NUWLYRY.js.map → recipes-EPLE2SNT.js.map} +0 -0
  162. /package/dist/{timers-QB4KYNAE.js.map → registry-HM4ELSXS.js.map} +0 -0
  163. /package/dist/{types-ECMEC5BZ.js.map → scheduler-XVFIK4XH.js.map} +0 -0
  164. /package/dist/{slack-D27BE37Q.js.map → slack-6P7ZJ32Y.js.map} +0 -0
  165. /package/dist/{types-TTB6DJUE.js.map → store-BAPDZHXE.js.map} +0 -0
  166. /package/dist/{subagent-EPOT3QDM.js.map → subagent-WV7QKQ3L.js.map} +0 -0
  167. /package/dist/{wiki-OUYKM7MB.js.map → task-store-RHISEU6V.js.map} +0 -0
  168. /package/dist/{telegram-UWV67KWG.js.map → telegram-PIYDRM46.js.map} +0 -0
  169. /package/dist/{workspace-setup-6GDBEORT.js.map → timers-5LPZN55J.js.map} +0 -0
  170. /package/dist/{whatsapp-KJ2NRKKQ.js.map → whatsapp-PFRDMUMT.js.map} +0 -0
  171. /package/dist/{whatsapp-state-3ZVQ6MQD.js.map → whatsapp-state-XMTCSRXU.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/agents/runtime.ts","../src/agents/error-map.ts","../src/agents/sessions.ts","../src/daemon/rate-limit.ts","../src/agents/context.ts","../src/agents/memory-store.ts","../src/agents/message-queue.ts","../src/agents/patterns.ts","../src/events/bus.ts","../src/agents/overage-status.ts","../src/agents/claude-code-quota.ts","../src/agents/prompt-size-tracker.ts","../src/agents/registry.ts"],"sourcesContent":["import { execa } from \"execa\"\nimport { execFile, spawn } from \"child_process\"\nimport { friendlyModelError, renderFriendlyError } from \"./error-map\"\nimport type { AgentDef } from \"@/daemon/config\"\n\n// --- Agent execution runtime ---\n// Routes agent tasks to the correct execution tier:\n// - claude-code: spawns claude CLI (subscription, full features)\n// - sdk: uses Claude Agent SDK (API key, programmatic)\n// - orchestrator: uses agentx's own agentic loop (any provider)\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 /** 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 /** 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 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 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 }\n }\n}\n\nexport interface AgentResponse {\n content: string\n error?: string\n tokensUsed?: number\n duration?: number\n claudeSessionId?: string\n usage?: TokenUsage // Real token counts from Claude's JSON output\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}\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): 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 return args\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 } {\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 const billedModel: string | undefined =\n (typeof data.model === \"string\" && data.model) ||\n (typeof data.message?.model === \"string\" && data.message.model) ||\n undefined\n\n return {\n text: data.result || data.content || \"\",\n sessionId: data.session_id,\n usage,\n billedModel,\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): 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 args = buildClaudeArgs(agent, prompt, false, resumeSessionId, task.model, task.systemPromptAppend)\n\n try {\n const timeoutMs = Math.max(60_000, (agent.maxExecutionMinutes ?? 20) * 60_000)\n const { stdout, stderr, exitCode } = await new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve, reject) => {\n const proc = execFile(\"claude\", args, {\n cwd: agent.workspace,\n timeout: timeoutMs,\n maxBuffer: 10 * 1024 * 1024,\n env: { ...process.env, HOME: process.env.HOME || \"/home/\" + (process.env.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 })\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 })\n\n if (!stdout && exitCode !== 0) {\n // exit 143 = 128+SIGTERM → our own `timeout` killed the process. Surface\n // that as a recognizable \"timed out after Xm\" message so operators can\n // bump agent.maxExecutionMinutes instead of guessing at an opaque 143.\n let errMsg: string\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 {\n errMsg = stderr?.trim() || `Claude Code exited with code ${exitCode}`\n }\n return {\n content: \"\",\n error: renderFriendlyError(friendlyModelError(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 error: renderFriendlyError(friendlyModelError(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 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): 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 args = buildClaudeArgs(agent, prompt, true, resumeSessionId, task.model, task.systemPromptAppend)\n\n let fullText = \"\"\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 /** 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 proc = execa(\"claude\", args, {\n cwd: agent.workspace,\n timeout: streamTimeoutMs,\n reject: false,\n env: process.env,\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\n // Parse stream-json output line by line\n if (proc.stdout) {\n let lineBuffer = \"\"\n\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n lineBuffer += chunk.toString()\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 // 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(fullText.length)\n if (delta) {\n fullText = block.text\n onDelta(delta, fullText)\n }\n }\n }\n }\n\n // \"content_block_delta\" for streaming text\n if (event.type === \"content_block_delta\" && event.delta?.text) {\n fullText += event.delta.text\n onDelta(event.delta.text, fullText)\n }\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 > fullText.length) {\n const delta = resultText.slice(fullText.length)\n fullText = resultText\n if (delta) onDelta(delta, fullText)\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 if (typeof event.session_id === \"string\") streamSessionId = event.session_id\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 }\n } catch {\n // Not JSON — could be raw text output, append it\n if (line.trim() && !line.startsWith(\"{\")) {\n fullText += line + \"\\n\"\n onDelta(line + \"\\n\", fullText)\n }\n }\n }\n })\n }\n\n const result = await proc\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 }\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 {\n errMsg = stderr.trim() || `Claude Code exited with code ${r.exitCode ?? \"unknown\"}`\n }\n return {\n content: \"\",\n error: renderFriendlyError(friendlyModelError(errMsg)),\n duration: Date.now() - start,\n }\n }\n\n if (streamApiError) {\n return {\n content: \"\",\n error: renderFriendlyError(friendlyModelError(streamApiError)),\n duration: Date.now() - start,\n usage: streamUsage,\n billedModel: streamBilledModel,\n claudeSessionId: streamSessionId,\n }\n }\n\n return {\n content: fullText,\n duration: Date.now() - start,\n usage: streamUsage,\n billedModel: streamBilledModel,\n claudeSessionId: streamSessionId,\n }\n } catch (error: any) {\n return {\n content: fullText || \"\",\n error: renderFriendlyError(friendlyModelError(error.message)),\n duration: Date.now() - start,\n usage: streamUsage,\n billedModel: streamBilledModel,\n claudeSessionId: streamSessionId,\n }\n }\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 error: `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 */\nexport async function executeOrchestrator(\n agent: AgentDef,\n task: AgentTask,\n apiKey?: string,\n historyContext?: string,\n): Promise<AgentResponse> {\n const start = Date.now()\n\n try {\n const { generate } = await import(\"@/agent\")\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 const result = await generate({\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 })\n\n return {\n content: result.content || \"Done.\",\n tokensUsed: result.tokensUsed,\n duration: Date.now() - start,\n }\n } catch (error: any) {\n return {\n content: \"\",\n error: `Orchestrator error: ${error.message}`,\n duration: Date.now() - start,\n }\n }\n}\n\n/**\n * Route a task to the correct execution tier.\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): Promise<AgentResponse> {\n switch (agent.tier) {\n case \"claude-code\":\n if (onDelta) {\n return executeClaudeCodeStreaming(agent, task, onDelta, historyContext, resumeSessionId, onEvent)\n }\n return executeClaudeCode(agent, task, historyContext, resumeSessionId)\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 apiKey = providers[providerName]?.apiKey\n return executeOrchestrator(agent, task, apiKey, historyContext)\n }\n\n default:\n return {\n content: \"\",\n error: `Unknown tier: ${agent.tier}`,\n }\n }\n}\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 | \"auth\"\n | \"permission\"\n | \"rate_limit\"\n | \"overloaded\"\n | \"context_too_large\"\n | \"invalid_request\"\n | \"upstream_api\"\n | \"timeout\"\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 // 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 if (errMessage && /out of (extra )?usage|out of credit|credit(s)? exhausted|insufficient credit/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","import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- Conversation session store ---\n// One session per agent + channel + chatId + day.\n// For claude-code tier: stores the Claude session ID so we can --resume it.\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}\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 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 /** Total billable input of the LAST turn on this claudeSessionId\n * (inputTokens + cacheReadTokens + cacheCreateTokens). Used to detect\n * tier-2 hits (>200K) from the prior turn so we can rotate before the\n * next turn pays the 1.5× multiplier again. */\n lastTurnInputTokens?: 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 (long timeout → prompt cache survives work\n * pauses) against \"fresh context\" (short timeout → new session rebuilds the\n * prompt from scratch). Agents on Opus pay ~$0.50 cache-create per task,\n * so the higher the timeout, the less often that cost recurs. 45 min keeps\n * cache warm for an active conversation but stops an all-day chat from\n * snowballing a single Claude CLI session into 500K+ tokens of replay. */\nconst DEFAULT_STALE_SESSION_MINUTES = 45\n/** Default hard cap on turns per Claude session. Claude CLI `--resume`\n * replays the entire prior session (every tool result, every file read)\n * on each turn, so cache-read grows linearly. 15 turns keeps the replay\n * under ~200K for most agents; rotate after that and seed the next\n * session from the compacted summary + recent-messages history. */\nconst DEFAULT_MAX_TURNS_PER_SESSION = 15\n/** Default tier-2 trigger. Claude bills tier-2 (1.5× rate) when a single\n * request's total input exceeds 200K. Rotating at 180K leaves headroom\n * for the next turn's additions before we re-enter the multiplier. */\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/** 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/** 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\n constructor(\n baseDir: string = process.cwd(),\n opts: {\n staleMinutes?: number\n maxTurnsPerSession?: number\n tierTwoThresholdTokens?: 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 }\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\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: new Date().toISOString(),\n }\n\n this.cache.set(key, session)\n this.save(session)\n return session\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 * 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 /**\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(agentId: string, channel: string, chatId: string): string {\n const session = this.getSession(agentId, channel, chatId)\n if (session.messages.length === 0) return \"\"\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 session.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 /**\n * Check if a Claude 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 isSessionStale(agentId: string, channel: string, chatId: string): boolean {\n const session = this.getSession(agentId, channel, chatId)\n if (!session.claudeSessionId) return false\n const elapsed = 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)\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 claudeSessionId, 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 the stored Claude session ID 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 claudeSessionId.\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.turnCount\n delete session.lastTurnInputTokens\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) return false\n return (session.turnCount ?? 0) >= this.maxTurnsPerSession\n }\n\n /**\n * Check whether the LAST turn on this session pushed total input past\n * the tier-2 threshold. If yes, rotate so we don't pay the 1.5×\n * multiplier again on the next turn.\n */\n shouldRotateByTierTwo(agentId: string, channel: string, chatId: string): boolean {\n const session = this.getSession(agentId, channel, chatId)\n if (!session.claudeSessionId) return false\n return (session.lastTurnInputTokens ?? 0) >= this.tierTwoThresholdTokens\n }\n\n /**\n * Record a completed turn's token usage and bump the turn counter.\n * Called after a successful Claude response returns with usage info.\n */\n recordTurnUsage(\n agentId: string,\n channel: string,\n chatId: string,\n usage: { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreateTokens: 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 session.updatedAt = new Date().toISOString()\n this.save(session)\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 getMaxTurnsPerSession(): number { return this.maxTurnsPerSession }\n getTierTwoThresholdTokens(): number { return this.tierTwoThresholdTokens }\n\n /**\n * Trim session to stay within limits.\n */\n private trim(session: Session): void {\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\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 memory: 600,\n history: 1200,\n \"cross-chat\": 800,\n wiki: 1000,\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 // 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 // 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 // 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 › Noqta › 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.2,\n maxTokens: budget(\"skills\", 2000),\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.3,\n maxTokens: budget(\"patterns\", 400),\n content: input.patternContext,\n tags: [\"patterns\", \"behavioral\", \"self-improving\"],\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.5,\n maxTokens: budget(\"memory\", 600),\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 // 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 // 8. Wiki knowledge\n if (input.wikiContext) {\n layers.push({\n name: \"wiki\",\n priority: 8,\n maxTokens: budget(\"wiki\", 1000),\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","import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { buildIndexCached, scoreAll } from \"../memory/bm25\"\n\n// --- Persistent agent memory store ---\n// JSONL-based. One file per agent: .agentx/memory/{agentId}.jsonl\n// Extracted by Haiku post-conversation, injected pre-conversation.\n\nexport interface MemoryFact {\n id: string\n agentId: string\n category: \"fact\" | \"secret\" | \"preference\" | \"commitment\" | \"task-state\"\n content: string\n keywords: string[]\n source: { channel: string; chatId: string; sender: string; date: string }\n createdAt: string\n expiresAt?: string\n // Spaced repetition fields\n accessCount?: number\n lastAccessed?: string\n nextReview?: string\n /** Review interval in days — doubles on each successful recall */\n intervalDays?: number\n}\n\nconst MAX_MEMORIES = 200\nconst TASK_STATE_TTL_DAYS = 7\n\n\nexport class MemoryStore {\n private memoryDir: string\n\n constructor(baseDir: string = process.cwd()) {\n this.memoryDir = resolve(baseDir, \".agentx/memory\")\n if (!existsSync(this.memoryDir)) {\n mkdirSync(this.memoryDir, { recursive: true })\n }\n }\n\n private filePath(agentId: string): string {\n return resolve(this.memoryDir, `${agentId}.jsonl`)\n }\n\n addMemory(agentId: string, fact: Omit<MemoryFact, \"id\" | \"createdAt\">): void {\n const entry: MemoryFact = {\n ...fact,\n id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,\n createdAt: new Date().toISOString(),\n }\n appendFileSync(this.filePath(agentId), JSON.stringify(entry) + \"\\n\")\n }\n\n getAll(agentId: string): MemoryFact[] {\n const file = this.filePath(agentId)\n if (!existsSync(file)) return []\n\n try {\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\").filter(l => l.trim())\n return lines.map(l => {\n try { return JSON.parse(l) as MemoryFact } catch { return null }\n }).filter((m): m is MemoryFact => m !== null)\n } catch {\n return []\n }\n }\n\n findRelevant(message: string, agentId: string, limit: number = 8): MemoryFact[] {\n const memories = this.getAll(agentId)\n if (memories.length === 0) return []\n\n // BM25 over keywords + content (cached)\n const docs = memories.map((m) => `${m.keywords.join(\" \")} ${m.content}`)\n const cachePath = resolve(this.memoryDir, `${agentId}_bm25_cache.json`)\n const index = buildIndexCached(docs, cachePath)\n const bm25Scores = scoreAll(message, index)\n const scoreMap = new Map(bm25Scores.map((r) => [r.docIndex, r.score]))\n\n const scored = memories.map((m, i) => {\n let s = scoreMap.get(i) ?? 0\n // Category boosts (preserved from original)\n if (m.category === \"secret\") s += 2\n if (m.category === \"commitment\") s += 1\n return { memory: m, score: s }\n })\n\n const matched = scored.filter((s) => s.score > 0)\n if (matched.length === 0) return memories.slice(-limit) // fallback: recent\n\n const selected = matched\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n .map((s) => s.memory)\n\n // Track access for spaced repetition (fire-and-forget)\n if (selected.length > 0) {\n try { this.recordAccess(agentId, selected.map(m => m.id)) } catch {}\n }\n\n return selected\n }\n\n buildContext(memories: MemoryFact[]): string {\n if (memories.length === 0) return \"\"\n\n const lines = [\"[Agent Memory — persistent facts from past conversations]\"]\n let chars = lines[0].length\n\n for (const m of memories) {\n const isDM = !m.source.chatId.startsWith(\"-\") && /^\\d+$/.test(m.source.chatId)\n const scope = isDM ? \"DM\" : m.source.chatId\n const line = `- [${m.category}] ${m.content} (${scope}, ${m.source.date})`\n\n if (chars + line.length > 2400) break\n lines.push(line)\n chars += line.length\n }\n\n lines.push(\"[End Memory]\")\n return lines.join(\"\\n\")\n }\n\n /**\n * Record that memories were accessed (for spaced repetition tracking).\n */\n recordAccess(agentId: string, memoryIds: string[]): void {\n if (memoryIds.length === 0) return\n const memories = this.getAll(agentId)\n const idSet = new Set(memoryIds)\n let changed = false\n\n for (const m of memories) {\n if (idSet.has(m.id)) {\n m.accessCount = (m.accessCount || 0) + 1\n m.lastAccessed = new Date().toISOString()\n // Double the review interval (spaced repetition: 1 → 2 → 4 → 8 → 16 days)\n const currentInterval = m.intervalDays || 1\n m.intervalDays = Math.min(currentInterval * 2, 30)\n m.nextReview = new Date(Date.now() + m.intervalDays * 86400000).toISOString()\n changed = true\n }\n }\n\n if (changed) {\n writeFileSync(\n this.filePath(agentId),\n memories.map(m => JSON.stringify(m)).join(\"\\n\") + \"\\n\",\n )\n }\n }\n\n /**\n * Get memories due for review (spaced repetition).\n * Returns facts whose nextReview date has passed or that have never been reviewed.\n */\n getDueForReview(agentId: string, limit: number = 5): MemoryFact[] {\n const memories = this.getAll(agentId)\n const now = Date.now()\n\n return memories\n .filter(m => {\n // Skip task-state (ephemeral)\n if (m.category === \"task-state\") return false\n // Never reviewed → due\n if (!m.nextReview) return true\n // Past review date → due\n return new Date(m.nextReview).getTime() <= now\n })\n .sort((a, b) => {\n // Prioritize: never reviewed, then oldest nextReview, then most accessed\n if (!a.nextReview && b.nextReview) return -1\n if (a.nextReview && !b.nextReview) return 1\n if (a.nextReview && b.nextReview) {\n return new Date(a.nextReview).getTime() - new Date(b.nextReview).getTime()\n }\n return (a.accessCount || 0) - (b.accessCount || 0)\n })\n .slice(0, limit)\n }\n\n /**\n * Build a recall prompt for memories due for review.\n * Injected into heartbeat context so the agent proactively recalls facts.\n */\n buildRecallContext(agentId: string): string {\n const due = this.getDueForReview(agentId, 5)\n if (due.length === 0) return \"\"\n\n const lines = [\"[Memory Recall — facts due for review, verify they are still accurate]\"]\n for (const m of due) {\n const age = Math.round((Date.now() - new Date(m.createdAt).getTime()) / 86400000)\n lines.push(`- [${m.category}] ${m.content} (${age}d old, accessed ${m.accessCount || 0}x)`)\n }\n lines.push(\"[If any fact is outdated, note the correction. Otherwise, acknowledge recall.]\")\n return lines.join(\"\\n\")\n }\n\n /**\n * Check if a substantially similar memory already exists.\n */\n hasSimilar(agentId: string, content: string): boolean {\n const memories = this.getAll(agentId)\n const contentLower = content.toLowerCase()\n const contentWords = contentLower.split(/\\s+/).filter(w => w.length > 3)\n\n return memories.some(m => {\n const mLower = m.content.toLowerCase()\n // Check for high word overlap\n const overlap = contentWords.filter(w => mLower.includes(w)).length\n return overlap >= contentWords.length * 0.7\n })\n }\n\n /**\n * Prune old/expired memories. Cap at MAX_MEMORIES per agent.\n */\n prune(agentId: string): void {\n let memories = this.getAll(agentId)\n if (memories.length === 0) return\n\n const now = Date.now()\n const ttlMs = TASK_STATE_TTL_DAYS * 24 * 60 * 60 * 1000\n\n // Remove expired task-state memories\n memories = memories.filter(m => {\n if (m.category === \"task-state\" && m.expiresAt) {\n return new Date(m.expiresAt).getTime() > now\n }\n if (m.category === \"task-state\" && !m.expiresAt) {\n return now - new Date(m.createdAt).getTime() < ttlMs\n }\n return true\n })\n\n // Cap at MAX_MEMORIES (keep most recent)\n if (memories.length > MAX_MEMORIES) {\n memories = memories.slice(-MAX_MEMORIES)\n }\n\n // Rewrite file\n const file = this.filePath(agentId)\n writeFileSync(file, memories.map(m => JSON.stringify(m)).join(\"\\n\") + \"\\n\")\n }\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}\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\n const combined: QueuedMessage = {\n text: messages.map((m) =>\n `[${m.sender}, ${new Date(m.timestamp).toISOString().slice(11, 16)}]: ${m.text}`\n ).join(\"\\n\"),\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 }\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-20250514\"\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\"\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 task is about to be dispatched to the agent runtime. */\n \"task:started\": {\n agentId: string\n channel: string\n chatId: string\n messagePreview: string // first ~200 chars\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 \"task:completed\": {\n agentId: string\n channel: string\n chatId: string\n durationMs: number\n error?: string\n inputTokens?: number\n outputTokens?: number\n cacheReadTokens?: number\n cacheCreateTokens?: number\n at: string\n }\n\n /** Claude session rotation fired (stale / max-turns / tier-2). */\n \"session:rotated\": {\n agentId: string\n channel: string\n chatId: string\n reason: \"stale\" | \"max-turns\" | \"tier-2\"\n lastTurnInputTokens?: number\n at: string\n }\n}\n\ntype EventName = keyof AgentXEvents\ntype Listener<E extends EventName> = (payload: AgentXEvents[E]) => void\n\nclass TypedEventBus {\n private inner = new EventEmitter()\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 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 return this.inner.emit(event, payload)\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. */\n removeAllListeners(): this {\n this.inner.removeAllListeners()\n return this\n }\n}\n\nlet _instance: TypedEventBus | undefined\nexport function getEventBus(): TypedEventBus {\n if (!_instance) _instance = new TypedEventBus()\n return _instance\n}\n\nexport type { TypedEventBus }\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-hasanah 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 Max-plan dispatch budget ---\n//\n// Anthropic enforces soft per-window caps on Max-plan message/request counts\n// (the published ballpark is ~45/5h for Pro, ~225/5h for Max 5×, ~900/5h for\n// Max 20×; hourly caps exist too). When the fleet burns through these, new\n// cold dispatches start failing with \"out of extra usage\" (overage gate,\n// handled separately) or silently degraded responses.\n//\n// This module tracks dispatch timestamps across ALL claude-code-tier agents\n// (they share one OAuth, so their usage pools together) in a simple ring\n// buffer and exposes:\n//\n// recordClaudeCodeDispatch() — call when a dispatch is about to fire\n// getClaudeCodeUsage() — counts in the last hour / last 5h\n// preflightQuotaGate(…) — optional short-circuit for cold dispatches\n// when usage is past the configured ceiling\n//\n// Values are deliberately conservative defaults (Max 5× ballpark). Tune via\n// setDispatchBudget() from DaemonConfig at daemon startup. When thresholds\n// are `undefined` the gate is a no-op and only the counters track (pure\n// observability, no behavior change).\n\nexport interface DispatchBudget {\n /** Soft hourly cap. Logs a warning at warnRatio, gates cold dispatches at 100%. */\n maxPerHour?: number\n /** Soft rolling-5-hour cap. Same semantics as maxPerHour. */\n maxPer5h?: number\n /** Warn when usage >= warnRatio × max (default 0.8). */\n warnRatio?: number\n}\n\nconst DEFAULT_BUDGET: Required<Pick<DispatchBudget, \"warnRatio\">> & DispatchBudget = {\n // No hard defaults — opt-in by config. Keep warnRatio so usage logs still\n // emit at 80% when the operator DOES set a cap.\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 clearDispatchHistory(): void { timestamps.length = 0 }\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\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}\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: budget.maxPerHour,\n maxPer5h: budget.maxPer5h,\n }\n if (budget.maxPerHour && budget.maxPerHour > 0) out.hourlyRatio = lastHour / budget.maxPerHour\n if (budget.maxPer5h && budget.maxPer5h > 0) out.fiveHourRatio = last5h / budget.maxPer5h\n return out\n}\n\n/**\n * Returns null when dispatch is allowed; returns an abort struct when usage\n * is past 100% of a configured cap. Warm sessions are always allowed through\n * (they replay via cache_read and are cheap) — only cold dispatches get\n * gated, same philosophy as preflightOverageGate.\n *\n * The function DOES NOT mutate state (does not increment counters). Call\n * `recordClaudeCodeDispatch()` separately once the caller commits to the\n * dispatch.\n */\nexport function preflightQuotaGate(hasWarmSession: boolean, now: number = Date.now()):\n | { abort: true; reason: string; message: string; usage: ClaudeCodeUsage }\n | null {\n const usage = getClaudeCodeUsage(now)\n if (hasWarmSession) return null\n if (budget.maxPerHour && usage.lastHour >= budget.maxPerHour) {\n return {\n abort: true,\n reason: \"hourly_cap\",\n message:\n `Claude-code fleet dispatch-budget hit: ${usage.lastHour}/${budget.maxPerHour} in the last hour. ` +\n `Cold dispatches are being held back to preserve warm-session headroom. ` +\n `Raise agents.budget.maxPerHour in DaemonConfig if this is a false positive.`,\n usage,\n }\n }\n if (budget.maxPer5h && usage.last5h >= budget.maxPer5h) {\n return {\n abort: true,\n reason: \"five_hour_cap\",\n message:\n `Claude-code fleet dispatch-budget hit: ${usage.last5h}/${budget.maxPer5h} in the last 5h. ` +\n `Cold dispatches are being held back to preserve warm-session headroom. ` +\n `Raise agents.budget.maxPer5h in DaemonConfig if this is a false positive.`,\n usage,\n }\n }\n return null\n}\n\n/**\n * Returns a warning string when usage has crossed the warnRatio threshold,\n * otherwise null. Caller should log and clear the ratcheting state (we don't\n * suppress duplicates here — suppression is a caller concern).\n */\nexport function warnIfNearingCap(now: number = Date.now()): string | null {\n const usage = getClaudeCodeUsage(now)\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 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 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 type { DaemonConfig, AgentDef } from \"@/daemon/config\"\nimport { executeTask, type AgentTask, type AgentResponse, type StreamCallback, type AgentPeer } from \"./runtime\"\nimport { SessionStore, detectLongMemoryHint } from \"./sessions\"\nimport { WikiHub } from \"@/wiki\"\nimport { RateLimiter } from \"@/daemon/rate-limit\"\nimport { TokenTracker } from \"@/daemon/token-tracker\"\nimport { buildAgentContext, type ContextInput } from \"./context\"\nimport { Classifier, GraphStore, type ClassifyResult } from \"@/graph\"\nimport { HandoverStore } from \"@/channels/handover-store\"\nimport { MemoryStore } from \"./memory-store\"\nimport { extractMemories } from \"./memory-extract\"\nimport { MessageQueue, type QueueMode, type QueuedMessage } from \"./message-queue\"\nimport { loadBootstrapFiles, buildBootstrapContext, detectSoulSwitch, listSoulProfiles } from \"./bootstrap\"\nimport { PatternStore, extractPatterns } from \"./patterns\"\nimport { loadReferences, renderReferences } from \"./references/loader\"\nimport { loadRecipes, resolveRecipes, type RecipeIndex } from \"./references/recipes\"\nimport type { ReferenceIndex } from \"./references/types\"\nimport { getEventBus } from \"@/events/bus\"\nimport type { LandscapeBuilder } from \"./landscape\"\nimport { preflightOverageGate } from \"./overage-status\"\nimport { preflightQuotaGate, recordClaudeCodeDispatch, warnIfNearingCap, setDispatchBudget } from \"./claude-code-quota\"\nimport { promptSizeKey, recordPromptSize, warnIfPromptGrowing } from \"./prompt-size-tracker\"\nimport { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- Agent Registry: lifecycle management + concurrency control ---\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 /** First 200 chars of the user message, for display. */\n messagePreview: 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}\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\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 /** 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\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 */\nfunction buildWikiQueryHint(agentWiki: ReturnType<WikiHub[\"getAgentWiki\"]>, agentId: string): string {\n let articleCount = 0\n try {\n articleCount = agentWiki.listArticles(agentId).length\n } catch {\n return \"\"\n }\n if (articleCount === 0) return \"\"\n const catalogPath = resolve(agentWiki.baseDir, \"_index.md\")\n if (!existsSync(catalogPath)) return \"\"\n const cli = process.argv[1] || \"dist/cli.js\"\n const wikiDir = agentWiki.baseDir.replace(new RegExp(`/agents/${agentId}(/[^/]+)?$`), \"\")\n return [\n \"[Institutional Wiki — cross-agent source of truth]\",\n `A curated wiki with ${articleCount} article${articleCount === 1 ? \"\" : \"s\"} grouped by type: person, project, place, concept, event, decision, pattern.`,\n \"\",\n \"The wiki is the canonical source for: who people are (team, clients, agents), past events (incidents, deploys), decisions and their reasoning, documented procedures, patterns.\",\n \"\",\n \"BEFORE you grep the workspace, search memory, or answer from your own prior context — if the question is about who / what happened / what we decided / how we do X, call:\",\n ` node ${cli} wiki query \"the user's question\" --dir ${wikiDir} --agent ${agentId}`,\n \"\",\n \"It walks the catalog + wikilink graph and returns a cited answer. Your workspace memory is local to you; the wiki is shared and authoritative. Prefer the wiki for institutional questions; prefer your workspace for code-level questions.\",\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 */\nfunction makeStreamEventFormatter(): (event: any) => string {\n let textSeen = \"\"\n return (event: any): string => {\n if (!event || typeof event !== \"object\") return \"\"\n const t = event.type\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 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 private messageQueue: MessageQueue\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 /** 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 })\n this.wikiHub = new WikiHub(undefined, undefined, \"unified\")\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 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 /** 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 /**\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 \"nadia\" in prose or \"devops-mtgl\" quoted in a reply to trigger\n * agents spuriously. Intentional handoffs still work because agents\n * write explicit `@noqta_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 /**\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 /**\n * Execute a task on an agent. Respects maxConcurrent limit.\n */\n async execute(task: AgentTask, onDelta?: StreamCallback): Promise<AgentResponse> {\n const state = this.agents.get(task.agentId)\n if (!state) {\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 qChannel = task.context?.channel || \"api\"\n const qChatId = task.context?.chatId || task.context?.group || task.context?.sender || \"default\"\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 if (qChannel === \"api\") {\n const start = Date.now()\n const maxWaitMs = 25 * 60_000\n const pollIntervalMs = 500\n while (state.activeTasks >= state.def.maxConcurrent) {\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 {\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 })\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 this.log(`[${task.agentId}] busy, message queued (mode: ${queued}, pending: ${pending})`)\n return {\n content: \"\",\n error: `__queued__:${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 if (!rateResult.ok) {\n this.log(`[${task.agentId}] ${rateResult.reason}`)\n return { content: \"\", error: rateResult.reason }\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 channel: task.context?.channel || \"api\",\n chatId: task.context?.chatId || task.context?.group,\n sender: task.context?.sender,\n startedAt: new Date(),\n }\n state.runningTasks.push(runningTask)\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 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 const formatted = formatter(event)\n if (formatted) pushToBuffer(formatted)\n }\n }\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})`)\n\n // Build conversation history for session continuity\n const channel = task.context?.channel || \"api\"\n const chatId = task.context?.chatId || task.context?.group || task.context?.sender || \"default\"\n const senderName = task.context?.sender || \"User\"\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 getEventBus().emit(\"task:started\", {\n agentId: task.agentId,\n channel,\n chatId,\n messagePreview: (task.message || \"\").slice(0, 200),\n at: new Date(taskStartedAt).toISOString(),\n })\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 let intent: ClassifyResult | undefined\n if (this.classifier && channel !== \"a2a\") {\n try {\n intent = (await this.classifier.classify({\n text: task.message,\n channel,\n sender: task.context?.sender,\n agentId: task.agentId,\n })) || undefined\n } catch (e: any) {\n this.log(`[classifier] classify failed for ${task.agentId}: ${e?.message || e}`)\n }\n }\n\n // Wiki context — Phase 3 Farzapedia alignment: instead of preloading BM25\n // hits (the old shallow-RAG path), we inject a short pointer to the\n // `agentx wiki query` tool. The agent decides WHEN institutional knowledge\n // matters and invokes the agentic query itself — walking _index.md, picking\n // candidates, following wikilinks 2–3 hops. Zero retrieval cost on messages\n // that don't need it.\n const agentWiki = this.wikiHub.getAgentWiki(task.agentId)\n const wikiContext = buildWikiQueryHint(agentWiki, task.agentId)\n\n // Load persistent agent memory (cross-session facts)\n const relevantMemories = this.memoryStore.findRelevant(task.message, task.agentId, 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\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 try {\n const { loadLocalSkills, getAutoInjectSkills } = await import(\"@/agent/skills/loader\")\n const skills = await loadLocalSkills(state.def.workspace)\n skillInjection = getAutoInjectSkills(skills, task.message)\n } catch {\n // Skill loading is optional\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 : 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)) {\n this.log(`[${task.agentId}] session stale for ${channel}:${chatId}, starting fresh`)\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 prior turn pushed total input past the tier-2 threshold (>200K billed\n // at 1.5×), rotate before paying the multiplier again. Claude CLI --resume\n // replays every past tool result, so one bloated turn keeps billing\n // tier-2 indefinitely until we drop the session.\n if (resumeSessionId && this.sessions.shouldRotateByTierTwo(task.agentId, channel, chatId)) {\n const lastTokens = this.sessions.getLastTurnInputTokens(task.agentId, channel, chatId)\n this.log(`[${task.agentId}] tier-2 rotation for ${channel}:${chatId} (last turn: ${lastTokens} input tokens ≥ ${this.sessions.getTierTwoThresholdTokens()})`)\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 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 // 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 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 const sessionHistory = !resumeSessionId\n ? this.sessions.buildHistoryContext(task.agentId, channel, chatId)\n : undefined\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 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 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 let crossChatContext = 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 \"Tarek Ksibi\" 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\" =\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 if (strategy === \"planner\") {\n try {\n const { planContext } = await import(\"./context-planner\")\n const plan = await 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 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 const systemPromptAppend = [\n state.def.systemPrompt || \"\",\n bootstrapContextText || \"\",\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 landscape: 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: patternContext || undefined,\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 wikiContext,\n handoverNote: this.buildHandoverNote(task.agentId, channel, chatId),\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 const historyContext = buildAgentContext(contextInput)\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 const taskWithSystemPrompt: AgentTask = { ...task, systemPromptAppend }\n\n let finalResponse: AgentResponse | undefined\n try {\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 our own dispatch-budget counters say the\n // fleet has burned through the hourly or 5-hour cap. Warm\n // sessions still pass; cold 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 if (state.def.tier === \"claude-code\") {\n const hasWarmSession = Boolean(resumeSessionId)\n const gates = [\n preflightOverageGate(hasWarmSession),\n preflightQuotaGate(hasWarmSession),\n ]\n const abort = gates.find((g) => g && g.abort)\n if (abort) {\n state.errors++\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 finalResponse = preflightResponse\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 }\n\n const response = await executeTask(state.def, taskWithSystemPrompt, this.providers, onDelta, historyContext, resumeSessionId, onEvent)\n finalResponse = response\n\n getEventBus().emit(\"task:completed\", {\n agentId: task.agentId,\n channel,\n chatId,\n durationMs: Date.now() - taskStartedAt,\n error: response.error || undefined,\n inputTokens: response.usage?.inputTokens,\n outputTokens: response.usage?.outputTokens,\n cacheReadTokens: response.usage?.cacheReadTokens,\n cacheCreateTokens: response.usage?.cacheCreateTokens,\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\n // Store Claude session ID for future --resume\n if (response.claudeSessionId) {\n this.sessions.setClaudeSessionId(task.agentId, channel, chatId, response.claudeSessionId)\n }\n\n // Record this turn's usage so next task can decide whether to rotate:\n // tracks turnCount + lastTurnInputTokens (input + cacheRead + cacheCreate).\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.usage) {\n this.sessions.recordTurnUsage(task.agentId, channel, chatId, response.usage)\n }\n\n // Tier-2 warning: Claude bills at 1.5× when a single turn's total\n // input crosses 200K. Surface it visibly so operators don't need to\n // read raw usage JSON to notice. Next turn will auto-rotate (see\n // shouldRotateByTierTwo), but logging THIS turn keeps it observable.\n if (response.usage) {\n const totalInput =\n (response.usage.inputTokens || 0) +\n (response.usage.cacheReadTokens || 0) +\n (response.usage.cacheCreateTokens || 0)\n if (totalInput >= this.sessions.getTierTwoThresholdTokens()) {\n this.log(`[${task.agentId}] TIER-2 HIT on ${channel}:${chatId}: ${totalInput} total input tokens (input=${response.usage.inputTokens}, cacheRead=${response.usage.cacheReadTokens}, cacheCreate=${response.usage.cacheCreateTokens}) — next turn will rotate`)\n }\n }\n\n // Wiki: export conversation as raw entry for later absorption\n if (response.content.length > 50) {\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 })\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)` : \"\"),\n )\n }\n\n return response\n } catch (error: any) {\n state.errors++\n this.log(`[${task.agentId}] unexpected error: ${error.message}`)\n finalResponse = { content: \"\", error: error.message }\n return finalResponse\n } finally {\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\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 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)`)\n // Re-execute each queued message as a new task\n for (const qm of queued) {\n this.execute({\n message: qm.text,\n agentId: task.agentId,\n context: (qm.originalContext as AgentTask[\"context\"]) || {\n channel: qm.channel,\n sender: qm.sender,\n chatId: qm.chatId,\n },\n }).catch((e) => {\n this.log(`[${task.agentId}] queued message failed: ${e.message}`)\n })\n }\n })\n .catch((e) => {\n this.log(`[${task.agentId}] queue flush failed: ${e.message}`)\n })\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 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: 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 /**\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 * 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"],"mappings":"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","names":["execa","execFile","friendlyModelError","raw","source","jsonMatch","parsed","inner","errType","errMessage","statusMatch","status","renderFriendlyError","f","buildPrompt","agent","task","historyContext","parts","ctx","envLines","isGitLab","isTelegram","peer","handle","role","buildClaudeArgs","prompt","streaming","resumeSessionId","modelOverride","systemPromptAppend","args","model","extractClaudeIsError","stdout","data","parseClaudeJsonOutput","usage","billedModel","executeClaudeCode","start","timeoutMs","stderr","exitCode","resolve","reject","execFile","error","errMsg","renderFriendlyError","friendlyModelError","apiErrorInStdout","parsed","executeClaudeCodeStreaming","onDelta","onEvent","fullText","streamBilledModel","streamUsage","streamSessionId","streamApiError","streamTimeoutMs","proc","execa","lineBuffer","chunk","lines","line","event","block","delta","resultText","result","r","executeSdk","apiKey","sdk","query","content","q","message","executeOrchestrator","generate","providerName","fullTask","executeTask","providers","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","CROSS_CHAT_HINT_PATTERNS","referencesOtherChat","message","re","LONG_MEMORY_PATTERNS","detectLongMemoryHint","max","p","isDuplicateOfLast","messages","role","name","content","last","bucketLabel","iso","m","hh","mm","qtr","SessionStore","baseDir","opts","agentId","channel","chatId","day","key","safe","file","data","session","senderName","claudeSessionId","lines","currentBucket","msg","bucket","limit","todayStart","defaultAfter","after","before","safeAgentId","allFiles","f","afterDay","beforeDay","dayRe","candidateFiles","collected","totalScanned","queryLower","participantSet","a","b","page","oldestTs","hasMore","currentKey","files","hints","recent","scope","memoryStore","needsCompaction","compactSession","applyCompaction","result","usage","totalChars","sum","removed","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","readFileSync","writeFileSync","appendFileSync","mkdirSync","existsSync","resolve","MAX_MEMORIES","TASK_STATE_TTL_DAYS","MemoryStore","baseDir","resolve","existsSync","mkdirSync","agentId","fact","entry","appendFileSync","file","readFileSync","l","m","message","limit","memories","docs","cachePath","index","buildIndexCached","bm25Scores","scoreAll","scoreMap","r","matched","i","s","selected","a","b","lines","chars","scope","line","memoryIds","idSet","changed","currentInterval","writeFileSync","now","due","age","content","contentWords","w","mLower","ttlMs","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","TypedEventBus","event","listener","payload","_instance","getEventBus","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","getClaudeCodeUsage","hourCutoff","ONE_H_MS","lastHour","last5h","out","budget","preflightQuotaGate","hasWarmSession","usage","warnIfNearingCap","ratio","stats","promptSizeKey","agentId","channel","chatId","recordPromptSize","key","total","parts","now","warnIfPromptGrowing","s","threshold","ratio","existsSync","mkdirSync","readdirSync","readFileSync","rmSync","writeFileSync","resolve","globalRegistry","setGlobalRegistry","registry","getGlobalRegistry","TASK_OUTPUT_BUFFER_MAX","TASK_OUTPUT_TTL_MS","TASK_HISTORY_DIR","TASK_HISTORY_RETENTION_DAYS","clip","s","max","buildWikiQueryHint","agentWiki","agentId","articleCount","catalogPath","cli","wikiDir","makeStreamEventFormatter","textSeen","event","m","sid","out","block","name","input","c","b","flag","AgentRegistry","HandoverStore","config","log","SessionStore","WikiHub","MemoryStore","PatternStore","RateLimiter","TokenTracker","MessageQueue","setDispatchBudget","GraphStore","a","draftAgent","Classifier","id","def","builder","mesh","next","channel","chatId","o","summary","text","opts","lower","bestId","bestLen","state","mention","mentionLower","found","peers","agent","task","onDelta","peerEntry","p","timeoutMs","e","qChannel","qChatId","start","maxWaitMs","pollIntervalMs","r","mode","queued","pending","rateResult","reason","waitMs","runningTask","output","pushToBuffer","chunk","sub","onEvent","formatter","formatted","senderName","taskStartedAt","getEventBus","intent","wikiContext","relevantMemories","memoryContext","relevantPatterns","patternContext","skillInjection","loadLocalSkills","getAutoInjectSkills","skills","resumeSessionId","lastTokens","turns","compactResult","quality","lost","d","sessionHistory","referencesBlock","cacheKey","cached","refs","recipes","loadReferences","loadRecipes","rootRefs","rootRecipes","resolved","resolveRecipes","renderReferences","crossChatContext","longMemoryRecall","lmHint","detectLongMemoryHint","recall","lines","ordered","t","stamp","who","strategy","sessionHistoryOverride","planDebug","plannerSucceeded","planContext","plan","soulSessionKey","soulSwitch","detectSoulSwitch","activeSoul","bootstrapFiles","loadBootstrapFiles","bootstrapContextText","buildBootstrapContext","systemPromptAppend","contextInput","historyContext","buildAgentContext","agentxContextBytes","sizeParts","sizeKey","promptSizeKey","recordPromptSize","driftWarning","warnIfPromptGrowing","taskWithSystemPrompt","finalResponse","hasWarmSession","abort","preflightOverageGate","preflightQuotaGate","g","preflightResponse","recordClaudeCodeDispatch","warning","warnIfNearingCap","response","executeTask","finalText","totalInput","entryId","extractMemories","extractPatterns","billedModel","tChannel","tSessionKey","error","idx","endedAt","record","rawSummary","firstLine","qm","total","day","dir","file","hours","now","buckets","windowStart","root","today","yesterday","dayDir","tsStr","ts","offsetHours","loaded","agentDir","agentPath","days","outer","files","f","rec","raw","retentionDays","cutoff","removed","dayEntries","limit","_t","_r","taskId","safeId"]}