agentix-cli 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-57MG4LBM.js → agent-Y4U4R3ZZ.js} +2 -2
- package/dist/{board-dashboard-HXDOYGIG.js → board-dashboard-2CPBWFOB.js} +339 -97
- package/dist/board-dashboard-2CPBWFOB.js.map +1 -0
- package/dist/chunk-2HXSJYAJ.js +15 -0
- package/dist/chunk-2HXSJYAJ.js.map +1 -0
- package/dist/{chunk-QW2OYR3N.js → chunk-ABVZHJUO.js} +2 -2
- package/dist/chunk-BHSPAP4U.js +44 -0
- package/dist/chunk-BHSPAP4U.js.map +1 -0
- package/dist/chunk-CO4YCIIA.js +13 -0
- package/dist/chunk-CO4YCIIA.js.map +1 -0
- package/dist/{chunk-4MREHZP5.js → chunk-GOLZ6NEL.js} +2 -2
- package/dist/chunk-GOLZ6NEL.js.map +1 -0
- package/dist/chunk-GSX4NK2N.js +2 -0
- package/dist/chunk-GSX4NK2N.js.map +1 -0
- package/dist/chunk-GV7S5BAX.js +2 -0
- package/dist/chunk-GV7S5BAX.js.map +1 -0
- package/dist/{chunk-WPPUOTY6.js → chunk-KX626FN4.js} +2 -2
- package/dist/chunk-OPKQNLGX.js +450 -0
- package/dist/chunk-OPKQNLGX.js.map +1 -0
- package/dist/chunk-OZOQKPJV.js +102 -0
- package/dist/chunk-OZOQKPJV.js.map +1 -0
- package/dist/{chunk-DLVOHCTA.js → chunk-P3TJHGO5.js} +2 -2
- package/dist/chunk-SZDACK6O.js +2 -0
- package/dist/chunk-SZDACK6O.js.map +1 -0
- package/dist/{chunk-TC3BSUP7.js → chunk-VYLZ7UYM.js} +2 -2
- package/dist/{chunk-WFHIIVLL.js → chunk-WHP4I44J.js} +75 -51
- package/dist/chunk-WHP4I44J.js.map +1 -0
- package/dist/chunk-X5D5AV5N.js +833 -0
- package/dist/chunk-X5D5AV5N.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/{config-QXWKEQON.js → config-BLOKGMER.js} +2 -2
- package/dist/config-mutator-RG7QY2EL.js +2 -0
- package/dist/doctor-ZTECTUHX.js +2 -0
- package/dist/index.d.ts +140 -3
- package/dist/index.js +1 -1
- package/dist/program-GZOJIWTB.js +2 -0
- package/dist/registry-QZIUSQ5J.js +2 -0
- package/dist/runner-BFYDWPUO.js +2 -0
- package/dist/scheduler-B2BQPV4M.js +2 -0
- package/dist/store-I3HEWVLQ.js +2 -0
- package/dist/store-I3HEWVLQ.js.map +1 -0
- package/dist/types-Y6ZCZ52Z.js +2 -0
- package/dist/types-Y6ZCZ52Z.js.map +1 -0
- package/dist/web/workflow-editor.global.js +12 -10
- package/dist/web/workflow-editor.global.js.map +1 -1
- package/dist/workspace-setup-DQCZX2NE.js +2 -0
- package/dist/workspace-setup-DQCZX2NE.js.map +1 -0
- package/package.json +1 -1
- package/dist/board-dashboard-HXDOYGIG.js.map +0 -1
- package/dist/chunk-23AUU4S2.js +0 -102
- package/dist/chunk-23AUU4S2.js.map +0 -1
- package/dist/chunk-4MREHZP5.js.map +0 -1
- package/dist/chunk-6B74TO2J.js +0 -743
- package/dist/chunk-6B74TO2J.js.map +0 -1
- package/dist/chunk-7GVXCOLO.js +0 -440
- package/dist/chunk-7GVXCOLO.js.map +0 -1
- package/dist/chunk-E4ZZILAK.js +0 -15
- package/dist/chunk-E4ZZILAK.js.map +0 -1
- package/dist/chunk-MBOLXKA2.js +0 -6
- package/dist/chunk-MBOLXKA2.js.map +0 -1
- package/dist/chunk-WFHIIVLL.js.map +0 -1
- package/dist/chunk-ZE2YFX4K.js +0 -45
- package/dist/chunk-ZE2YFX4K.js.map +0 -1
- package/dist/config-mutator-53XGR2NT.js +0 -2
- package/dist/doctor-SEOUFHSL.js +0 -2
- package/dist/program-WWCXMRU2.js +0 -2
- package/dist/registry-HM4ELSXS.js +0 -2
- package/dist/scheduler-XVFIK4XH.js +0 -2
- package/dist/workspace-setup-FPGN5VNY.js +0 -2
- /package/dist/{agent-57MG4LBM.js.map → agent-Y4U4R3ZZ.js.map} +0 -0
- /package/dist/{chunk-QW2OYR3N.js.map → chunk-ABVZHJUO.js.map} +0 -0
- /package/dist/{chunk-WPPUOTY6.js.map → chunk-KX626FN4.js.map} +0 -0
- /package/dist/{chunk-DLVOHCTA.js.map → chunk-P3TJHGO5.js.map} +0 -0
- /package/dist/{chunk-TC3BSUP7.js.map → chunk-VYLZ7UYM.js.map} +0 -0
- /package/dist/{config-QXWKEQON.js.map → config-BLOKGMER.js.map} +0 -0
- /package/dist/{config-mutator-53XGR2NT.js.map → config-mutator-RG7QY2EL.js.map} +0 -0
- /package/dist/{doctor-SEOUFHSL.js.map → doctor-ZTECTUHX.js.map} +0 -0
- /package/dist/{program-WWCXMRU2.js.map → program-GZOJIWTB.js.map} +0 -0
- /package/dist/{registry-HM4ELSXS.js.map → registry-QZIUSQ5J.js.map} +0 -0
- /package/dist/{scheduler-XVFIK4XH.js.map → runner-BFYDWPUO.js.map} +0 -0
- /package/dist/{workspace-setup-FPGN5VNY.js.map → scheduler-B2BQPV4M.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agents/runtime.ts","../src/agents/error-map.ts","../src/agents/process-registry-instance.ts","../src/agents/process-registry.ts","../src/agents/sessions.ts","../src/daemon/rate-limit.ts","../src/daemon/token-tracker.ts","../src/agents/context.ts","../src/agents/memory-store.ts","../src/agents/agent-memory.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 { StringDecoder } from \"string_decoder\"\nimport { friendlyModelError, renderFriendlyError } from \"./error-map\"\nimport { buildAgentEnv } from \"@/utils/workspace-env\"\nimport type { AgentDef } from \"@/daemon/config\"\nimport { getProcessRegistry } from \"./process-registry-instance\"\nimport { RegistryCapExceeded, type ProcessKey } from \"./process-registry\"\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\n/** Message shown to operators when the `claude` CLI isn't on PATH. Hits both\n * the streaming and non-streaming paths — without this we used to surface\n * \"Claude Code exited with code unknown\" / \"code ENOENT\", neither of which\n * hints at the actual fix. */\nfunction claudeMissingMessage(): string {\n return (\n `Claude Code CLI not found on PATH. Install it before starting an agent: ` +\n `https://docs.claude.com/en/docs/claude-code . On Linux/macOS: npm i -g @anthropic-ai/claude-code ` +\n `then verify with claude --version. If you don't intend to use the claude-code engine, set the agent's tier to \"sdk\" or \"orchestrator\" in agentx.json.`\n )\n}\n\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 /** Phase 1 / 6 — when set, identifies the intent-ledger decision row\n * this task corresponds to. The registry records a resolution row\n * on this decision when the task completes (success / error / mesh-\n * fallback). Active-task safety in `decideAndCommit` reads\n * intent_resolutions to clear in-flight slots; without resolution\n * writes, every dispatched decision sits in-flight forever and\n * Inv-ActiveTaskSafety becomes vacuously over-aggressive. Channel\n * adapters (gitlab, router) set this when their record*Dispatch\n * helper returns a dispatched decision. */\n intentRef?: {\n eventId: string\n decidedBy: string\n }\n /** ULID for the per-execution trace row in task_traces (improvement\n * plan #2). Allocated by registry.execute right before emitting the\n * task:started bus event so per-step capture sites in the streaming\n * parser can append rows under the same id. Optional — when unset,\n * step capture is a no-op. */\n taskId?: string\n 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 | string }>((resolve, reject) => {\n const childEnv = buildAgentEnv(agent.workspace)\n const proc = execFile(\"claude\", args, {\n cwd: agent.workspace,\n timeout: timeoutMs,\n maxBuffer: 10 * 1024 * 1024,\n env: { ...childEnv, HOME: childEnv.HOME || \"/home/\" + (childEnv.USER || \"user\") },\n }, (error, stdout, stderr) => {\n // Always resolve — we handle errors ourselves based on stdout/stderr\n resolve({\n stdout: stdout || \"\",\n stderr: stderr || \"\",\n exitCode: error ? (error as any).code ?? 1 : 0,\n })\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 if (exitCode === \"ENOENT\" || /ENOENT|spawn claude/i.test(stderr || \"\")) {\n errMsg = claudeMissingMessage()\n } else {\n errMsg = stderr?.trim() || `Claude Code exited with code ${exitCode}`\n }\n return {\n content: \"\",\n 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: buildAgentEnv(agent.workspace),\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 // StringDecoder buffers partial UTF-8 sequences across chunk\n // boundaries; plain Buffer.toString() does not, so a chunk that\n // ends mid-character (Arabic = 2 bytes, common emoji = 4 bytes)\n // produced replacement chars or dropped letters in the agent's\n // outbound text. The user reported \"letters missing from words\"\n // on WhatsApp specifically — Arabic + emoji-heavy traffic is\n // exactly the workload that exposes this. Hoisted out of the if\n // block so the post-`await proc` flush can call decoder.end().\n let lineBuffer = \"\"\n const decoder = new StringDecoder(\"utf8\")\n if (proc.stdout) {\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n lineBuffer += decoder.write(chunk as Buffer)\n const lines = lineBuffer.split(\"\\n\")\n lineBuffer = lines.pop() || \"\"\n\n for (const line of lines) {\n if (!line.trim()) continue\n try {\n const event = JSON.parse(line)\n\n // Surface every parsed event for observability (dashboard streaming).\n // Best-effort — never let a subscriber crash the runtime.\n if (onEvent) {\n try { onEvent(event) } catch { /* */ }\n }\n\n // 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 // Flush any partial UTF-8 sequence still buffered in the decoder.\n // The stream-json output normally ends with a newline so this is a\n // no-op, but it keeps the channel honest if the producer ever\n // exits mid-character.\n const tail = decoder.end()\n if (tail) lineBuffer += tail\n\n // If we got no streaming content, fall back to stdout\n if (!fullText && result.stdout) {\n fullText = typeof result.stdout === \"string\" ? result.stdout : \"\"\n }\n\n if (result.exitCode !== 0 && !fullText) {\n const stderr = typeof result.stderr === \"string\" ? result.stderr : \"\"\n // execa sets exitCode=undefined when the child is killed by signal —\n // surface that as a recognizable timeout message instead of leaking\n // \"exited with code undefined\" into the user's chat. Mirrors the\n // non-streaming path's exit-143 handling.\n const r = result as { exitCode?: number; signal?: string; timedOut?: boolean; failed?: boolean; code?: string; shortMessage?: string }\n let errMsg: string\n if (r.timedOut) {\n errMsg = `Claude Code timed out after ${Math.round(streamTimeoutMs / 60_000)}m. Bump agent.maxExecutionMinutes for \"${agent.name || \"this agent\"}\" if tasks need longer.`\n } else if (r.signal) {\n errMsg = stderr.trim() || `Claude Code killed by signal ${r.signal}`\n } else if (r.code === \"ENOENT\" || /ENOENT|spawn claude/i.test((r.shortMessage || \"\") + \" \" + stderr)) {\n // execa surfaces \"binary not on PATH\" as { failed: true, code: 'ENOENT',\n // exitCode: undefined } — without an explicit branch this used to leak\n // through as \"Claude Code exited with code unknown\", leaving operators\n // with no clue that the issue is a missing CLI.\n errMsg = claudeMissingMessage()\n } else {\n errMsg = stderr.trim() || `Claude Code exited with code ${r.exitCode ?? \"unknown\"}`\n }\n return {\n content: \"\",\n 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 */\n/**\n * Execute a task by reusing a persistent claude subprocess held in the\n * ProcessRegistry. Driven over `--input-format stream-json` on stdin;\n * the same parser path handles output. See\n * docs/architecture/persistent-claude-process.md for the design.\n *\n * Forwards every stream event to `onEvent` so the existing trace\n * capture site (registry.ts emitTraceStepsFromStreamEvent) continues\n * to populate task_trace_steps unchanged.\n *\n * Returns null when the registry can't allocate a slot (cap exceeded,\n * binary not installed, etc.) — caller falls back to spawn-per-task.\n */\nasync function executeClaudeCodePersistent(\n agent: AgentDef,\n task: AgentTask,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n): Promise<AgentResponse | null> {\n const registry = getProcessRegistry()\n if (!registry) return null\n\n const start = Date.now()\n const channel = task.context?.channel || \"api\"\n const chatId = task.context?.chatId || task.context?.group || task.context?.sender || \"default\"\n const key: ProcessKey = { agentId: task.agentId, channel, chatId }\n\n let handle\n try {\n handle = registry.acquire(key, {\n agentId: task.agentId,\n channel,\n chatId,\n workspace: agent.workspace,\n model: task.model || agent.model,\n permissionMode: agent.permissionMode,\n systemPromptAppend: task.systemPromptAppend,\n resumeSessionId,\n })\n } catch (e: any) {\n if (e instanceof RegistryCapExceeded) return null\n // Anything else — return null too; caller falls back. Persistent\n // path must never be more brittle than the spawn-per-task one.\n return null\n }\n\n const prompt = buildPrompt(agent, task, historyContext)\n\n // Accumulate response data from the stream events. Mirrors what\n // parseClaudeJsonOutput does for the spawn-per-task JSON envelope.\n let finalText = \"\"\n let finalError: string | undefined\n let usage: TokenUsage | undefined\n let billedModel: string | undefined\n let sessionId: string | undefined\n\n try {\n for await (const evt of handle.runTurn({ message: prompt, taskId: task.taskId ?? \"unknown\" })) {\n // Forward to existing onEvent — trace step emitter, dashboard\n // formatter, etc. all keep working without changes.\n if (onEvent) {\n try { onEvent(evt.raw) } catch { /* observability best-effort */ }\n }\n\n if (evt.type === \"assistant\") {\n // Assistant snapshots are cumulative — overwrite, don't append.\n const blocks = ((evt.raw as any).message?.content ?? []) as Array<{ type: string; text?: string }>\n for (const block of blocks) {\n if (block.type === \"text\" && typeof block.text === \"string\") {\n finalText = block.text\n }\n }\n }\n\n if (evt.type === \"result\") {\n const r = evt.raw as any\n if (r.is_error) {\n finalError = renderFriendlyError(friendlyModelError(typeof r.result === \"string\" ? r.result : \"claude error\"))\n } else if (typeof r.result === \"string\" && r.result.length > 0) {\n // Final result text (sometimes more authoritative than the last\n // assistant snapshot, especially for very short responses).\n finalText = r.result\n }\n if (typeof r.session_id === \"string\") sessionId = r.session_id\n if (r.usage) {\n usage = {\n inputTokens: r.usage.input_tokens || 0,\n outputTokens: r.usage.output_tokens || 0,\n cacheReadTokens: r.usage.cache_read_input_tokens || 0,\n cacheCreateTokens: r.usage.cache_creation_input_tokens || 0,\n }\n }\n if (typeof (r.message?.model) === \"string\") billedModel = r.message.model\n else if (typeof r.model === \"string\") billedModel = r.model\n }\n }\n } catch (e: any) {\n finalError = `persistent claude process error: ${e?.message || String(e)}`\n }\n\n return {\n content: finalText,\n error: finalError,\n duration: Date.now() - start,\n usage,\n billedModel,\n claudeSessionId: sessionId,\n }\n}\n\nexport async function executeTask(\n agent: AgentDef,\n task: AgentTask,\n providers: Record<string, { apiKey?: string }>,\n onDelta?: StreamCallback,\n historyContext?: string,\n resumeSessionId?: string,\n onEvent?: (event: any) => void,\n): Promise<AgentResponse> {\n switch (agent.tier) {\n case \"claude-code\":\n // Persistent-process path (improvement plan #5, persistent flavor).\n // Returns null when the registry isn't available or can't allocate\n // a slot — we fall through to the legacy spawn-per-task path so\n // a registry-only failure can never block dispatch.\n if (agent.persistentProcess) {\n const persistent = await executeClaudeCodePersistent(agent, task, historyContext, resumeSessionId, onEvent)\n if (persistent !== null) return persistent\n }\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 type { ProcessRegistry } from \"./process-registry\"\n\n// --- Singleton accessor for the live ProcessRegistry ---\n//\n// Lifetime: the daemon constructs a registry in AgentRegistry's\n// constructor (when at least one agent has `persistentProcess: true`)\n// and registers it here. `executeTask` reads the singleton when it\n// needs to dispatch via the persistent path. Tests use the\n// set/reset hooks to inject fakes without touching the daemon.\n//\n// Why a singleton (vs threading the registry through executeTask's\n// signature): executeTask is called from many sites — registry,\n// cron, mesh — and adding a parameter to all of them is invasive.\n// The registry is a process-global resource (one per daemon), same\n// lifetime story as the event bus.\n\nlet _registry: ProcessRegistry | null = null\n\nexport function getProcessRegistry(): ProcessRegistry | null {\n return _registry\n}\n\nexport function setProcessRegistry(r: ProcessRegistry | null): void {\n _registry = r\n}\n\n/** Test-only — guarantees a clean slate between cases. */\nexport function resetProcessRegistryForTesting(): void {\n _registry = null\n}\n","// --- Process Registry: persistent claude processes per session ---\n//\n// Tracks one `claude -p --input-format stream-json` subprocess per\n// (agentId, channel, chatId). Tasks for the same chat reuse the live\n// process across turns; the empirical cache-amortization win is ~3-5×\n// (turn 1 cache_create=12897, turn 2 cache_create=20 in the\n// 2026-05-03 spike). When a chat goes idle past idleTimeoutMs the\n// process is killed; when at the cap, the oldest idle process is\n// evicted to make room.\n//\n// This file is the registry only. Real subprocess wiring (spawn,\n// stdin write, stdout read) lives behind the ProcessFactory interface\n// so the registry can be tested without spawning real children.\n//\n// Design rationale (see docs/architecture/persistent-claude-process.md):\n// - Process-per-session (not pool-per-agent) — perfect chat\n// isolation; same composite key as SessionStore.\n// - Slash commands DO NOT work in `-p` mode (verbatim from the\n// headless docs). Rotation = kill + respawn, optionally with an\n// application-level summary in --append-system-prompt.\n// - Concurrency: same-chat turns serialize through the handle's\n// own queue; different-chat turns are independent processes.\n\nexport interface ProcessKey {\n agentId: string\n channel: string\n chatId: string\n}\n\nexport type ProcessKeyString = string\n\n/** Stringify the key for Map storage / logging. The composite is the\n * same one SessionStore uses, so process and session entries line up\n * without an extra mapping table. */\nexport function processKeyToString(k: ProcessKey): ProcessKeyString {\n return `${k.agentId}:${k.channel}:${k.chatId}`\n}\n\nexport function parseProcessKey(s: ProcessKeyString): ProcessKey {\n const [agentId, channel, ...rest] = s.split(\":\")\n return { agentId, channel, chatId: rest.join(\":\") }\n}\n\n/**\n * Lifecycle state of a process. Values are strings (not enum) so they\n * round-trip through JSON for `agentx process list` without an\n * encode/decode step.\n *\n * warm-cold → spawned, no claudeSessionId yet\n * warm-hot → at least one turn done, cache reuse expected\n * idle → not running a turn, eligible for eviction\n * dead → killed or exited; will be removed from the registry\n */\nexport type ProcessState = \"warm-cold\" | \"warm-hot\" | \"idle\" | \"dead\"\n\nexport interface ProcessSnapshot {\n key: ProcessKey\n pid: number | null\n claudeSessionId: string | null\n state: ProcessState\n spawnedAt: number\n lastTurnAt: number\n turnCount: number\n /** Last result event's input_tokens — drives tier-2 rotation decisions. */\n lastInputTokens: number\n /** Trace ULID of the in-flight turn, when one is active. */\n pendingTaskId: string | null\n /** Reason the process is dead, when state === \"dead\". */\n deadReason?: string\n /** Managed-marker hash of CLAUDE.md at spawn time (null for unmarked\n * / user-edited files). The drift sweep re-reads the live file's\n * hash and kills idle handles whose hash no longer matches, so the\n * next dispatch picks up the edited prompt. */\n claudeMdHash: string | null\n}\n\nexport interface SpawnOptions {\n agentId: string\n channel: string\n chatId: string\n workspace: string\n model?: string\n permissionMode?: string\n systemPromptAppend?: string\n /** Pass-through to `claude --resume <id>`. Set when SessionStore has a\n * stored claudeSessionId for this chat (e.g. across daemon restart). */\n resumeSessionId?: string\n}\n\nexport interface TurnInput {\n /** User-facing text the parent wrote to stdin under\n * `{type:\"user\", message:{role:\"user\", content:<here>}}`. */\n message: string\n /** Trace ULID for correlating events back to a task_traces row. */\n taskId: string\n}\n\nexport interface TurnEvent {\n /** stream-json event type — one of \"system\" | \"assistant\" | \"user\" |\n * \"result\" | \"stream_event\" | \"rate_limit_event\" | … */\n type: string\n /** Raw event line (parsed). Forwarded verbatim to the streaming\n * parser in registry.ts so trace step capture stays unchanged. */\n raw: Record<string, unknown>\n}\n\nexport interface ProcessHandle {\n readonly key: ProcessKey\n state(): ProcessState\n snapshot(): ProcessSnapshot\n /**\n * Send one user turn, yield stream-json events as they arrive, and\n * return when a `result` event is observed. The handle serializes\n * concurrent runTurn calls so two simultaneous tasks on the same\n * chat queue rather than racing.\n */\n runTurn(input: TurnInput): AsyncIterable<TurnEvent>\n /**\n * Kill the underlying process. SIGTERM, then SIGKILL after a grace\n * period. Resolves once the process is gone. `reason` shows up in\n * `agentx process list --include-dead` and the trace step.\n */\n kill(reason: string): Promise<void>\n}\n\nexport interface ProcessFactory {\n /** Spawn (or fake) a process bound to `key` with `opts`. */\n spawn(key: ProcessKey, opts: SpawnOptions): ProcessHandle\n}\n\n/**\n * Thrown by `acquire` when at the global or per-agent cap and no idle\n * handle is evictable. Callers — typically the per-turn driver in\n * runtime.ts — catch this and fall back to spawn-per-task. Using a\n * named error class is more robust than message-sniffing.\n */\nexport class RegistryCapExceeded extends Error {\n constructor(\n message: string,\n public readonly scope: \"global\" | \"agent\",\n public readonly cap: number,\n ) {\n super(message)\n this.name = \"RegistryCapExceeded\"\n }\n}\n\nexport interface ProcessRegistryConfig {\n /** Hard global cap. Default 64. When at cap, oldest idle handle is\n * evicted to make room for the next acquire. */\n maxProcessesGlobal?: number\n /** Per-agent cap. Default 16. Prevents one chatty agent from\n * starving another. */\n maxProcessesPerAgent?: number\n /** A handle is \"idle\" idleTimeoutMs after its last turn completed.\n * Idle handles are eligible for eviction; once `staleTimeoutMs` is\n * also exceeded, the handle is killed unconditionally on the next\n * sweep. Defaults: 30s idle, 45min stale. */\n idleTimeoutMs?: number\n staleTimeoutMs?: number\n /** Sweep interval. Default 5_000 (5s). */\n sweepIntervalMs?: number\n factory: ProcessFactory\n log?: (msg: string) => void\n /** Optional drift detector — called per idle handle on each sweep.\n * Returns the current workspace CLAUDE.md hash for the agent (or\n * null when unmarked / missing). The registry compares against the\n * hash stored on the handle's snapshot at spawn time and kills\n * handles whose hashes don't match.\n *\n * Callers wire this from src/agents/workspace-setup.ts where the\n * managed-marker hash logic already lives — the registry stays\n * decoupled from the marker format itself. When unset, drift\n * detection is disabled (default for tests). */\n currentWorkspaceHash?: (key: ProcessKey, workspace: string) => string | null\n}\n\nexport class ProcessRegistry {\n private handles = new Map<ProcessKeyString, ProcessHandle>()\n private sweeper?: ReturnType<typeof setInterval>\n /** Per-handle workspace path captured at acquire time. Drift sweep\n * re-reads each handle's workspace via the currentWorkspaceHash\n * hook to compare against the stored hash. */\n private handleWorkspace = new Map<ProcessKeyString, string>()\n private readonly cfg: Required<Omit<ProcessRegistryConfig, \"factory\" | \"log\" | \"currentWorkspaceHash\">> & {\n factory: ProcessFactory\n log: (msg: string) => void\n currentWorkspaceHash?: (key: ProcessKey, workspace: string) => string | null\n }\n\n constructor(cfg: ProcessRegistryConfig) {\n this.cfg = {\n maxProcessesGlobal: cfg.maxProcessesGlobal ?? 64,\n maxProcessesPerAgent: cfg.maxProcessesPerAgent ?? 16,\n idleTimeoutMs: cfg.idleTimeoutMs ?? 30_000,\n staleTimeoutMs: cfg.staleTimeoutMs ?? 45 * 60 * 1000,\n sweepIntervalMs: cfg.sweepIntervalMs ?? 5_000,\n factory: cfg.factory,\n log: cfg.log ?? (() => {}),\n currentWorkspaceHash: cfg.currentWorkspaceHash,\n }\n }\n\n /** Begin the idle-eviction sweep timer. Idempotent. */\n start(): void {\n if (this.sweeper) return\n this.sweeper = setInterval(() => this.sweepIdle(), this.cfg.sweepIntervalMs)\n if (typeof this.sweeper.unref === \"function\") this.sweeper.unref()\n }\n\n /**\n * Stop the sweeper and kill every live handle. Used on daemon\n * shutdown. Resolves when all kills complete.\n */\n async stop(): Promise<void> {\n if (this.sweeper) {\n clearInterval(this.sweeper)\n this.sweeper = undefined\n }\n const handles = Array.from(this.handles.values())\n this.handles.clear()\n await Promise.allSettled(handles.map((h) => h.kill(\"registry-stop\")))\n }\n\n /**\n * Get-or-spawn a process for `key`. Spawns through the factory when\n * no handle exists. When at the global or per-agent cap, evicts the\n * oldest idle handle to make room; throws if no idle handle is\n * available (caller can fall back to spawn-per-task or queue).\n */\n acquire(key: ProcessKey, opts: SpawnOptions): ProcessHandle {\n const ks = processKeyToString(key)\n const existing = this.handles.get(ks)\n if (existing && existing.state() !== \"dead\") return existing\n\n // Make room if at cap.\n if (this.handles.size >= this.cfg.maxProcessesGlobal) {\n this.evictOldestIdle({ scope: \"global\" })\n }\n const perAgentCount = this.countForAgent(key.agentId)\n if (perAgentCount >= this.cfg.maxProcessesPerAgent) {\n this.evictOldestIdle({ scope: \"agent\", agentId: key.agentId })\n }\n if (this.handles.size >= this.cfg.maxProcessesGlobal) {\n throw new RegistryCapExceeded(\n `process registry at global cap (${this.cfg.maxProcessesGlobal}) and no idle handle to evict`,\n \"global\",\n this.cfg.maxProcessesGlobal,\n )\n }\n if (this.countForAgent(key.agentId) >= this.cfg.maxProcessesPerAgent) {\n throw new RegistryCapExceeded(\n `process registry at per-agent cap for ${key.agentId} (${this.cfg.maxProcessesPerAgent}) and no idle handle to evict`,\n \"agent\",\n this.cfg.maxProcessesPerAgent,\n )\n }\n\n const handle = this.cfg.factory.spawn(key, opts)\n this.handles.set(ks, handle)\n this.handleWorkspace.set(ks, opts.workspace)\n this.cfg.log(`[process-registry] spawned ${ks}`)\n return handle\n }\n\n /**\n * Drop a key from the registry, optionally killing the process.\n * Without `kill: true`, the handle stays alive — useful when the\n * caller wants the registry to forget about it (e.g. config reload)\n * but the in-flight turn should finish.\n */\n release(key: ProcessKey, opts: { kill?: boolean; reason?: string } = {}): void {\n const ks = processKeyToString(key)\n const handle = this.handles.get(ks)\n if (!handle) return\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n if (opts.kill) {\n void handle.kill(opts.reason ?? \"release\")\n }\n }\n\n /** Operator surface: snapshot every live handle. */\n list(): ProcessSnapshot[] {\n return Array.from(this.handles.values()).map((h) => h.snapshot())\n }\n\n /** Force-kill one handle, removing it from the registry. */\n async kill(key: ProcessKey, reason = \"operator\"): Promise<void> {\n const ks = processKeyToString(key)\n const handle = this.handles.get(ks)\n if (!handle) return\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n await handle.kill(reason)\n }\n\n // ---------- internals ----------\n\n private countForAgent(agentId: string): number {\n let n = 0\n for (const h of this.handles.values()) {\n if (h.key.agentId === agentId && h.state() !== \"dead\") n += 1\n }\n return n\n }\n\n /**\n * Evict the oldest idle handle in the requested scope. \"Oldest\" is\n * defined by lastTurnAt (LRU). Returns true when one was evicted.\n * No-op when no idle handle is found — the caller is responsible\n * for the cap-exceeded error path.\n */\n private evictOldestIdle(scope: { scope: \"global\" } | { scope: \"agent\"; agentId: string }): boolean {\n let target: ProcessHandle | undefined\n let oldest = Number.POSITIVE_INFINITY\n for (const h of this.handles.values()) {\n if (h.state() !== \"idle\") continue\n if (scope.scope === \"agent\" && h.key.agentId !== scope.agentId) continue\n const ts = h.snapshot().lastTurnAt\n if (ts < oldest) {\n oldest = ts\n target = h\n }\n }\n if (!target) return false\n const ks = processKeyToString(target.key)\n this.handles.delete(ks)\n void target.kill(\"evicted (cap reached)\")\n this.cfg.log(`[process-registry] evicted ${ks} (cap)`)\n return true\n }\n\n /**\n * Per-tick sweep — kills idle processes past timeout, reaps dead\n * handles, and detects CLAUDE.md drift on idle handles. Drift\n * kills only fire on idle handles to avoid disturbing a turn that's\n * still streaming; the next sweep catches the handle once it\n * transitions back to idle.\n */\n private sweepIdle(): void {\n const now = Date.now()\n for (const [ks, h] of this.handles.entries()) {\n const snap = h.snapshot()\n if (h.state() === \"dead\") {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n this.cfg.log(`[process-registry] reaped dead ${ks}`)\n continue\n }\n if (h.state() !== \"idle\") continue\n const idleFor = now - snap.lastTurnAt\n if (idleFor >= this.cfg.staleTimeoutMs) {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n void h.kill(`stale (idle ${Math.round(idleFor / 1000)}s)`)\n this.cfg.log(`[process-registry] killed stale ${ks}`)\n continue\n }\n if (idleFor >= this.cfg.idleTimeoutMs) {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n void h.kill(`idle (${Math.round(idleFor / 1000)}s)`)\n this.cfg.log(`[process-registry] killed idle ${ks}`)\n continue\n }\n\n // Drift detection — only when the operator wired a hash hook.\n // Reads the current managed-marker hash from the workspace's\n // CLAUDE.md and kills handles whose hash has changed since\n // spawn, so the next dispatch loads the edited prompt.\n if (this.cfg.currentWorkspaceHash) {\n const workspace = this.handleWorkspace.get(ks)\n if (workspace) {\n let currentHash: string | null = null\n try {\n currentHash = this.cfg.currentWorkspaceHash(h.key, workspace)\n } catch (e: any) {\n // Hash-read failure must not crash the sweeper. Log + skip.\n this.cfg.log(`[process-registry] drift-check failed for ${ks}: ${e?.message || e}`)\n continue\n }\n if (currentHash !== snap.claudeMdHash) {\n this.handles.delete(ks)\n this.handleWorkspace.delete(ks)\n const fromHash = snap.claudeMdHash ?? \"unmarked\"\n const toHash = currentHash ?? \"unmarked\"\n void h.kill(`claude-md drifted (${fromHash} → ${toHash})`)\n this.cfg.log(`[process-registry] killed drifted ${ks} (${fromHash} → ${toHash})`)\n }\n }\n }\n }\n }\n}\n","import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from \"fs\"\nimport { resolve } from \"path\"\nimport type { ChannelAdapter, SeededMessage } from \"@/channels/types\"\nimport { debug } from \"@/observability/debug\"\n\n// --- Conversation session store ---\n// One session per agent + channel + chatId + day.\n// For 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 /** True when this message was injected by SessionStore.seedFromAdapter()\n * reading the live channel on cold session create — never via the inbound\n * router pipeline. Marked so observers can distinguish seeded turns from\n * live ones; the model sees them identically in buildHistoryContext. */\n seeded?: boolean\n /** Channel-side message id (Telegram update_id, GitLab note id, WhatsApp\n * key.id). Used to dedup re-seeds — if a message with this externalId is\n * already in the session, appendSeededMessages skips it. */\n externalId?: string\n}\n\nexport interface Session {\n id: string\n agentId: string\n channel: string\n chatId: string\n day: string // YYYY-MM-DD\n claudeSessionId?: string // Claude Code native session ID (for --resume)\n 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 /** Resolves a channel name to its adapter — installed by the daemon after\n * channels are registered (see daemon/index.ts after router.register). The\n * cold-create branch consults it to call adapter.seedHistory(). Optional;\n * when unset, seeding is a no-op (back-compat for tests, CLI tools, and\n * any caller that constructs SessionStore standalone). */\n private adapterResolver?: (channel: string) => ChannelAdapter | undefined\n /** In-flight seed promises keyed by sessionKey. Coalesces concurrent\n * cold-creates against the same (agent, channel, chatId, day) so we hit\n * the channel API exactly once per fresh session. */\n private seedingPromises: Map<string, Promise<void>> = new Map()\n\n constructor(\n baseDir: string = process.cwd(),\n opts: {\n staleMinutes?: number\n maxTurnsPerSession?: number\n tierTwoThresholdTokens?: number\n } = {},\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 /** Install the channel-adapter resolver. Called once at daemon startup\n * after MessageRouter has registered every adapter. Hot-reload of channel\n * adapters re-uses the same router, so the resolver stays valid across\n * /reload cycles without re-installation. */\n setAdapterResolver(fn: (channel: string) => ChannelAdapter | undefined): void {\n this.adapterResolver = fn\n }\n\n /** Seed a fresh session from the live channel before any user/agent\n * messages get appended. No-op when:\n * - the session already has messages (warm cache or partially loaded)\n * - no adapter resolver is installed\n * - the channel adapter has no seedHistory method\n * Concurrent calls against the same key share one in-flight promise so\n * the channel API is hit exactly once per cold create. Errors are\n * swallowed — a seeding failure should never block a turn. */\n async seedIfEmpty(agentId: string, channel: string, chatId: string): Promise<void> {\n if (!this.adapterResolver) return\n const key = this.sessionKey(agentId, channel, chatId)\n const inFlight = this.seedingPromises.get(key)\n if (inFlight) return inFlight\n\n const session = this.getSession(agentId, channel, chatId)\n if (session.messages.length > 0) return\n\n const adapter = this.adapterResolver(channel)\n if (!adapter?.seedHistory) return\n\n const promise = (async () => {\n try {\n const seeds = await adapter.seedHistory!(chatId, {\n maxMessages: MAX_MESSAGES,\n maxChars: MAX_HISTORY_CHARS,\n })\n if (seeds.length > 0) this.appendSeededMessages(session, seeds)\n } catch {\n // best-effort\n }\n })()\n this.seedingPromises.set(key, promise)\n try {\n await promise\n } finally {\n this.seedingPromises.delete(key)\n }\n }\n\n /** Append seeded messages to a session in chronological order, deduping\n * by externalId against anything already present. Marks each entry with\n * `seeded: true` so observers can distinguish them; the model's prompt\n * (buildHistoryContext) renders them identically to live turns. */\n private appendSeededMessages(session: Session, seeds: SeededMessage[]): void {\n if (seeds.length === 0) return\n const existingIds = new Set<string>()\n for (const m of session.messages) {\n if (m.externalId) existingIds.add(m.externalId)\n }\n let appended = 0\n for (const s of seeds) {\n if (s.externalId && existingIds.has(s.externalId)) continue\n session.messages.push({\n role: s.role,\n name: s.name,\n content: s.content,\n timestamp: s.timestamp,\n seeded: true,\n externalId: s.externalId,\n })\n appended++\n }\n if (appended === 0) return\n this.trim(session)\n session.updatedAt = new Date().toISOString()\n this.save(session)\n }\n\n /**\n * Build a deterministic session key.\n */\n private sessionKey(agentId: string, channel: string, chatId: string): string {\n const day = new Date().toISOString().slice(0, 10) // YYYY-MM-DD\n return `${agentId}:${channel}:${chatId}:${day}`\n }\n\n private sessionFile(key: string): string {\n // Sanitize key for filesystem\n const safe = key.replace(/[^a-zA-Z0-9_:-]/g, \"_\")\n return resolve(this.sessionsDir, `${safe}.json`)\n }\n\n /**\n * Get or create a session for this agent+channel+chat+day.\n */\n getSession(agentId: string, channel: string, chatId: string): Session {\n const key = this.sessionKey(agentId, channel, chatId)\n\n // Check cache\n if (this.cache.has(key)) {\n return this.cache.get(key)!\n }\n\n // Try loading from disk\n const file = this.sessionFile(key)\n if (existsSync(file)) {\n try {\n const data = JSON.parse(readFileSync(file, \"utf-8\")) as Session\n this.cache.set(key, data)\n return data\n } catch {\n // Corrupted file — start fresh\n }\n }\n\n // Create new session\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 /** Cross-agent view of a chat. Aggregates session messages from EVERY\n * agent that has a session for `(channel, chatId)` in the requested\n * window, sorts oldest-first, and dedups exact (timestamp, role, name,\n * content) collisions (which happen when two agents in the same group\n * both record the same inbound).\n *\n * Differs from recallTurns in two ways:\n * 1. Not scoped to one agentId — answers \"what's in this chat across\n * all agents\", which is the question the cx→devops→marketing thread\n * on 2026-04-29 needed and didn't have.\n * 2. Returns oldest-first so the caller reads it like a transcript;\n * recallTurns returns newest-first because it paginates.\n *\n * Bounded by the same MAX_MESSAGES default as buildHistoryContext so\n * callers don't accidentally pull a 5000-turn group into one prompt. */\n recentByChatId(opts: {\n channel: string\n chatId: string\n sinceISO?: string\n limit?: number\n }): Array<{\n ts: string\n role: \"user\" | \"agent\"\n senderName: string\n content: string\n agentId: string\n sessionFile: string\n }> {\n const limit = Math.min(Math.max(opts.limit ?? MAX_MESSAGES, 1), 200)\n // Default window: last 24h. Wider than recallTurns' \"today only\" because\n // the typical use is \"what was just said\" — covers overnight gaps that\n // span midnight UTC.\n const sinceISO = opts.sinceISO ?? new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()\n const sinceDay = sinceISO.slice(0, 10)\n\n let allFiles: string[]\n try {\n allFiles = readdirSync(this.sessionsDir).filter((f) => f.endsWith(\".json\"))\n } catch {\n return []\n }\n\n // The session-file naming convention is `{agentId}:{channel}:{chatId}:{day}.json`.\n // Build a substring key that captures channel+chatId; the agentId prefix\n // and day suffix vary so we filter those after parse. Note `chatId` may\n // itself contain colons (GitLab chatIds look like \"group/project:issue:123\"),\n // so we can't naively split on \":\". Cheap pre-filter: must contain\n // `:${channel}:${chatId}:` as a substring.\n const key = `:${opts.channel}:${opts.chatId}:`\n const candidates = allFiles.filter((f) => {\n if (!f.includes(key)) return false\n // Day suffix prefilter — skip files whose day is older than sinceDay.\n const m = f.match(/:(\\d{4}-\\d{2}-\\d{2})\\.json$/)\n if (!m) return false\n return m[1] >= sinceDay\n })\n\n type Row = {\n ts: string\n role: \"user\" | \"agent\"\n senderName: string\n content: string\n agentId: string\n sessionFile: string\n }\n const rows: Row[] = []\n for (const file of candidates) {\n let data: Session\n try {\n data = JSON.parse(readFileSync(resolve(this.sessionsDir, file), \"utf-8\")) as Session\n } catch {\n continue\n }\n // Defensive re-check after parse — substring match could collide with\n // an agent whose id literally contains \":telegram:1816212449:\".\n if (data.channel !== opts.channel || data.chatId !== opts.chatId) continue\n\n for (const msg of data.messages) {\n if (!msg.timestamp || msg.timestamp < sinceISO) continue\n rows.push({\n ts: msg.timestamp,\n role: msg.role,\n senderName: msg.name ?? \"\",\n content: msg.content,\n agentId: data.agentId,\n sessionFile: file,\n })\n }\n }\n\n // Dedup exact collisions across agents (same inbound recorded by two\n // agents in the same group). Key on (ts, role, name, content).\n const seen = new Set<string>()\n const dedupped: Row[] = []\n for (const r of rows) {\n const k = `${r.ts}|${r.role}|${r.senderName}|${r.content}`\n if (seen.has(k)) continue\n seen.add(k)\n dedupped.push(r)\n }\n\n dedupped.sort((a, b) => a.ts.localeCompare(b.ts))\n // Take the most recent `limit` (last entries when sorted oldest-first).\n return dedupped.slice(-limit)\n }\n\n /**\n * Check if a 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 const beforeCount = session.messages.length\n // Trim by message count\n if (session.messages.length > MAX_MESSAGES) {\n session.messages = session.messages.slice(-MAX_MESSAGES)\n }\n\n // Trim by total character count\n let totalChars = session.messages.reduce((sum, m) => sum + m.content.length, 0)\n while (totalChars > MAX_HISTORY_CHARS && session.messages.length > 2) {\n const removed = session.messages.shift()!\n totalChars -= removed.content.length\n }\n\n // Surface silent drops under `--debug context`. Without this, a long\n // chat that rolls past the 30-message / 12K-char caps quietly loses\n // older turns and the operator has no signal — exactly the failure\n // mode that disguises itself as \"agent forgot\" amnesia.\n const dropped = beforeCount - session.messages.length\n if (dropped > 0) {\n debug.cat(\n \"context\",\n `[${session.agentId}] ${session.channel}:${session.chatId} trim dropped ${dropped} message(s): ${beforeCount} -> ${session.messages.length} (${totalChars}c)`,\n )\n }\n }\n\n private save(session: Session): void {\n try {\n const file = this.sessionFile(session.id)\n writeFileSync(file, JSON.stringify(session, null, 2))\n } catch {\n // Best-effort persistence\n }\n }\n}\n","// --- Rate limiter for agent tasks ---\n// Prevents message floods from exhausting Claude API/subscription.\n// Per-agent, sliding window. `acquire()` queues callers when the window is\n// full instead of failing them outright — good for chatty channels where\n// dropping a message would look like the bot is broken.\n\nexport class RateLimiter {\n private windows: Map<string, number[]> = new Map()\n private maxPerMinute: number\n private maxPerHour: number\n\n constructor(maxPerMinute: number = 10, maxPerHour: number = 100) {\n this.maxPerMinute = maxPerMinute\n this.maxPerHour = maxPerHour\n }\n\n /**\n * Check if an agent can process a task right now. Does NOT wait. Call\n * `acquire()` if you want queueing behaviour.\n */\n check(agentId: string): { allowed: boolean; reason?: string; waitMs?: number } {\n const now = Date.now()\n const timestamps = this.windows.get(agentId) || []\n\n // Clean old entries (older than 1 hour).\n const hourAgo = now - 3600_000\n const recent = timestamps.filter((t) => t > hourAgo)\n\n // Per-minute window.\n const minuteAgo = now - 60_000\n const inMinute = recent.filter((t) => t > minuteAgo)\n if (inMinute.length >= this.maxPerMinute) {\n const oldest = inMinute[0]\n const waitMs = Math.max(50, oldest + 60_000 - now)\n return {\n allowed: false,\n reason: `Rate limit: ${inMinute.length}/${this.maxPerMinute} per minute`,\n waitMs,\n }\n }\n\n // Per-hour window.\n if (recent.length >= this.maxPerHour) {\n const oldest = recent[0]\n const waitMs = Math.max(1000, oldest + 3600_000 - now)\n return {\n allowed: false,\n reason: `Rate limit: ${recent.length}/${this.maxPerHour} per hour`,\n waitMs,\n }\n }\n\n // Record this slot and allow.\n recent.push(now)\n this.windows.set(agentId, recent)\n return { allowed: true }\n }\n\n /**\n * Wait until a slot opens for this agent, then record + return ok. If the\n * caller exceeds `maxWaitMs`, give up with the last rate-limit reason. A\n * concurrent task that acquires a slot first will push THIS task's wake-up\n * further out — that's intended: FIFO-ish through the agent's normal\n * message queue, no starvation in practice.\n *\n * `onWait` fires once at the start of waiting so callers can surface the\n * \"queued\" state (logging, dashboard hint, etc).\n */\n async acquire(\n agentId: string,\n opts: { maxWaitMs?: number; onWait?: (reason: string, waitMs: number) => void } = {},\n ): Promise<{ ok: true } | { ok: false; reason: string }> {\n const maxWaitMs = opts.maxWaitMs ?? 5 * 60_000\n const deadline = Date.now() + maxWaitMs\n let firstWait = true\n\n while (true) {\n const r = this.check(agentId)\n if (r.allowed) return { ok: true }\n if (Date.now() >= deadline) {\n return { ok: false, reason: `${r.reason} (waited ${Math.round(maxWaitMs / 1000)}s, giving up)` }\n }\n if (firstWait) {\n firstWait = false\n opts.onWait?.(r.reason!, r.waitMs!)\n }\n // Cap the sleep so concurrent waiters re-check often enough to grab the\n // next slot in turn, rather than all firing at the same instant.\n const remaining = deadline - Date.now()\n const sleepMs = Math.min(r.waitMs ?? 1000, remaining, 5_000)\n await new Promise((resolve) => setTimeout(resolve, sleepMs))\n }\n }\n\n /**\n * Current usage for an agent — used by the dashboard and `agentx usage`.\n */\n usage(agentId: string): { lastMinute: number; lastHour: number } {\n const now = Date.now()\n const timestamps = this.windows.get(agentId) || []\n return {\n lastMinute: timestamps.filter((t) => t > now - 60_000).length,\n lastHour: timestamps.filter((t) => t > now - 3600_000).length,\n }\n }\n}\n","import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- Token usage tracker ---\n// Tracks token usage per agent per day with cache-aware, tier-aware cost\n// calculation. Persisted to .agentx/usage/{YYYY-MM-DD}.json.\n//\n// Design notes:\n// - Pricing is per-model-FAMILY (opus/sonnet/haiku). Exact model name\n// variants map via getModelFamily(). Override the table by dropping\n// .agentx/pricing/custom.json (same shape) — merged over defaults at\n// construction, lets operators adjust without a rebuild.\n// - Anthropic bills 1.5× input/output when a single request's total input\n// (input + cacheCreate + cacheRead) exceeds 200K tokens. We split each\n// recorded task into tier1/tier2 buckets and bill them separately so\n// long-context sessions are not undercounted.\n// - Per-channel breakdowns let operators see \"GitLab drove 70% of today's\n// spend\" — the lever for channel-specific session + context tuning.\n// - Session tracking (unique chatId:channel pairs seen today per agent)\n// gives an avg-tasks-per-session proxy — when it spikes, it's usually\n// retry/clarification chains, a signal the context budget or model\n// capability is under-sized for that traffic.\n\n// Anthropic pricing per million tokens (as of April 2026, 5-min ephemeral).\n// Keys here must match getModelFamily() output.\n//\n// IMPORTANT: Opus 4.6 is 3× cheaper than Opus 4.1. Haiku 4.5 is more expensive\n// than Haiku 3.5. Our getModelFamily() resolves the exact version so the cost\n// report matches what Anthropic actually bills. Source: LiteLLM pricing cache\n// at ~/.cache/codeburn/litellm-pricing.json + codeburn/src/models.ts.\n//\n// Cache multipliers (consistent across all models):\n// cache write (5-min ephemeral) = 1.25 × input\n// cache read = 0.10 × input\nexport const CACHE_AWARE_PRICING: Record<string, { input: number; output: number; cacheRead: number; cacheCreate: number }> = {\n // Opus 4.6 / 4.5 — $5/$25 per MTok\n \"claude-opus-4-6\": { input: 5, output: 25, cacheRead: 0.5, cacheCreate: 6.25 },\n \"claude-opus-4-5\": { input: 5, output: 25, cacheRead: 0.5, cacheCreate: 6.25 },\n // Opus 4.1 and earlier — $15/$75 per MTok (legacy, keep for historical runs)\n \"claude-opus\": { input: 15, output: 75, cacheRead: 1.5, cacheCreate: 18.75 },\n // Sonnet 4.6 / 4.5 — $3/$15 per MTok\n \"claude-sonnet\": { input: 3, output: 15, cacheRead: 0.3, cacheCreate: 3.75 },\n // Haiku 4.5 — $1/$5 per MTok (more expensive than Haiku 3.5)\n \"claude-haiku\": { input: 1, output: 5, cacheRead: 0.1, cacheCreate: 1.25 },\n}\n\n/** Per-request input size (input + cacheCreate + cacheRead) above this\n * threshold triggers Anthropic's extended-context tier: all four rates\n * get multiplied. Applies to Opus 4.x and Sonnet 4.x 1M variants. */\nexport const CONTEXT_TIER_THRESHOLD = 200_000\nexport const TIER2_MULTIPLIER = 1.5\n\nexport function getModelFamily(model: string): string {\n const lower = model.toLowerCase()\n if (lower.includes(\"opus\")) {\n // Opus 4.6 and 4.5 are 3× cheaper than 4.1 — must distinguish.\n if (lower.includes(\"4-6\") || lower.includes(\"4.6\") || lower.includes(\"opus-4-6\")) return \"claude-opus-4-6\"\n if (lower.includes(\"4-5\") || lower.includes(\"4.5\") || lower.includes(\"opus-4-5\")) return \"claude-opus-4-5\"\n return \"claude-opus\" // legacy 4.1 and earlier\n }\n if (lower.includes(\"haiku\")) return \"claude-haiku\"\n return \"claude-sonnet\"\n}\n\nexport interface ChannelUsage {\n tasks: number\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreateTokens: number\n tier2InputTokens: number\n tier2OutputTokens: number\n tier2CacheReadTokens: number\n tier2CacheCreateTokens: number\n totalDuration: number\n errors: number\n}\n\nexport interface AgentUsage {\n tasks: number\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreateTokens: number\n /** Tokens from requests whose input crossed CONTEXT_TIER_THRESHOLD (billed at\n * TIER2_MULTIPLIER × normal rate). Kept separate from the tier1 buckets so\n * cost math stays honest when a session accumulates past 200K. */\n tier2InputTokens?: number\n tier2OutputTokens?: number\n tier2CacheReadTokens?: number\n tier2CacheCreateTokens?: number\n totalDuration: number\n errors: number\n model?: string\n /** Per-channel breakdown so we can attribute cost to Telegram vs GitLab vs WhatsApp. */\n byChannel?: Record<string, ChannelUsage>\n /** Unique session identifiers (channel:chatId) this agent participated in\n * today. Used to derive avg tasks/session. Capped at 500 entries to keep\n * the daily JSON small. */\n sessionKeys?: string[]\n}\n\nexport interface DailyUsage {\n date: string\n agents: Record<string, AgentUsage>\n}\n\nexport interface DailyReport {\n date: string\n totalTasks: number\n totalInput: number\n totalOutput: number\n totalCacheRead: number\n totalCacheCreate: number\n totalCost: number\n topAgent: string\n topCost: number\n agentCosts: Record<string, number>\n /** Per-agent avg tasks/session — high values suggest retry chains. */\n agentSessionStats: Record<string, { sessions: number; avgTasksPerSession: number }>\n /** Aggregate cost by channel across all agents. */\n byChannel: Record<string, number>\n}\n\n/** Shape of a per-request usage report coming from the runtime. Keeps the\n * record() signature tidy and lets callers opt into tier2 accounting. */\nexport interface TaskUsage {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheCreateTokens: number\n}\n\n/** Split a TaskUsage into tier1 and tier2 buckets using the same\n * CONTEXT_TIER_THRESHOLD that record() applies. Output inherits the\n * tier of its request. Returned as a flat object so callers can spread\n * it directly into bus payloads or DB writes. */\nexport function splitTaskUsageByTier(u: TaskUsage): {\n inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheCreateTokens: number\n tier2InputTokens: number; tier2OutputTokens: number; tier2CacheReadTokens: number; tier2CacheCreateTokens: number\n} {\n const totalInput = u.inputTokens + u.cacheReadTokens + u.cacheCreateTokens\n if (totalInput > CONTEXT_TIER_THRESHOLD) {\n return {\n inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreateTokens: 0,\n tier2InputTokens: u.inputTokens, tier2OutputTokens: u.outputTokens,\n tier2CacheReadTokens: u.cacheReadTokens, tier2CacheCreateTokens: u.cacheCreateTokens,\n }\n }\n return {\n inputTokens: u.inputTokens, outputTokens: u.outputTokens,\n cacheReadTokens: u.cacheReadTokens, cacheCreateTokens: u.cacheCreateTokens,\n tier2InputTokens: 0, tier2OutputTokens: 0, tier2CacheReadTokens: 0, tier2CacheCreateTokens: 0,\n }\n}\n\nconst SESSION_KEYS_MAX = 500\nconst PRICING_OVERRIDE_PATH = \".agentx/pricing/custom.json\"\n\nexport class TokenTracker {\n private dir: string\n private cache: DailyUsage | null = null\n private pricing: typeof CACHE_AWARE_PRICING\n\n constructor(baseDir: string = resolve(process.cwd(), \".agentx/usage\")) {\n this.dir = baseDir\n mkdirSync(this.dir, { recursive: true })\n this.pricing = loadPricingWithOverrides()\n }\n\n /** Returns the active pricing table (hardcoded defaults with any per-model\n * overrides merged in). Useful for UI / debugging. */\n getPricing(): typeof CACHE_AWARE_PRICING {\n return this.pricing\n }\n\n /**\n * Calculate cost for a slice of usage. Accepts either a full AgentUsage or\n * a narrowed ChannelUsage — both expose the same token buckets we price on.\n * Tier2 tokens are billed at TIER2_MULTIPLIER × the normal rate.\n */\n static calculateCost(usage: AgentUsage | ChannelUsage, model?: string, pricingTable: typeof CACHE_AWARE_PRICING = CACHE_AWARE_PRICING): number {\n const family = getModelFamily(model || (usage as AgentUsage).model || \"claude-sonnet\")\n const p = pricingTable[family] || pricingTable[\"claude-sonnet\"]\n const tier2In = (usage as any).tier2InputTokens || 0\n const tier2Out = (usage as any).tier2OutputTokens || 0\n const tier2CR = (usage as any).tier2CacheReadTokens || 0\n const tier2CW = (usage as any).tier2CacheCreateTokens || 0\n\n const tier1 =\n ((usage.inputTokens || 0) / 1_000_000) * p.input +\n ((usage.outputTokens || 0) / 1_000_000) * p.output +\n ((usage.cacheReadTokens || 0) / 1_000_000) * p.cacheRead +\n ((usage.cacheCreateTokens || 0) / 1_000_000) * p.cacheCreate\n\n const tier2 =\n (tier2In / 1_000_000) * p.input +\n (tier2Out / 1_000_000) * p.output +\n (tier2CR / 1_000_000) * p.cacheRead +\n (tier2CW / 1_000_000) * p.cacheCreate\n\n return tier1 + tier2 * TIER2_MULTIPLIER\n }\n\n /**\n * Instance-level cost calculation — uses the tracker's resolved pricing\n * table so any custom override file is honored. Prefer this over the\n * static variant when available.\n */\n cost(usage: AgentUsage | ChannelUsage, model?: string): number {\n return TokenTracker.calculateCost(usage, model, this.pricing)\n }\n\n /**\n * Record a task execution with real or estimated token counts.\n *\n * `channel` attributes the cost to a surface (telegram/gitlab/whatsapp/cron).\n * `sessionKey` is an opaque \"this chat on this day\" identifier — we use it\n * to count unique sessions so an avg-tasks-per-session metric falls out.\n */\n record(\n agentId: string,\n duration: number,\n realUsage?: TaskUsage,\n messageLength?: number,\n responseLength?: number,\n error?: boolean,\n model?: string,\n channel?: string,\n sessionKey?: string,\n ): void {\n const daily = this.today()\n const agent = daily.agents[agentId] || {\n tasks: 0, inputTokens: 0, outputTokens: 0,\n cacheReadTokens: 0, cacheCreateTokens: 0,\n tier2InputTokens: 0, tier2OutputTokens: 0,\n tier2CacheReadTokens: 0, tier2CacheCreateTokens: 0,\n totalDuration: 0, errors: 0, byChannel: {}, sessionKeys: [],\n }\n\n agent.tasks++\n agent.totalDuration += duration\n if (model) agent.model = model\n\n // Split this request into tier1/tier2 based on its TOTAL input size\n // (input + cacheCreate + cacheRead). Output inherits the tier of its\n // request. Estimated paths (no realUsage) are always tier1 — they're\n // too small to reach the threshold.\n let i1 = 0, o1 = 0, cr1 = 0, cw1 = 0, i2 = 0, o2 = 0, cr2 = 0, cw2 = 0\n if (realUsage) {\n const totalInput = realUsage.inputTokens + realUsage.cacheReadTokens + realUsage.cacheCreateTokens\n if (totalInput > CONTEXT_TIER_THRESHOLD) {\n i2 = realUsage.inputTokens\n o2 = realUsage.outputTokens\n cr2 = realUsage.cacheReadTokens\n cw2 = realUsage.cacheCreateTokens\n } else {\n i1 = realUsage.inputTokens\n o1 = realUsage.outputTokens\n cr1 = realUsage.cacheReadTokens\n cw1 = realUsage.cacheCreateTokens\n }\n } else if (messageLength !== undefined && responseLength !== undefined) {\n i1 = Math.ceil(messageLength / 4)\n o1 = Math.ceil(responseLength / 4)\n }\n\n agent.inputTokens += i1\n agent.outputTokens += o1\n agent.cacheReadTokens += cr1\n agent.cacheCreateTokens += cw1\n agent.tier2InputTokens = (agent.tier2InputTokens || 0) + i2\n agent.tier2OutputTokens = (agent.tier2OutputTokens || 0) + o2\n agent.tier2CacheReadTokens = (agent.tier2CacheReadTokens || 0) + cr2\n agent.tier2CacheCreateTokens = (agent.tier2CacheCreateTokens || 0) + cw2\n\n if (error) agent.errors++\n\n // Per-channel attribution. Unknown channel falls into \"other\".\n if (!agent.byChannel) agent.byChannel = {}\n const chKey = (channel || \"other\").toLowerCase()\n const ch = agent.byChannel[chKey] || {\n tasks: 0, inputTokens: 0, outputTokens: 0,\n cacheReadTokens: 0, cacheCreateTokens: 0,\n tier2InputTokens: 0, tier2OutputTokens: 0,\n tier2CacheReadTokens: 0, tier2CacheCreateTokens: 0,\n totalDuration: 0, errors: 0,\n }\n ch.tasks++\n ch.totalDuration += duration\n ch.inputTokens += i1; ch.outputTokens += o1\n ch.cacheReadTokens += cr1; ch.cacheCreateTokens += cw1\n ch.tier2InputTokens += i2; ch.tier2OutputTokens += o2\n ch.tier2CacheReadTokens += cr2; ch.tier2CacheCreateTokens += cw2\n if (error) ch.errors++\n agent.byChannel[chKey] = ch\n\n // Session tracking — dedup sessions by opaque key (normally channel:chatId).\n if (sessionKey) {\n if (!agent.sessionKeys) agent.sessionKeys = []\n if (!agent.sessionKeys.includes(sessionKey)) {\n agent.sessionKeys.push(sessionKey)\n if (agent.sessionKeys.length > SESSION_KEYS_MAX) {\n agent.sessionKeys.splice(0, agent.sessionKeys.length - SESSION_KEYS_MAX)\n }\n }\n }\n\n daily.agents[agentId] = agent\n this.save(daily)\n }\n\n /**\n * Get today's usage summary.\n */\n today(): DailyUsage {\n const date = new Date().toISOString().slice(0, 10)\n\n if (this.cache?.date === date) return this.cache\n\n const path = this.filePath(date)\n if (existsSync(path)) {\n try {\n this.cache = JSON.parse(readFileSync(path, \"utf-8\"))\n return this.cache!\n } catch {}\n }\n\n this.cache = { date, agents: {} }\n return this.cache\n }\n\n /**\n * Get usage for a specific date.\n */\n getDate(date: string): DailyUsage | null {\n const path = this.filePath(date)\n if (!existsSync(path)) return null\n try {\n return JSON.parse(readFileSync(path, \"utf-8\"))\n } catch {\n return null\n }\n }\n\n /**\n * Generate a daily cost report for a given date. Includes per-agent,\n * per-channel costs and session statistics.\n */\n generateDailyReport(date?: string): DailyReport | null {\n const targetDate = date || new Date().toISOString().slice(0, 10)\n const usage = this.getDate(targetDate)\n if (!usage) return null\n\n let totalTasks = 0, totalInput = 0, totalOutput = 0\n let totalCacheRead = 0, totalCacheCreate = 0, totalCost = 0\n let topAgent = \"\", topCost = 0\n const agentCosts: Record<string, number> = {}\n const agentSessionStats: Record<string, { sessions: number; avgTasksPerSession: number }> = {}\n const byChannel: Record<string, number> = {}\n\n for (const [id, agent] of Object.entries(usage.agents)) {\n const cost = this.cost(agent)\n agentCosts[id] = cost\n totalTasks += agent.tasks\n totalInput += (agent.inputTokens || 0) + (agent.tier2InputTokens || 0)\n totalOutput += (agent.outputTokens || 0) + (agent.tier2OutputTokens || 0)\n totalCacheRead += (agent.cacheReadTokens || 0) + (agent.tier2CacheReadTokens || 0)\n totalCacheCreate += (agent.cacheCreateTokens || 0) + (agent.tier2CacheCreateTokens || 0)\n totalCost += cost\n if (cost > topCost) { topCost = cost; topAgent = id }\n\n const sessions = agent.sessionKeys?.length || 0\n agentSessionStats[id] = {\n sessions,\n avgTasksPerSession: sessions > 0 ? agent.tasks / sessions : agent.tasks,\n }\n\n for (const [chKey, ch] of Object.entries(agent.byChannel || {})) {\n const chCost = this.cost(ch, agent.model)\n byChannel[chKey] = (byChannel[chKey] || 0) + chCost\n }\n }\n\n return {\n date: targetDate, totalTasks, totalInput, totalOutput,\n totalCacheRead, totalCacheCreate, totalCost, topAgent, topCost, agentCosts,\n agentSessionStats, byChannel,\n }\n }\n\n /**\n * Append a daily report row to TOKEN_COSTS.md. Adds channel + session\n * breakdown inline for at-a-glance spend attribution.\n */\n appendToTokenCosts(report: DailyReport, filePath?: string): void {\n const file = filePath || resolve(process.cwd(), \".agentx/TOKEN_COSTS.md\")\n const fmt = (n: number) => n < 0.01 ? n.toFixed(4) : n.toFixed(2)\n const fmtTok = (n: number) =>\n n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M` :\n n >= 1_000 ? `${(n / 1_000).toFixed(0)}K` :\n String(n)\n\n const agentBreakdown = Object.entries(report.agentCosts)\n .sort(([, a], [, b]) => b - a)\n .map(([id, cost]) => {\n const stats = report.agentSessionStats[id]\n const sessTag = stats && stats.sessions > 0 ? ` (${stats.sessions}s, ${stats.avgTasksPerSession.toFixed(1)}t/s)` : \"\"\n return `${id}: $${fmt(cost)}${sessTag}`\n })\n .join(\", \")\n\n const channelBreakdown = Object.entries(report.byChannel)\n .sort(([, a], [, b]) => b - a)\n .map(([ch, cost]) => `${ch}: $${fmt(cost)}`)\n .join(\", \")\n\n const row = `| ${report.date} | ${report.totalTasks} | ${fmtTok(report.totalInput)} | ${fmtTok(report.totalOutput)} | ${fmtTok(report.totalCacheRead)} | ${fmtTok(report.totalCacheCreate)} | $${fmt(report.totalCost)} | ${agentBreakdown} | ${channelBreakdown} |`\n\n if (!existsSync(file)) {\n const header =\n \"# Token Costs\\n\\n\" +\n \"Daily token cost tracking. Auto-appended at midnight (Africa/Tunis).\\n\" +\n \"Agent breakdown: `agent: $cost (Ns, T.Tt/s)` — sessions, tasks/session.\\n\\n\" +\n \"| Date | Tasks | Input | Output | Cache R | Cache W | Cost | Per Agent | Per Channel |\\n\" +\n \"|------|-------|-------|--------|---------|---------|------|-----------|-------------|\\n\"\n writeFileSync(file, header + row + \"\\n\")\n } else {\n appendFileSync(file, row + \"\\n\")\n }\n }\n\n /**\n * Return true if TOKEN_COSTS.md already has a row for `date`. Lets callers\n * skip duplicate appends after daemon restarts or timer-edge double fires.\n */\n hasTokenCostsEntry(date: string, filePath?: string): boolean {\n const file = filePath || resolve(process.cwd(), \".agentx/TOKEN_COSTS.md\")\n if (!existsSync(file)) return false\n try {\n const content = readFileSync(file, \"utf-8\")\n // Rows look like: `| 2026-04-18 | ...`\n return new RegExp(`^\\\\|\\\\s*${date}\\\\s*\\\\|`, \"m\").test(content)\n } catch {\n return false\n }\n }\n\n /**\n * Find the most recent date already logged to TOKEN_COSTS.md (YYYY-MM-DD).\n * Returns null when the file doesn't exist or has no rows.\n */\n lastTokenCostsDate(filePath?: string): string | null {\n const file = filePath || resolve(process.cwd(), \".agentx/TOKEN_COSTS.md\")\n if (!existsSync(file)) return null\n try {\n const content = readFileSync(file, \"utf-8\")\n const dates = content.match(/^\\|\\s*(\\d{4}-\\d{2}-\\d{2})\\s*\\|/gm) ?? []\n if (dates.length === 0) return null\n // Extract the date portion and pick the max — rows can be out of order\n // after a catch-up, so don't rely on file order.\n const parsed = dates\n .map((m) => m.match(/(\\d{4}-\\d{2}-\\d{2})/)?.[1])\n .filter((d): d is string => !!d)\n parsed.sort()\n return parsed[parsed.length - 1] ?? null\n } catch {\n return null\n }\n }\n\n /**\n * Append reports for any days between (lastLogged + 1) and `throughDate`\n * that have usage on disk but no row in TOKEN_COSTS.md. Idempotent —\n * safe to call on every daemon startup. Bounded to `maxDays` to avoid\n * a stalled daemon backfilling a year's worth on first start.\n *\n * Returns the list of dates actually appended, for logging.\n */\n catchUpTokenCosts(\n throughDate: string,\n opts: { maxDays?: number; filePath?: string } = {},\n ): string[] {\n const maxDays = opts.maxDays ?? 30\n const appended: string[] = []\n\n const last = this.lastTokenCostsDate(opts.filePath)\n // Walk backward from throughDate, collecting dates to fill. Stop at the\n // last-logged date (exclusive) or after maxDays, whichever comes first.\n const candidates: string[] = []\n const cursor = new Date(`${throughDate}T00:00:00Z`)\n for (let i = 0; i < maxDays; i++) {\n const date = cursor.toISOString().slice(0, 10)\n if (last && date <= last) break\n candidates.push(date)\n cursor.setUTCDate(cursor.getUTCDate() - 1)\n }\n // Oldest-first so the file stays chronological.\n candidates.reverse()\n\n for (const date of candidates) {\n if (this.hasTokenCostsEntry(date, opts.filePath)) continue\n const report = this.generateDailyReport(date)\n if (!report || report.totalTasks === 0) continue\n this.appendToTokenCosts(report, opts.filePath)\n appended.push(date)\n }\n return appended\n }\n\n /**\n * Get summary across last N days.\n */\n summary(days: number = 7): {\n totalTasks: number\n totalTokens: number\n totalInput: number\n totalOutput: number\n totalCacheRead: number\n totalCacheCreate: number\n cacheHitRatio: number\n totalErrors: number\n totalCost: number\n byAgent: Record<string, { tasks: number; input: number; output: number; cacheRead: number; cacheCreate: number; total: number; avgDuration: number; cost: number }>\n byChannel: Record<string, { tasks: number; cost: number }>\n sessionStats: Record<string, { sessions: number; avgTasksPerSession: number }>\n } {\n let totalTasks = 0\n let totalInput = 0\n let totalOutput = 0\n let totalCacheRead = 0\n let totalCacheCreate = 0\n let totalErrors = 0\n let totalCost = 0\n const byAgent: Record<string, { tasks: number; input: number; output: number; cacheRead: number; cacheCreate: number; total: number; avgDuration: number; totalDuration: number; cost: number; model?: string; sessions: number }> = {}\n const byChannel: Record<string, { tasks: number; cost: number }> = {}\n\n for (let i = 0; i < days; i++) {\n const date = new Date(Date.now() - i * 86400_000).toISOString().slice(0, 10)\n const usage = this.getDate(date)\n if (!usage) continue\n\n for (const [id, agent] of Object.entries(usage.agents)) {\n const cost = this.cost(agent)\n totalTasks += agent.tasks\n const agentInput = (agent.inputTokens || 0) + (agent.tier2InputTokens || 0)\n const agentOutput = (agent.outputTokens || 0) + (agent.tier2OutputTokens || 0)\n const agentCR = (agent.cacheReadTokens || 0) + (agent.tier2CacheReadTokens || 0)\n const agentCW = (agent.cacheCreateTokens || 0) + (agent.tier2CacheCreateTokens || 0)\n totalInput += agentInput\n totalOutput += agentOutput\n totalCacheRead += agentCR\n totalCacheCreate += agentCW\n totalErrors += agent.errors\n totalCost += cost\n\n const existing = byAgent[id] || { tasks: 0, input: 0, output: 0, cacheRead: 0, cacheCreate: 0, total: 0, avgDuration: 0, totalDuration: 0, cost: 0, sessions: 0 }\n existing.tasks += agent.tasks\n existing.input += agentInput\n existing.output += agentOutput\n existing.cacheRead += agentCR\n existing.cacheCreate += agentCW\n existing.total = existing.input + existing.output + existing.cacheRead + existing.cacheCreate\n existing.totalDuration += agent.totalDuration\n existing.avgDuration = existing.totalDuration / existing.tasks\n existing.cost += cost\n existing.sessions += agent.sessionKeys?.length || 0\n if (agent.model) existing.model = agent.model\n byAgent[id] = existing\n\n for (const [chKey, ch] of Object.entries(agent.byChannel || {})) {\n const chCost = this.cost(ch, agent.model)\n const acc = byChannel[chKey] || { tasks: 0, cost: 0 }\n acc.tasks += ch.tasks\n acc.cost += chCost\n byChannel[chKey] = acc\n }\n }\n }\n\n const totalTokens = totalInput + totalOutput + totalCacheRead + totalCacheCreate\n const cacheHitRatio = (totalCacheRead + totalCacheCreate) > 0\n ? totalCacheRead / (totalCacheRead + totalCacheCreate)\n : 0\n\n const sessionStats: Record<string, { sessions: number; avgTasksPerSession: number }> = {}\n for (const [id, a] of Object.entries(byAgent)) {\n sessionStats[id] = {\n sessions: a.sessions,\n avgTasksPerSession: a.sessions > 0 ? a.tasks / a.sessions : a.tasks,\n }\n }\n\n return { totalTasks, totalTokens, totalInput, totalOutput, totalCacheRead, totalCacheCreate, cacheHitRatio, totalErrors, totalCost, byAgent, byChannel, sessionStats }\n }\n\n private filePath(date: string): string {\n return resolve(this.dir, `${date}.json`)\n }\n\n private save(usage: DailyUsage): void {\n try {\n writeFileSync(this.filePath(usage.date), JSON.stringify(usage, null, 2))\n } catch {}\n }\n}\n\n/**\n * Load CACHE_AWARE_PRICING and merge any operator-supplied override. The\n * override file is optional; operators tweak rates (e.g. when Anthropic\n * publishes a price change) without rebuilding the daemon.\n */\nfunction loadPricingWithOverrides(): typeof CACHE_AWARE_PRICING {\n const result = JSON.parse(JSON.stringify(CACHE_AWARE_PRICING))\n try {\n const p = resolve(process.cwd(), PRICING_OVERRIDE_PATH)\n if (!existsSync(p)) return result\n const raw = JSON.parse(readFileSync(p, \"utf-8\"))\n for (const [family, rates] of Object.entries(raw)) {\n if (!result[family]) result[family] = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 }\n Object.assign(result[family], rates as object)\n }\n } catch {\n // Malformed override file — stay with defaults rather than crash at boot.\n }\n return result\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","import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// --- AgentMemory: Claude-Code-style structured memory per agent ---\n//\n// Four memory kinds, each a separate file with YAML frontmatter + markdown\n// body, gathered under a per-agent root index (MEMORY.md):\n//\n// user/ — \"who this human is, what they do, what they know\"\n// feedback/ — \"rules the user gave me; what to do / avoid\"\n// project/ — \"facts about current work the code can't reveal\"\n// reference/ — \"pointers to external systems (linear board, grafana, …)\"\n//\n// Layout on disk:\n// .agentx/agent-memory/<agentId>/\n// MEMORY.md — one-line hook per memory, auto-maintained\n// user_deep_backend_expertise.md\n// feedback_no_mock_database.md\n// ...\n//\n// Philosophy: the wiki is authoritative / cross-agent / documented.\n// Memory is experiential / per-agent / \"what I learned across runs.\" A\n// memory can be promoted to a wiki article (planned follow-up) after\n// multiple agents confirm the same fact, but we don't enforce that\n// here. Authors keep both layers parallel.\n\nexport type MemoryType = \"user\" | \"feedback\" | \"project\" | \"reference\"\n\nexport interface MemoryRecord {\n /** Slug-safe name, unique within an agent. */\n name: string\n type: MemoryType\n /** One-line hook — used as the MEMORY.md index entry. */\n description: string\n /** The body, excluding frontmatter. */\n body: string\n createdAt: string\n updatedAt: string\n}\n\nconst TYPES: MemoryType[] = [\"user\", \"feedback\", \"project\", \"reference\"]\n\nexport interface AgentMemoryOptions {\n baseDir?: string\n}\n\nexport class AgentMemory {\n readonly baseDir: string\n\n constructor(opts: AgentMemoryOptions = {}) {\n const root = opts.baseDir ?? resolve(process.cwd(), \".agentx\")\n this.baseDir = resolve(root, \"agent-memory\")\n }\n\n private dirFor(agentId: string): string {\n const safe = slug(agentId)\n const dir = resolve(this.baseDir, safe)\n mkdirSync(dir, { recursive: true })\n return dir\n }\n\n private fileFor(agentId: string, type: MemoryType, name: string): string {\n return resolve(this.dirFor(agentId), `${type}_${slug(name)}.md`)\n }\n\n private indexPath(agentId: string): string {\n return resolve(this.dirFor(agentId), \"MEMORY.md\")\n }\n\n /** Save (create or update) a memory. Keeps createdAt stable across\n * updates; bumps updatedAt. Always rewrites MEMORY.md. */\n save(args: {\n agentId: string\n type: MemoryType\n name: string\n description: string\n body: string\n }): MemoryRecord {\n if (!TYPES.includes(args.type)) throw new Error(`invalid memory type: ${args.type}`)\n if (!args.name.trim()) throw new Error(\"memory name is required\")\n if (!args.description.trim()) throw new Error(\"memory description is required\")\n\n const path = this.fileFor(args.agentId, args.type, args.name)\n const now = new Date().toISOString()\n let createdAt = now\n const existing = safeRead(path)\n if (existing) {\n const parsed = parseMemoryFile(existing)\n if (parsed) createdAt = parsed.createdAt\n }\n const record: MemoryRecord = {\n name: slug(args.name),\n type: args.type,\n description: args.description.trim(),\n body: args.body.trimEnd(),\n createdAt,\n updatedAt: now,\n }\n writeFileSync(path, serialize(record))\n this.rewriteIndex(args.agentId)\n return record\n }\n\n get(agentId: string, name: string): MemoryRecord | null {\n for (const type of TYPES) {\n const path = this.fileFor(agentId, type, name)\n if (!existsSync(path)) continue\n const parsed = parseMemoryFile(safeRead(path) ?? \"\")\n if (parsed) return parsed\n }\n return null\n }\n\n /** Every memory for an agent. Sorted by type then name for stable\n * index rendering. */\n list(agentId: string): MemoryRecord[] {\n const dir = this.dirFor(agentId)\n if (!existsSync(dir)) return []\n const out: MemoryRecord[] = []\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\") || entry.name === \"MEMORY.md\") continue\n const parsed = parseMemoryFile(safeRead(resolve(dir, entry.name)) ?? \"\")\n if (parsed) out.push(parsed)\n }\n return out.sort((a, b) => {\n const ta = TYPES.indexOf(a.type)\n const tb = TYPES.indexOf(b.type)\n if (ta !== tb) return ta - tb\n return a.name.localeCompare(b.name)\n })\n }\n\n /** Remove a memory by name. Returns true on success, false if none found. */\n remove(agentId: string, name: string): boolean {\n let removed = false\n for (const type of TYPES) {\n const path = this.fileFor(agentId, type, name)\n if (existsSync(path)) { unlinkSync(path); removed = true }\n }\n if (removed) this.rewriteIndex(agentId)\n return removed\n }\n\n /** The content of MEMORY.md — used at prompt-build time to inline into\n * the agent's system prompt. Returns \"\" when the agent has no\n * memories (so the prompt builder can no-op). */\n indexMarkdown(agentId: string): string {\n const path = this.indexPath(agentId)\n return safeRead(path) ?? \"\"\n }\n\n /** Push the agent's memory into its workspace so Claude Code sessions\n * pick it up automatically. Does two things:\n *\n * 1. Writes `<workspace>/.agentx-memory.md` — full MEMORY.md content,\n * explicit file the skill can `Read` at session start.\n * 2. Merges that same content into `<workspace>/CLAUDE.md` between\n * sentinel comments so Claude Code auto-loads it into the cached\n * system context on every session. Idempotent: subsequent syncs\n * replace the sentinel block.\n *\n * Never touches content outside the sentinel block — if the file\n * doesn't exist, creates it with just the sentinels. Safe to call\n * on any save / delete. */\n syncToWorkspace(agentId: string, workspacePath: string): void {\n if (!workspacePath) return\n const memory = this.indexMarkdown(agentId).trim()\n\n // 1. Explicit file for the `remember` skill to Read if it wants to.\n const explicitPath = resolve(workspacePath, \".agentx-memory.md\")\n if (memory) {\n writeFileSync(explicitPath, memory + \"\\n\")\n } else if (existsSync(explicitPath)) {\n unlinkSync(explicitPath)\n }\n\n // 2. Sentinel-merged into CLAUDE.md for auto-inject.\n const claudePath = resolve(workspacePath, \"CLAUDE.md\")\n const start = \"<!-- AGENTX-MEMORY-START — auto-managed by agentx; edit via `agentx memory` -->\"\n const end = \"<!-- AGENTX-MEMORY-END -->\"\n const block = memory\n ? `${start}\\n${memory}\\n${end}`\n : \"\"\n\n const existing = safeRead(claudePath) ?? \"\"\n const startIdx = existing.indexOf(start)\n const endIdx = existing.indexOf(end)\n\n let next: string\n if (startIdx >= 0 && endIdx > startIdx) {\n // Replace existing sentinel block (or remove it if memory is empty).\n const before = existing.slice(0, startIdx).replace(/\\n+$/, \"\")\n const after = existing.slice(endIdx + end.length).replace(/^\\n+/, \"\")\n next = block\n ? [before, block, after].filter(Boolean).join(\"\\n\\n\") + \"\\n\"\n : [before, after].filter(Boolean).join(\"\\n\\n\") + (before || after ? \"\\n\" : \"\")\n } else if (block) {\n // No prior block — append to the end of CLAUDE.md (creates the file\n // if needed). Keeps any existing content untouched.\n next = existing\n ? existing.replace(/\\n+$/, \"\") + \"\\n\\n\" + block + \"\\n\"\n : block + \"\\n\"\n } else {\n // Empty memory + no prior block → nothing to do.\n return\n }\n writeFileSync(claudePath, next)\n }\n\n /** Regenerate MEMORY.md from the on-disk files. Called automatically\n * after every save / remove; safe to call manually. */\n rewriteIndex(agentId: string): void {\n const entries = this.list(agentId)\n if (entries.length === 0) {\n const p = this.indexPath(agentId)\n if (existsSync(p)) unlinkSync(p)\n return\n }\n const grouped: Record<MemoryType, MemoryRecord[]> = {\n user: [], feedback: [], project: [], reference: [],\n }\n for (const r of entries) grouped[r.type].push(r)\n\n const lines: string[] = []\n lines.push(`# Memory — ${agentId}`, \"\")\n lines.push(\"This is the agent's experiential memory: user preferences,\")\n lines.push(\"feedback, project-specific context, and external references.\")\n lines.push(\"Updated via `agentx memory` CLI or by the agent itself.\")\n for (const type of TYPES) {\n const group = grouped[type]\n if (!group.length) continue\n lines.push(\"\", `## ${titleCase(type)}`, \"\")\n for (const r of group) {\n lines.push(`- **${r.name}** — ${r.description} _(updated ${r.updatedAt.slice(0, 10)})_`)\n }\n }\n lines.push(\"\")\n writeFileSync(this.indexPath(agentId), lines.join(\"\\n\"))\n }\n}\n\n// --- Serialisation helpers ---------------------------------------------\n\nfunction serialize(r: MemoryRecord): string {\n const frontmatter = [\n \"---\",\n `name: ${r.name}`,\n `description: ${escapeYaml(r.description)}`,\n `type: ${r.type}`,\n `created: ${r.createdAt}`,\n `updated: ${r.updatedAt}`,\n \"---\",\n \"\",\n ].join(\"\\n\")\n return frontmatter + r.body + \"\\n\"\n}\n\n/** Minimal frontmatter parser. Accepts `key: value` lines between `---`\n * markers; body is everything after. No YAML flow syntax — if we need\n * it later, swap for a proper parser. */\nexport function parseMemoryFile(raw: string): MemoryRecord | null {\n if (!raw.startsWith(\"---\\n\")) return null\n const end = raw.indexOf(\"\\n---\\n\", 4)\n if (end < 0) return null\n const header = raw.slice(4, end)\n const body = raw.slice(end + 5).replace(/^\\n+/, \"\").trimEnd()\n const fm: Record<string, string> = {}\n for (const line of header.split(\"\\n\")) {\n const colon = line.indexOf(\":\")\n if (colon < 0) continue\n const key = line.slice(0, colon).trim()\n const val = unescapeYaml(line.slice(colon + 1).trim())\n fm[key] = val\n }\n if (!fm.name || !fm.type || !fm.description) return null\n if (!TYPES.includes(fm.type as MemoryType)) return null\n return {\n name: fm.name,\n type: fm.type as MemoryType,\n description: fm.description,\n body,\n createdAt: fm.created ?? \"\",\n updatedAt: fm.updated ?? fm.created ?? \"\",\n }\n}\n\nfunction escapeYaml(s: string): string {\n if (/[:#\\n]/.test(s)) return `\"${s.replace(/\"/g, \"\\\\\\\"\")}\"`\n return s\n}\n\nfunction unescapeYaml(s: string): string {\n if (s.startsWith(\"\\\"\") && s.endsWith(\"\\\"\") && s.length >= 2) {\n return s.slice(1, -1).replace(/\\\\\"/g, \"\\\"\")\n }\n return s\n}\n\nfunction slug(s: string): string {\n return s.toLowerCase().replace(/[^a-z0-9]+/g, \"_\").replace(/^_+|_+$/g, \"\") || \"unnamed\"\n}\n\nfunction titleCase(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction safeRead(path: string): string | null {\n try { return readFileSync(path, \"utf-8\") } catch { return null }\n}\n","// --- Message Queue with Modes ---\n//\n// Handles messages arriving while an agent is busy processing.\n// Prevents duplicate runs, lost messages, and race conditions.\n//\n// Modes:\n// collect — batch messages arriving during a run, deliver all at once when done\n// followup — queue each message as a separate follow-up turn after current run\n// drop — silently discard messages while busy (for non-critical channels)\n\nexport type QueueMode = \"collect\" | \"followup\" | \"drop\"\n\nexport interface QueuedMessage {\n text: string\n sender: string\n timestamp: number\n channel: string\n chatId: string\n /** Full context from the original message, preserved for re-routing */\n originalContext?: Record<string, unknown>\n}\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 *\n * taskId is the ULID for the per-execution trace\n * (src/storage/traces.ts). Emitters generate it BEFORE firing this\n * event so it can also be threaded into the runtime call chain;\n * the SQLite subscriber uses it to open the trace row, and the\n * streaming parser uses it to record per-step tool_use/tool_result\n * rows. Optional for backward compatibility — emitters that haven't\n * been updated still produce a valid event and the subscriber\n * falls back to allocating its own id. */\n \"task:started\": {\n agentId: string\n channel: string\n chatId: string\n messagePreview: string // first ~200 chars\n at: string\n taskId?: string\n }\n\n /** A single step inside an in-flight task — typically a tool call or\n * tool result inside the Claude streaming response. Producer is the\n * per-event callback inside registry.execute's streaming path; the\n * SQLite subscriber persists each step into task_trace_steps under\n * the task's ULID. inputSummary / outputSummary are caller-byte-\n * capped (typically 8KB) so a runaway tool result can't blow up the\n * bus payload or DB row size.\n *\n * Conventional `name` values: \"tool_use\" | \"tool_result\" |\n * \"llm_message\" | \"session_rotation\" | \"error\" | \"preflight\". Free-\n * form strings are accepted to let future capture sites extend\n * without a schema bump. */\n \"task:step\": {\n taskId: string\n agentId: string\n name: string\n action?: string\n status?: \"ok\" | \"error\" | \"in-flight\"\n inputSummary?: string\n outputSummary?: string\n error?: string\n ms?: number\n at: string\n }\n\n /** Task finished — success or failure. Includes usage so subscribers\n * can write to billing, sqlite, or analytics without re-querying.\n *\n * Tier-2 fields hold the portion of THIS task's tokens that fell into\n * the \"above-threshold\" pricing tier (decided at record time by\n * TokenTracker; see usage_daily v5 in src/storage/sqlite.ts). They are\n * optional and additive — emitters that don't compute the split simply\n * omit them and subscribers treat the missing values as 0. */\n \"task:completed\": {\n /** Trace ULID — when present, lets subscribers finalize the exact\n * trace row that the matching task:started opened, eliminating the\n * ambiguity when two tasks for the same (agent, channel, chatId)\n * overlap. Optional for backward compatibility; subscribers fall\n * back to a pending-map lookup when absent. */\n taskId?: string\n agentId: string\n channel: string\n chatId: string\n durationMs: number\n error?: string\n inputTokens?: number\n outputTokens?: number\n cacheReadTokens?: number\n cacheCreateTokens?: number\n tier2InputTokens?: number\n tier2OutputTokens?: number\n tier2CacheReadTokens?: number\n tier2CacheCreateTokens?: number\n at: string\n }\n\n /** Claude session rotation fired (stale / max-turns / tier-2). */\n \"session:rotated\": {\n /** Trace ULID of the in-flight task at rotation time, when known.\n * When set, the rotation is recorded as a step on that trace. */\n taskId?: string\n agentId: string\n channel: string\n chatId: string\n reason: \"stale\" | \"max-turns\" | \"tier-2\"\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, splitTaskUsageByTier } 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 { AgentMemory } from \"./agent-memory\"\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 { getDefaultLedger } from \"@/intent/instance\"\nimport { loadRecipes, resolveRecipes, type RecipeIndex } from \"./references/recipes\"\nimport type { ReferenceIndex } from \"./references/types\"\nimport { getEventBus } from \"@/events/bus\"\nimport { newEventId } from \"@/intent/ulid\"\nimport { debug } from \"@/observability/debug\"\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 */\n/**\n * Cap a string at the configured byte budget, suffixing with \"…[+N]\" when\n * truncated so the trace reader knows there's more upstream. 8KB lets us\n * keep a useful preview of e.g. a Bash tool's stdout while bounding the\n * row size — anything larger almost always exceeds operator attention.\n */\nexport const TRACE_STEP_SUMMARY_BYTES = 8 * 1024\nexport function clipForTrace(s: string): string {\n if (s.length <= TRACE_STEP_SUMMARY_BYTES) return s\n const remaining = s.length - TRACE_STEP_SUMMARY_BYTES\n return s.slice(0, TRACE_STEP_SUMMARY_BYTES) + `…[+${remaining}]`\n}\n\n/**\n * Translate a Claude stream-json event into zero-or-more task:step bus\n * events. Mirrors the block walks in makeStreamEventFormatter but emits\n * structured rows for SQLite persistence rather than terminal text.\n *\n * Why a separate function (and not extending the formatter)? Two\n * concerns. The formatter is for the dashboard's live terminal modal —\n * its output is a string and its job is to read like a transcript. The\n * trace store wants typed rows that survive a daemon restart. Keeping\n * them separate means future refactors of either don't ripple.\n */\nexport function emitTraceStepsFromStreamEvent(taskId: string, agentId: string, event: any): void {\n if (!event || typeof event !== \"object\") return\n const t = event.type\n const at = new Date().toISOString()\n const bus = getEventBus()\n\n if (t === \"assistant\" && event.message?.content) {\n for (const block of event.message.content) {\n if (block.type === \"tool_use\") {\n const inputJson = block.input != null ? JSON.stringify(block.input) : \"\"\n bus.emit(\"task:step\", {\n taskId,\n agentId,\n name: \"tool_use\",\n action: typeof block.name === \"string\" ? block.name : null,\n status: \"in-flight\",\n inputSummary: inputJson ? clipForTrace(inputJson) : null,\n at,\n } as any)\n }\n }\n return\n }\n if (t === \"user\" && event.message?.content) {\n for (const block of event.message.content) {\n if (block.type === \"tool_result\") {\n const content = Array.isArray(block.content)\n ? block.content.map((b: any) => (typeof b?.text === \"string\" ? b.text : \"\")).join(\"\")\n : typeof block.content === \"string\" ? block.content : \"\"\n bus.emit(\"task:step\", {\n taskId,\n agentId,\n // Action mirrors the originating tool name when Claude provides\n // tool_use_id. We don't have a quick lookup here without\n // threading more state, so leave action null and let consumers\n // pair via tool_use_id if they care.\n name: \"tool_result\",\n status: block.is_error ? \"error\" : \"ok\",\n outputSummary: content ? clipForTrace(content) : null,\n at,\n } as any)\n }\n }\n return\n }\n}\n\nfunction makeStreamEventFormatter(): (event: any) => string {\n let textSeen = \"\"\n 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 /** Structured per-agent memory (Claude-Code-style: user / feedback /\n * project / reference). Separate from the BM25 fact store above —\n * that's for short-lived facts extracted from conversations; this is\n * for long-lived behavioural memory that inlines into the system\n * prompt on every task. */\n readonly agentMemory: AgentMemory = new AgentMemory()\n private patternStore: PatternStore\n private rateLimiter: RateLimiter\n private tokenTracker: TokenTracker\n private landscape?: LandscapeBuilder\n /** Mesh handle. When set, `execute` falls back to a peer that advertises\n * the requested agent before returning \"Unknown agent\". Duck-typed so\n * we don't create a circular import with src/a2a/mesh.ts. */\n private meshFallback?: {\n findPeerWithSkill: (skillId: string) => { peer: { url: string; token?: string } } | undefined\n sendTask: (peerName: string, text: string, agentId?: string, opts?: { timeoutMs?: number }) => Promise<string>\n directory: () => Array<{ peer: string; healthy: boolean; skills: Array<{ id: string }> }>\n }\n 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 classifierModel: config.graph.classifierModel,\n log: (...a) => log(\"[classifier]\", ...a),\n })\n }\n\n for (const [id, def] of Object.entries(config.agents)) {\n this.agents.set(id, {\n id,\n def,\n activeTasks: 0,\n totalTasks: 0,\n errors: 0,\n runningTasks: [],\n })\n }\n }\n\n /**\n * Set landscape builder (called after mesh init).\n */\n setLandscape(builder: LandscapeBuilder): void {\n this.landscape = builder\n }\n\n /** Wire the mesh so `execute` can fall back to a peer that hosts an\n * agent this node doesn't have. Called by the daemon after mesh boot.\n * Pass `undefined` to disable fallback (testing, mesh disabled). */\n setMeshFallback(mesh: NonNullable<AgentRegistry[\"meshFallback\"]>): void {\n this.meshFallback = mesh\n }\n\n /** 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 /** Fire-and-forget pre-rotation memo capture. Spawns a one-shot Haiku\n * call against the about-to-be-dropped Claude session asking for the\n * facts/decisions worth carrying forward, persists the result into\n * MemoryStore. Runs asynchronously so the calling rotation path\n * doesn't pay the extraction latency — the next user turn dispatches\n * immediately on the fresh session, and the memo lands in time for\n * whichever turn arrives ~30s later (which is when atlas's bloated-\n * session WhatsApp conversations actually need it).\n *\n * Errors are swallowed by design — memory continuity is best-effort,\n * it must never block or break the rotation it's hooked into. */\n private async captureRotationMemoAsync(\n agentId: string,\n def: AgentDef,\n resumeSessionId: string,\n channel: string,\n chatId: string,\n reason: string,\n ): Promise<void> {\n try {\n const { extractRotationMemo } = await import(\"./rotation-memo\")\n const result = await extractRotationMemo(def, resumeSessionId)\n if (!result.memo) {\n this.log(\n `[${agentId}] rotation memo skipped (${reason}, ${result.reason}, ${result.durationMs}ms)`,\n )\n return\n }\n // Pull a few keywords from the chatId / channel so the memo\n // surfaces from MemoryStore.findRelevant when the same chat\n // continues. Without keywords BM25 has nothing to score against\n // and the memo only floats up via the recent-fallback branch.\n const chatKeyword = chatId.split(/[:@.]/).filter(Boolean).slice(0, 4)\n this.memoryStore.addMemory(agentId, {\n agentId,\n category: \"fact\",\n content: `[Rotation memo · ${reason}] ${result.memo}`,\n keywords: [\"rotation-memo\", channel, ...chatKeyword],\n source: { channel, chatId, sender: \"system:rotation\", date: new Date().toISOString().slice(0, 10) },\n })\n this.log(\n `[${agentId}] rotation memo captured (${reason}, ${result.durationMs}ms, ${result.memo.length} chars)`,\n )\n } catch (e: any) {\n this.log(`rotation memo error: ${e?.message || e}`)\n }\n }\n\n /**\n * Build peer list for context engine.\n */\n private buildPeerList(agentId: string, channel?: string): Array<{ name: string; handle?: string; role?: string }> {\n const peers: Array<{ name: string; handle?: string; role?: string }> = []\n for (const [id, state] of this.agents) {\n if (id === agentId) continue\n peers.push({\n name: state.def.name,\n handle: this.getChannelHandle(id, channel),\n role: state.def.systemPrompt?.split(\"\\n\")[0]?.slice(0, 80),\n })\n }\n return peers\n }\n\n /**\n * Get the primary channel handle for an agent (e.g. \"@my_bot\" on telegram).\n */\n private getChannelHandle(agentId: string, channel?: string): string | undefined {\n const agent = this.agents.get(agentId)?.def\n if (!agent) return undefined\n\n // For telegram, find the mention that starts with @ (bot username)\n if (channel === \"telegram\") {\n return agent.mentions.find((m) => m.startsWith(\"@\"))\n }\n\n // Fallback: first mention\n return agent.mentions[0]\n }\n\n /**\n * Execute a task on an agent. Respects maxConcurrent limit.\n *\n * Wraps `executeInternal` to record an intent-ledger resolution\n * after completion when `task.intentRef` is set. The resolution\n * write clears the dispatched-but-unresolved slot in\n * `intent_decisions` so Inv-ActiveTaskSafety lookups stop blocking\n * subsequent dispatches to the same (project, subject). Without\n * this, every dispatched decision sits in-flight forever and the\n * active-task check becomes vacuously over-aggressive.\n */\n async execute(task: AgentTask, onDelta?: StreamCallback): Promise<AgentResponse> {\n const startedAt = Date.now()\n let response: AgentResponse\n try {\n response = await this.executeInternal(task, onDelta)\n } catch (e: any) {\n response = { content: \"\", error: e?.message ?? String(e) }\n }\n if (task.intentRef) {\n try {\n const status = response.error\n ? (/timed out|timeout/i.test(response.error) ? \"timed-out\" : \"failed\")\n : \"completed\"\n getDefaultLedger().recordResolution({\n decisionEventId: task.intentRef.eventId,\n decisionDecidedBy: task.intentRef.decidedBy,\n resolvedAt: Date.now(),\n status: status as \"completed\" | \"failed\" | \"timed-out\",\n durationMs: Date.now() - startedAt,\n resultSummary: response.error\n ? response.error.slice(0, 200)\n : (response.content?.slice(0, 200) ?? null),\n })\n } catch (e: any) {\n // Non-fatal — the ledger may have a unique-constraint hit (the\n // resolution was already recorded by a prior call), or the\n // ledger may have failed entirely. Either way, agent dispatch\n // must succeed regardless.\n this.log(`[ledger] resolution write failed for ${task.intentRef.eventId}/${task.intentRef.decidedBy}: ${e?.message ?? e}`)\n }\n }\n return response\n }\n\n /**\n * Internal dispatcher — the real body. See `execute` for the public\n * wrapper that adds intent-ledger resolution recording.\n */\n private async executeInternal(task: AgentTask, onDelta?: StreamCallback): 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 // Allocate the per-execution trace id eagerly so both the streaming\n // parser (per-step capture below) and the bus emit a few lines down\n // can target the same task_traces row. Closures defined here reference\n // it by name; the variable is set before any callback can fire.\n const traceTaskId = newEventId()\n task.taskId = traceTaskId\n\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 // Per-step trace capture (improvement plan #2). Fire one\n // task:step event per tool_use / tool_result block. The\n // SQLite subscriber persists it under traceTaskId.\n emitTraceStepsFromStreamEvent(traceTaskId, task.agentId, event)\n }\n } else {\n // Even without a dashboard subscriber, we want trace steps for\n // /traces/:id. Build a minimal onEvent that ONLY emits steps.\n onEvent = (event: any) => {\n emitTraceStepsFromStreamEvent(traceTaskId, task.agentId, event)\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 // Mirror the live channel before recording the new user message — on a\n // cold-create (fresh chatId, new day after rotation), this calls the\n // adapter's seedHistory and back-fills recent messages so the agent\n // doesn't start blind. No-op for warm sessions, non-channel callers\n // (cron/api/a2a), or channels without a seedHistory implementation.\n await this.sessions.seedIfEmpty(task.agentId, channel, chatId)\n\n // Record user message in session\n this.sessions.addUserMessage(task.agentId, channel, chatId, senderName, task.message)\n\n const taskStartedAt = Date.now()\n // traceTaskId allocated above (alongside the streaming onEvent\n // callback). Including it in task:started lets the SQLite\n // subscriber open the trace row under the same id the per-step\n // emitter uses.\n getEventBus().emit(\"task:started\", {\n agentId: task.agentId,\n channel,\n chatId,\n messagePreview: (task.message || \"\").slice(0, 200),\n at: new Date(taskStartedAt).toISOString(),\n taskId: traceTaskId,\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 void this.captureRotationMemoAsync(task.agentId, state.def, resumeSessionId, channel, chatId, \"stale\")\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 void this.captureRotationMemoAsync(task.agentId, state.def, resumeSessionId, channel, chatId, \"tier-2\")\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 void this.captureRotationMemoAsync(task.agentId, state.def, resumeSessionId, channel, chatId, \"max-turns\")\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 // Context-rebuild diagnostic. Fires under `--debug context` (or `all`).\n // The amnesia-vs-misreasoning question — \"did the agent see X in its\n // prompt?\" — was unanswerable from session JSONs alone (the rendered\n // prompt prefix is never persisted). This log captures it per-turn:\n // - resume vs fresh (after rotation, this is \"fresh\")\n // - sessionHistory length when rendered (chars + line count)\n // - session.messages snapshot: count, first ts, last ts, first/last\n // name+content preview so we can tell what's actually in the file.\n // Off by default — zero overhead unless the operator opted in.\n if (debug && (debug as any).cat) {\n try {\n const sess = this.sessions.getSession(task.agentId, channel, chatId)\n const msgs = sess.messages\n const first = msgs[0]\n const last = msgs[msgs.length - 1]\n const fingerprint = sessionHistory === undefined\n ? `resume=${resumeSessionId ? resumeSessionId.slice(0, 8) : \"?\"} (no rebuild)`\n : `fresh history=${sessionHistory.length}c/${sessionHistory.split(\"\\n\").length}L`\n const summary = `[${task.agentId}] ${channel}:${chatId} ${fingerprint} | session.messages=${msgs.length}` +\n (msgs.length > 0\n ? ` first=${first.timestamp.slice(11, 19)}(${first.role}:${(first.content || \"\").slice(0, 40).replace(/\\n/g, \" \")}) last=${last.timestamp.slice(11, 19)}(${last.role}:${(last.content || \"\").slice(0, 40).replace(/\\n/g, \" \")})`\n : \"\")\n debug.cat(\"context\", summary)\n } catch (e: any) {\n // Diagnostic logging must never throw into the hot path.\n debug.cat(\"context\", `[${task.agentId}] context-diag error: ${e?.message ?? e}`)\n }\n }\n\n // Verified deterministic references — opt-in per agent via\n // `contextReferences: true`. Only injected when starting a FRESH Claude\n // session: once a session is in progress, the agent already has any\n // facts it learned in earlier turns, and re-rendering them on every\n // resumed turn just bloats the per-turn user-message context. That\n // bloat compounded with --resume's full-history replay was hitting the\n // 180k tier-2 threshold in 5–10 turns and forcing constant rotation,\n // which discarded the conversation Claude session — causing exactly\n // the \"I don't have prior context\" symptom users observed.\n // The resolver itself runs cheaply in-memory; we just gate the rendered\n // block on `!resumeSessionId`.\n let referencesBlock: string | undefined\n if (state.def.contextReferences && !resumeSessionId) {\n try {\n const cacheKey = state.def.workspace\n let cached = this.referencesCache.get(cacheKey)\n if (!cached) {\n const [refs, recipes] = await 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 // AgentMemory — structured per-agent memory (user / feedback /\n // project / reference). Inlined into the cacheable system prompt so\n // it survives --resume and shows up on every turn. Empty when the\n // agent has no memories yet — no prompt bloat for fresh agents.\n const agentMemoryBlock = this.agentMemory.indexMarkdown(task.agentId)\n\n const systemPromptAppend = [\n state.def.systemPrompt || \"\",\n bootstrapContextText || \"\",\n agentMemoryBlock || \"\",\n ].filter((s) => s.trim().length > 0).join(\"\\n\\n\") || undefined\n\n const contextInput: ContextInput = {\n channel,\n chatId,\n channelScope: task.context?.group ? \"group\" : (channel === \"gitlab\" ? \"project\" : \"personal\"),\n groupName: task.context?.group,\n agentId: task.agentId,\n agentName: state.def.name,\n agentHandle: this.getChannelHandle(task.agentId, channel),\n systemPrompt: state.def.systemPrompt,\n sender: senderName,\n senderId: task.context?.senderId,\n senderUsername: task.context?.senderUsername,\n 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 // Split this request's tokens into tier1/tier2 buckets so subscribers\n // (sqlite usage_daily, audit, dashboard) record both lanes. The same\n // threshold that TokenTracker.record() applies — extracted as a\n // helper so the two sites can never drift.\n const split = response.usage ? splitTaskUsageByTier(response.usage) : undefined\n getEventBus().emit(\"task:completed\", {\n taskId: traceTaskId,\n agentId: task.agentId,\n channel,\n chatId,\n durationMs: Date.now() - taskStartedAt,\n error: response.error || undefined,\n inputTokens: split?.inputTokens,\n outputTokens: split?.outputTokens,\n cacheReadTokens: split?.cacheReadTokens,\n cacheCreateTokens: split?.cacheCreateTokens,\n tier2InputTokens: split?.tier2InputTokens,\n tier2OutputTokens: split?.tier2OutputTokens,\n tier2CacheReadTokens: split?.tier2CacheReadTokens,\n tier2CacheCreateTokens: split?.tier2CacheCreateTokens,\n 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 // Stamp the classifier's path on the entry so `wiki absorb` can\n // propagate it to the article's `graphPath` without\n // reconstructing fingerprints from transformed content. Without\n // this, articles never carry graphPath and the wiki retrieval\n // scorer multiplies the graph weight (default 0.6) by 0.\n meta: intent?.path?.length\n ? { intentPath: intent.path, intentPathLabel: intent.pathLabel }\n : undefined,\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","StringDecoder","friendlyModelError","raw","source","jsonMatch","parsed","inner","errType","errMessage","statusMatch","status","renderFriendlyError","f","_registry","getProcessRegistry","setProcessRegistry","r","processKeyToString","k","RegistryCapExceeded","message","scope","cap","ProcessRegistry","cfg","handles","h","key","opts","ks","processKeyToString","existing","handle","reason","agentId","n","target","oldest","ts","now","snap","idleFor","workspace","currentHash","e","fromHash","toHash","claudeMissingMessage","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","childEnv","buildAgentEnv","execFile","error","errMsg","renderFriendlyError","friendlyModelError","apiErrorInStdout","parsed","executeClaudeCodeStreaming","onDelta","onEvent","fullText","streamBilledModel","streamUsage","streamSessionId","streamApiError","streamTimeoutMs","proc","execa","lineBuffer","decoder","StringDecoder","chunk","lines","line","event","block","delta","resultText","result","tail","r","executeSdk","apiKey","sdk","query","content","q","message","executeOrchestrator","generate","providerName","fullTask","executeClaudeCodePersistent","registry","getProcessRegistry","channel","chatId","key","e","RegistryCapExceeded","finalText","finalError","sessionId","evt","blocks","executeTask","providers","persistent","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","resolve","existsSync","mkdirSync","fn","agentId","channel","chatId","key","inFlight","session","adapter","promise","seeds","existingIds","appended","s","day","safe","file","data","readFileSync","senderName","claudeSessionId","lines","currentBucket","msg","bucket","limit","todayStart","defaultAfter","after","before","safeAgentId","allFiles","readdirSync","f","afterDay","beforeDay","dayRe","candidateFiles","collected","totalScanned","queryLower","participantSet","a","b","page","oldestTs","hasMore","sinceISO","sinceDay","candidates","rows","seen","dedupped","r","k","currentKey","files","hints","recent","scope","memoryStore","needsCompaction","compactSession","applyCompaction","result","usage","beforeCount","totalChars","sum","removed","dropped","debug","writeFileSync","RateLimiter","maxPerMinute","maxPerHour","agentId","now","timestamps","hourAgo","recent","t","minuteAgo","inMinute","oldest","waitMs","opts","maxWaitMs","deadline","firstWait","r","remaining","sleepMs","resolve","readFileSync","writeFileSync","appendFileSync","existsSync","mkdirSync","resolve","CACHE_AWARE_PRICING","CONTEXT_TIER_THRESHOLD","TIER2_MULTIPLIER","getModelFamily","model","lower","splitTaskUsageByTier","u","SESSION_KEYS_MAX","PRICING_OVERRIDE_PATH","TokenTracker","baseDir","loadPricingWithOverrides","usage","pricingTable","family","p","tier2In","tier2Out","tier2CR","tier2CW","tier1","tier2","agentId","duration","realUsage","messageLength","responseLength","error","channel","sessionKey","daily","agent","i1","o1","cr1","cw1","i2","o2","cr2","cw2","chKey","ch","date","path","targetDate","totalTasks","totalInput","totalOutput","totalCacheRead","totalCacheCreate","totalCost","topAgent","topCost","agentCosts","agentSessionStats","byChannel","id","cost","sessions","chCost","report","filePath","file","fmt","n","fmtTok","agentBreakdown","a","b","stats","sessTag","channelBreakdown","row","content","dates","parsed","m","d","throughDate","opts","maxDays","appended","last","candidates","cursor","i","days","totalErrors","byAgent","agentInput","agentOutput","agentCR","agentCW","existing","acc","totalTokens","cacheHitRatio","sessionStats","result","raw","rates","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","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","existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","writeFileSync","resolve","TYPES","AgentMemory","opts","root","agentId","safe","slug","dir","type","name","args","path","now","createdAt","existing","safeRead","parsed","parseMemoryFile","record","serialize","out","entry","a","b","ta","tb","removed","workspacePath","memory","explicitPath","claudePath","start","end","block","startIdx","endIdx","next","before","after","entries","p","grouped","lines","group","titleCase","r","escapeYaml","raw","header","body","fm","line","colon","key","val","unescapeYaml","s","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","TRACE_STEP_SUMMARY_BYTES","clipForTrace","remaining","emitTraceStepsFromStreamEvent","taskId","event","t","at","bus","getEventBus","block","inputJson","content","b","makeStreamEventFormatter","textSeen","m","sid","out","name","input","c","flag","AgentRegistry","AgentMemory","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","resumeSessionId","reason","extractRotationMemo","result","chatKeyword","e","peers","agent","task","onDelta","startedAt","response","status","getDefaultLedger","peerEntry","p","timeoutMs","qChannel","qChatId","start","maxWaitMs","pollIntervalMs","r","mode","queued","pending","rateResult","waitMs","runningTask","output","pushToBuffer","chunk","sub","traceTaskId","newEventId","onEvent","formatter","formatted","senderName","taskStartedAt","intent","wikiContext","relevantMemories","memoryContext","relevantPatterns","patternContext","skillInjection","loadLocalSkills","getAutoInjectSkills","skills","lastTokens","turns","compactResult","quality","lost","d","sessionHistory","debug","msgs","first","last","fingerprint","referencesBlock","cacheKey","cached","refs","recipes","loadReferences","loadRecipes","rootRefs","rootRecipes","resolved","resolveRecipes","renderReferences","crossChatContext","longMemoryRecall","lmHint","detectLongMemoryHint","recall","lines","ordered","stamp","who","strategy","sessionHistoryOverride","planDebug","plannerSucceeded","planContext","plan","soulSessionKey","soulSwitch","detectSoulSwitch","activeSoul","bootstrapFiles","loadBootstrapFiles","bootstrapContextText","buildBootstrapContext","agentMemoryBlock","systemPromptAppend","contextInput","historyContext","buildAgentContext","agentxContextBytes","sizeParts","sizeKey","promptSizeKey","recordPromptSize","driftWarning","warnIfPromptGrowing","taskWithSystemPrompt","finalResponse","hasWarmSession","abort","preflightOverageGate","preflightQuotaGate","g","preflightResponse","recordClaudeCodeDispatch","warning","warnIfNearingCap","executeTask","split","splitTaskUsageByTier","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","f","tsStr","ts","offsetHours","loaded","agentDir","agentPath","days","outer","files","rec","raw","retentionDays","cutoff","removed","dayEntries","limit","_t","_r","safeId"]}
|