@aitne/daemon 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/dist/adapters/adapter-watchdog.d.ts +70 -0
  2. package/dist/adapters/adapter-watchdog.js +115 -0
  3. package/dist/adapters/discord.d.ts +17 -1
  4. package/dist/adapters/discord.js +33 -0
  5. package/dist/adapters/notification-manager.d.ts +27 -1
  6. package/dist/adapters/notification-manager.js +54 -39
  7. package/dist/adapters/slack-adapter.d.ts +26 -1
  8. package/dist/adapters/slack-adapter.js +41 -0
  9. package/dist/adapters/telegram-adapter.d.ts +18 -1
  10. package/dist/adapters/telegram-adapter.js +41 -2
  11. package/dist/adapters/types.d.ts +20 -0
  12. package/dist/adapters/whatsapp-adapter.d.ts +26 -7
  13. package/dist/adapters/whatsapp-adapter.js +74 -21
  14. package/dist/api/env-writer.js +8 -5
  15. package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
  16. package/dist/api/helpers/agent-errors-registry.js +5 -5
  17. package/dist/api/routes/agent.js +33 -12
  18. package/dist/api/routes/agents/index.js +75 -16
  19. package/dist/api/routes/agents/views.d.ts +37 -2
  20. package/dist/api/routes/agents/views.js +64 -2
  21. package/dist/api/routes/background-task.d.ts +22 -0
  22. package/dist/api/routes/background-task.js +338 -0
  23. package/dist/api/routes/browser-history.js +9 -1
  24. package/dist/api/routes/context/permissions.js +3 -2
  25. package/dist/api/routes/context/snapshots.js +0 -3
  26. package/dist/api/routes/context/write.js +3 -17
  27. package/dist/api/routes/dashboard/config.js +48 -12
  28. package/dist/api/routes/dashboard/cost-approvals.js +66 -0
  29. package/dist/api/routes/dashboard/notifications.js +9 -9
  30. package/dist/api/routes/integrations/crud-patch.js +5 -1
  31. package/dist/api/routes/integrations-reconcile.js +2 -2
  32. package/dist/api/routes/notion.d.ts +1 -1
  33. package/dist/api/routes/observations.js +7 -7
  34. package/dist/api/routes/obsidian.d.ts +1 -1
  35. package/dist/api/routes/receipts.js +5 -1
  36. package/dist/api/routes/setup-migrate.js +1 -1
  37. package/dist/api/routes/setup.js +1 -1
  38. package/dist/api/routes/task-flows.d.ts +1 -1
  39. package/dist/api/routes/task-flows.js +1 -1
  40. package/dist/api/routes/tuning.d.ts +29 -0
  41. package/dist/api/routes/tuning.js +304 -0
  42. package/dist/api/server.d.ts +44 -16
  43. package/dist/api/server.js +9 -0
  44. package/dist/bootstrap/adapters.d.ts +19 -0
  45. package/dist/bootstrap/adapters.js +61 -0
  46. package/dist/bootstrap/api.d.ts +5 -3
  47. package/dist/bootstrap/api.js +45 -13
  48. package/dist/bootstrap/catchup.d.ts +1 -1
  49. package/dist/bootstrap/catchup.js +11 -11
  50. package/dist/bootstrap/event-pipeline.d.ts +11 -0
  51. package/dist/bootstrap/event-pipeline.js +245 -7
  52. package/dist/bootstrap/observers.js +9 -6
  53. package/dist/bootstrap/schedule-helpers.d.ts +104 -6
  54. package/dist/bootstrap/schedule-helpers.js +172 -19
  55. package/dist/config.js +26 -12
  56. package/dist/core/agent-core.d.ts +33 -1
  57. package/dist/core/agent-core.js +36 -1
  58. package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
  59. package/dist/core/agents/activity-scan-cadence.js +127 -0
  60. package/dist/core/agents/agent-route-override.d.ts +53 -0
  61. package/dist/core/agents/agent-route-override.js +69 -0
  62. package/dist/core/agents/builtin-registry.d.ts +51 -14
  63. package/dist/core/agents/builtin-registry.js +92 -15
  64. package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
  65. package/dist/core/agents/config-gate-reconcile.js +51 -0
  66. package/dist/core/agents/cron-substitute.d.ts +1 -1
  67. package/dist/core/agents/cron-substitute.js +1 -1
  68. package/dist/core/agents/custom-routine-migration.d.ts +60 -0
  69. package/dist/core/agents/custom-routine-migration.js +149 -0
  70. package/dist/core/agents/firing-blocked.d.ts +1 -1
  71. package/dist/core/agents/hourly-cadence.d.ts +102 -0
  72. package/dist/core/agents/hourly-cadence.js +126 -0
  73. package/dist/core/agents/loader-boot.js +23 -0
  74. package/dist/core/agents/loader.d.ts +19 -0
  75. package/dist/core/agents/loader.js +34 -2
  76. package/dist/core/agents/override-merge.d.ts +1 -1
  77. package/dist/core/agents/override-merge.js +9 -1
  78. package/dist/core/agents/recurrence-convert.d.ts +1 -1
  79. package/dist/core/agents/recurrence-convert.js +1 -1
  80. package/dist/core/agents/recurring-schedule-adapter.js +8 -0
  81. package/dist/core/alerts.js +6 -6
  82. package/dist/core/backends/auth-health-monitor.d.ts +2 -2
  83. package/dist/core/backends/auth-health-monitor.js +1 -1
  84. package/dist/core/backends/backend-router.d.ts +27 -1
  85. package/dist/core/backends/backend-router.js +165 -1
  86. package/dist/core/backends/claude-code-core.d.ts +71 -31
  87. package/dist/core/backends/claude-code-core.js +282 -54
  88. package/dist/core/backends/cli-quota-guards.d.ts +29 -1
  89. package/dist/core/backends/cli-quota-guards.js +40 -5
  90. package/dist/core/backends/codex-core.d.ts +6 -0
  91. package/dist/core/backends/codex-core.js +22 -6
  92. package/dist/core/backends/failure-spend.d.ts +58 -0
  93. package/dist/core/backends/failure-spend.js +137 -0
  94. package/dist/core/backends/gemini-cli-core.d.ts +6 -0
  95. package/dist/core/backends/gemini-cli-core.js +25 -6
  96. package/dist/core/backends/model-registry.d.ts +1 -1
  97. package/dist/core/backends/model-registry.js +4 -4
  98. package/dist/core/backends/opencode-core.d.ts +1 -1
  99. package/dist/core/backends/opencode-core.js +5 -5
  100. package/dist/core/backends/plan-presets.js +39 -15
  101. package/dist/core/bang-commands/commands-cost.js +3 -1
  102. package/dist/core/bang-commands/commands-report.js +4 -3
  103. package/dist/core/bang-commands/commands-research.js +4 -1
  104. package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
  105. package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
  106. package/dist/core/bang-commands/commands-stop-start.js +3 -3
  107. package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
  108. package/dist/core/bang-commands/commands-task-control.js +147 -0
  109. package/dist/core/bang-commands/commands-wiki.js +5 -5
  110. package/dist/core/bang-commands/index.d.ts +2 -0
  111. package/dist/core/bang-commands/index.js +12 -0
  112. package/dist/core/bang-commands/registry.d.ts +12 -0
  113. package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
  114. package/dist/core/browser-history/research-cluster-fanout.js +39 -16
  115. package/dist/core/channel-timeline.d.ts +5 -1
  116. package/dist/core/channel-timeline.js +13 -0
  117. package/dist/core/context/index-reconciler.js +5 -2
  118. package/dist/core/context/policy-index-reconciler.d.ts +6 -4
  119. package/dist/core/context/policy-index-runner.js +25 -6
  120. package/dist/core/context-builder-calendar.js +10 -2
  121. package/dist/core/context-builder-conversation.d.ts +8 -1
  122. package/dist/core/context-builder-conversation.js +41 -7
  123. package/dist/core/context-builder-yesterday.js +4 -3
  124. package/dist/core/context-builder.d.ts +7 -2
  125. package/dist/core/context-builder.js +62 -20
  126. package/dist/core/context-file-serializer.d.ts +1 -1
  127. package/dist/core/context-file-serializer.js +1 -1
  128. package/dist/core/context-health.js +2 -2
  129. package/dist/core/context-paths.d.ts +1 -1
  130. package/dist/core/context-paths.js +1 -1
  131. package/dist/core/context-validation/prepare-write.js +1 -1
  132. package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
  133. package/dist/core/context-vault-aliases.d.ts +0 -13
  134. package/dist/core/context-vault-aliases.js +37 -0
  135. package/dist/core/custom-routines.d.ts +99 -0
  136. package/dist/core/custom-routines.js +187 -0
  137. package/dist/core/daemon-api-cli.js +49 -0
  138. package/dist/core/day-boundary.d.ts +46 -0
  139. package/dist/core/day-boundary.js +40 -0
  140. package/dist/core/dispatcher-activity-scan.d.ts +221 -0
  141. package/dist/core/dispatcher-activity-scan.js +775 -0
  142. package/dist/core/dispatcher-error-handling.d.ts +6 -11
  143. package/dist/core/dispatcher-error-handling.js +38 -62
  144. package/dist/core/dispatcher-hourly-check.js +6 -1
  145. package/dist/core/dispatcher-message-handler.d.ts +10 -0
  146. package/dist/core/dispatcher-message-handler.js +17 -0
  147. package/dist/core/dispatcher-morning-routine.d.ts +6 -6
  148. package/dist/core/dispatcher-morning-routine.js +13 -13
  149. package/dist/core/dispatcher-result-processor.d.ts +33 -0
  150. package/dist/core/dispatcher-result-processor.js +167 -11
  151. package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
  152. package/dist/core/dispatcher-scheduled-background-task.js +89 -0
  153. package/dist/core/dispatcher-scheduled-tasks.d.ts +63 -1
  154. package/dist/core/dispatcher-scheduled-tasks.js +213 -6
  155. package/dist/core/dispatcher-task-delivery.d.ts +105 -0
  156. package/dist/core/dispatcher-task-delivery.js +555 -0
  157. package/dist/core/dispatcher-types.d.ts +48 -9
  158. package/dist/core/dispatcher-types.js +3 -3
  159. package/dist/core/dispatcher.d.ts +112 -31
  160. package/dist/core/dispatcher.js +284 -59
  161. package/dist/core/dm-freshness-metrics.d.ts +1 -1
  162. package/dist/core/drift-effects.js +2 -2
  163. package/dist/core/feedback/consolidation-prep.js +17 -5
  164. package/dist/core/feedback/eviction-scorer.js +6 -2
  165. package/dist/core/feedback/lesson-format.js +9 -4
  166. package/dist/core/feedback/lesson-injection.d.ts +1 -1
  167. package/dist/core/feedback/lesson-injection.js +17 -2
  168. package/dist/core/feedback/lesson-store-overview.d.ts +8 -4
  169. package/dist/core/feedback/lesson-store-overview.js +8 -4
  170. package/dist/core/feedback/regeneralization-prep.js +29 -16
  171. package/dist/core/feedback/self-performance-prep.d.ts +186 -0
  172. package/dist/core/feedback/self-performance-prep.js +541 -0
  173. package/dist/core/feedback/tuning-actuator.d.ts +198 -0
  174. package/dist/core/feedback/tuning-actuator.js +432 -0
  175. package/dist/core/feedback/tuning-recommender.d.ts +247 -0
  176. package/dist/core/feedback/tuning-recommender.js +580 -0
  177. package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
  178. package/dist/core/feedback/tuning-revert-monitor.js +213 -0
  179. package/dist/core/health-monitor.d.ts +6 -0
  180. package/dist/core/health-monitor.js +1 -1
  181. package/dist/core/injection-policy.d.ts +4 -4
  182. package/dist/core/injection-policy.js +4 -4
  183. package/dist/core/integration-main-backend.js +4 -0
  184. package/dist/core/management-md.d.ts +2 -2
  185. package/dist/core/management-md.js +51 -13
  186. package/dist/core/morning/orchestrator.d.ts +2 -2
  187. package/dist/core/morning/orchestrator.js +2 -2
  188. package/dist/core/notification-gate.d.ts +64 -0
  189. package/dist/core/notification-gate.js +51 -0
  190. package/dist/core/notification-rate-limit.d.ts +40 -0
  191. package/dist/core/notification-rate-limit.js +50 -0
  192. package/dist/core/policy-files.d.ts +1 -1
  193. package/dist/core/policy-files.js +2 -2
  194. package/dist/core/pre-pass-freshness.d.ts +4 -4
  195. package/dist/core/retention.d.ts +5 -0
  196. package/dist/core/retention.js +20 -4
  197. package/dist/core/review-context.d.ts +1 -1
  198. package/dist/core/review-context.js +10 -5
  199. package/dist/core/roadmap-write-lock.d.ts +2 -1
  200. package/dist/core/roadmap-write-lock.js +15 -10
  201. package/dist/core/routine-acquisition-plan.d.ts +47 -1
  202. package/dist/core/routine-acquisition-plan.js +78 -20
  203. package/dist/core/routine-fetch-window-retry.js +7 -4
  204. package/dist/core/routine-fetch-window-runner.d.ts +39 -3
  205. package/dist/core/routine-fetch-window-runner.js +264 -13
  206. package/dist/core/routine-windows.d.ts +2 -2
  207. package/dist/core/routine-windows.js +8 -5
  208. package/dist/core/scheduler.d.ts +175 -16
  209. package/dist/core/scheduler.js +559 -102
  210. package/dist/core/signal-detector.d.ts +12 -0
  211. package/dist/core/signal-detector.js +53 -9
  212. package/dist/core/skills-compiler-denied-tools.js +2 -2
  213. package/dist/core/skills-compiler-skill-index.d.ts +2 -2
  214. package/dist/core/skills-compiler-skill-index.js +2 -2
  215. package/dist/core/skills-compiler-variants.d.ts +1 -1
  216. package/dist/core/skills-compiler-variants.js +8 -0
  217. package/dist/core/skills-compiler.d.ts +29 -26
  218. package/dist/core/skills-compiler.js +117 -81
  219. package/dist/core/skills-manifest.d.ts +37 -0
  220. package/dist/core/skills-manifest.js +73 -2
  221. package/dist/core/sleep-inhibitor.d.ts +79 -0
  222. package/dist/core/sleep-inhibitor.js +132 -0
  223. package/dist/core/slim-system-prompt-loader.d.ts +77 -0
  224. package/dist/core/slim-system-prompt-loader.js +141 -0
  225. package/dist/core/spawn-gates.d.ts +126 -0
  226. package/dist/core/spawn-gates.js +180 -0
  227. package/dist/core/today-direct-writer.d.ts +2 -2
  228. package/dist/core/today-direct-writer.js +1 -1
  229. package/dist/core/today-write-lock.d.ts +4 -2
  230. package/dist/core/today-write-lock.js +30 -20
  231. package/dist/core/wake-detector.d.ts +55 -0
  232. package/dist/core/wake-detector.js +80 -0
  233. package/dist/core/wiki/compile-lock.d.ts +1 -1
  234. package/dist/core/wiki/compile-lock.js +1 -1
  235. package/dist/core/workdir.js +15 -6
  236. package/dist/db/activity-scan-signals.d.ts +77 -0
  237. package/dist/db/activity-scan-signals.js +378 -0
  238. package/dist/db/agents-store.d.ts +28 -0
  239. package/dist/db/agents-store.js +62 -0
  240. package/dist/db/background-task-clarifications-store.d.ts +81 -0
  241. package/dist/db/background-task-clarifications-store.js +152 -0
  242. package/dist/db/background-task-store.d.ts +207 -0
  243. package/dist/db/background-task-store.js +380 -0
  244. package/dist/db/browser-history-store.d.ts +39 -6
  245. package/dist/db/browser-history-store.js +51 -7
  246. package/dist/db/browser-task-clarifications-store.d.ts +12 -0
  247. package/dist/db/browser-task-clarifications-store.js +35 -5
  248. package/dist/db/browser-task-store.d.ts +3 -0
  249. package/dist/db/browser-task-store.js +29 -4
  250. package/dist/db/deferred-dm.d.ts +86 -0
  251. package/dist/db/deferred-dm.js +199 -0
  252. package/dist/db/migrations.js +330 -0
  253. package/dist/db/observations.d.ts +2 -2
  254. package/dist/db/observations.js +3 -3
  255. package/dist/db/schema.js +217 -16
  256. package/dist/db/voice-transcripts-store.d.ts +1 -1
  257. package/dist/index.js +86 -29
  258. package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
  259. package/dist/messaging/browser-task-mcp-notifier.js +30 -151
  260. package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
  261. package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
  262. package/dist/observers/delegated-sync-worker.d.ts +6 -6
  263. package/dist/observers/delegated-sync-worker.js +10 -10
  264. package/dist/observers/git-delegated-cron.d.ts +1 -1
  265. package/dist/observers/git-delegated-cron.js +2 -2
  266. package/dist/observers/github-poller-classifier.d.ts +3 -3
  267. package/dist/observers/github-poller-classifier.js +3 -3
  268. package/dist/observers/imminent-event-scheduler.d.ts +1 -1
  269. package/dist/observers/imminent-event-scheduler.js +1 -1
  270. package/dist/observers/mail-poller.d.ts +1 -0
  271. package/dist/observers/mail-poller.js +42 -3
  272. package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
  273. package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
  274. package/dist/observers/observation-summarizer/worker.d.ts +2 -2
  275. package/dist/observers/observation-summarizer/worker.js +4 -4
  276. package/dist/observers/obsidian-watcher.d.ts +1 -1
  277. package/dist/observers/obsidian-watcher.js +1 -1
  278. package/dist/safety/agent-write-tracker.d.ts +4 -4
  279. package/dist/safety/agent-write-tracker.js +4 -4
  280. package/dist/safety/audit.d.ts +43 -5
  281. package/dist/safety/audit.js +86 -18
  282. package/dist/safety/risk-classifier.d.ts +6 -0
  283. package/dist/safety/risk-classifier.js +75 -11
  284. package/dist/scheduler/activity-scan-gate.d.ts +86 -0
  285. package/dist/scheduler/activity-scan-gate.js +132 -0
  286. package/dist/services/background-task/background-task-budget.d.ts +80 -0
  287. package/dist/services/background-task/background-task-budget.js +91 -0
  288. package/dist/services/background-task/background-task-driver.d.ts +105 -0
  289. package/dist/services/background-task/background-task-driver.js +416 -0
  290. package/dist/services/background-task/background-task-runner.d.ts +96 -0
  291. package/dist/services/background-task/background-task-runner.js +673 -0
  292. package/dist/services/background-task/background-task-tools.d.ts +84 -0
  293. package/dist/services/background-task/background-task-tools.js +247 -0
  294. package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
  295. package/dist/services/background-task/background-task-transition-events.js +54 -0
  296. package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
  297. package/dist/services/browser-history/automation/egress-denylist.js +16 -6
  298. package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
  299. package/dist/services/browser-task/browser-task-runner.js +53 -8
  300. package/dist/services/observations-batch.d.ts +1 -1
  301. package/dist/services/observations-batch.js +2 -2
  302. package/dist/settings/runtime-settings.d.ts +38 -11
  303. package/dist/settings/runtime-settings.js +203 -40
  304. package/dist/settings/settings-store.js +11 -3
  305. package/package.json +4 -4
@@ -7,6 +7,7 @@ import { OBSERVATIONS_MCP_SERVER_NAME, createObservationsMcpServer, } from "../.
7
7
  import { parseMcpToolName } from "../../services/mcp/risk.js";
8
8
  import { logMcpToolCall, updateMcpToolCallResult } from "../../services/mcp/tool-audit.js";
9
9
  import { BackendQuotaError, BackendDecisiveFailure, } from "../agent-core.js";
10
+ import { PriceFetcher } from "./price-fetcher.js";
10
11
  import { flattenToolResultContent } from "../../services/delegated-tool-runtime.js";
11
12
  import { runDelegatedTool as runDelegatedToolFn, runDelegatedTask as runDelegatedTaskFn, } from "./claude-delegated.js";
12
13
  import { createSessionWorkdir, cleanupSessionWorkdir } from "../workdir.js";
@@ -15,7 +16,7 @@ import { buildDaemonApiCliEnv } from "../daemon-api-cli.js";
15
16
  import { createLogger } from "../../logging.js";
16
17
  import { DEFAULT_CLAUDE_HIGH_MODEL, DEFAULT_CLAUDE_MEDIUM_MODEL, findRegisteredModel, getModelsForBackend, } from "./model-registry.js";
17
18
  import { ALWAYS_DISALLOWED_TOOLS } from "../../safety/always-disallowed.js";
18
- import { loadFetchWindowSystemPrompt, resetFetchWindowSystemPromptForTest, } from "../fetch-window-prompt-loader.js";
19
+ import { loadFetchWindowSystemPrompt, loadSlimSystemPrompt, resetFetchWindowSystemPromptForTest, } from "../slim-system-prompt-loader.js";
19
20
  import { CliPathCache } from "./cli-utils.js";
20
21
  import { extractSilentApiErrors, logSilentApiErrors, } from "./silent-api-error-detector.js";
21
22
  import { CLAUDE_PROBE_TOOLS_PROMPT, computeDelegatedClaudeTools, computeNativeClaudeTools, describeClaudeProbeResultError, extractClaudeProbeTools, } from "./claude-probe.js";
@@ -74,35 +75,56 @@ const logger = createLogger("claude-code-core");
74
75
  */
75
76
  const CLAUDE_SDK_SETTING_SOURCES = ["user", "project"];
76
77
  /**
77
- * `routine.fetch_window` is a short, lite-tier pre-pass with high
78
- * per-session prompt-cache write cost (docs/design/appendices/fetch-window-cost-reduction.md
79
- * §1). The full `preset: "claude_code"` system prompt drags in ~30 K tokens
80
- * of built-in tool descriptions, the skills index, the memory-system docs,
81
- * and tone/style guidance none of which the fetcher uses. Phase 1
82
- * replaces the preset with a fully custom systemPrompt string for this
83
- * one process key (SDK 0.2.98 has no `presetOptions` granularity to drop
84
- * sub-sections of the preset, so a string prompt is the only lever).
78
+ * Slim, lite-tier process keys swap the verbose `preset: "claude_code"`
79
+ * system prompt (~30 K tokens of built-in tool descriptions, the skills
80
+ * index, the memory-system docs, and tone/style guidance the key never
81
+ * uses) for a tight custom systemPrompt string. SDK 0.2.98 has no
82
+ * `presetOptions` granularity to drop sub-sections of the preset, so a
83
+ * string prompt is the only lever. `buildSystemPrompt` resolves membership
84
+ * through the shared registry in `core/slim-system-prompt-loader.ts`
85
+ * (`loadSlimSystemPrompt`) the SAME loader the `SkillsCompiler` uses to
86
+ * write the byte-identical body into Codex / Gemini AGENTS.md / GEMINI.md,
87
+ * so adding a slim key is a one-line registry edit that wires both backends.
88
+ * Per-key agent profiles + task-flow bodies still ship the operational
89
+ * rules; the slim system prompt only sets the broad stance, and the SDK
90
+ * still loads the per-cwd CLAUDE.md the SkillsCompiler materializes.
91
+ * - `routine.fetch_window` — docs/design/appendices/fetch-window-cost-reduction.md
92
+ * Phase 1 / 1.5.
93
+ * - `routine.research_cluster_update` — RESEARCH_CLUSTER_COST_FIX_PLAN.md F4.
94
+ */
95
+ /**
96
+ * Slim keys whose Claude SDK session ALSO sheds the daemon user's `~/.claude`
97
+ * scope: `settingSources` drops to `["project"]` and `strictMcpConfig` is
98
+ * forced on. On a dev machine the `"user"` source pulls in the user's plugin
99
+ * SKILL.md tree (~178 files) + the ~25 K-token user-scope claude.ai MCP
100
+ * connector schemas (`mcp__claude_ai_*`) into EVERY session's prompt-cache
101
+ * prefix (RESEARCH_CLUSTER_COST_FIX_PLAN.md RC4). Dropping it is pure win
102
+ * for a key that reaches no integration through those connectors.
103
+ *
104
+ * This is a STRICT SUBSET of the slim-system-prompt keys, NOT the same set:
105
+ * a key only qualifies when it serves NO native-mode integration. In native
106
+ * integration mode the fetcher reaches Gmail / Calendar / Notion precisely
107
+ * through the user-scope claude.ai connectors, so `routine.fetch_window` keeps
108
+ * `["user", "project"]` and is deliberately ABSENT here even though it has a
109
+ * slim system prompt. `routine.research_cluster_update` only ever curls the
110
+ * daemon's own browser-history + context REST API (no claude.ai connector),
111
+ * so shedding the user scope cannot starve it.
85
112
  *
86
- * Phase 1.5 promotes the same template to a single source of truth across
87
- * backends: the loader lives in `core/fetch-window-prompt-loader.ts` so the
88
- * `SkillsCompiler` can write the same body verbatim into Codex / Gemini
89
- * instruction files (AGENTS.md / GEMINI.md) without a cross-backend
90
- * import. The agent profile (`agent-assets/agent-profiles/routine-fetch-window.md`)
91
- * and the task-flow body still ship their operational rules — the system
92
- * prompt only sets the broad stance, and the SDK still loads the per-cwd
93
- * CLAUDE.md the SkillsCompiler materializes per session.
113
+ * `strictMcpConfig` is defense-in-depth on top of the `settingSources` drop:
114
+ * it shuts out any settings-file-sourced MCP server, while the daemon's own
115
+ * servers (including the in-process `aitne-observations` server) are passed
116
+ * programmatically via `options.mcpServers` (`composeMcpServers`) which
117
+ * `strictMcpConfig` does not touch. Typed `ReadonlySet<ProcessKey>` so a
118
+ * key rename in @aitne/shared lights up at the literal below.
94
119
  */
95
- // Typed as `ProcessKey` (not the inferred string literal) so that if
96
- // `ProcessKey` is ever narrowed in @aitne/shared/process-key.ts — e.g. the
97
- // pre-pass is renamed — this declaration is the compile error, not a
98
- // silently-dead branch in `buildSystemPrompt`.
99
- const FETCH_WINDOW_PROCESS_KEY = "routine.fetch_window";
120
+ const USER_SCOPE_SHED_PROCESS_KEYS = new Set([
121
+ "routine.research_cluster_update",
122
+ ]);
100
123
  /**
101
- * Test-only surface: lets `claude-code-core.test.ts` exercise the
102
- * fetch_window prompt loader without reaching into module internals via
103
- * `as any` casts. Re-exports the shared loader (now hoisted to
104
- * `core/fetch-window-prompt-loader.ts`) so the existing test import path
105
- * keeps working after Phase 1.5's hoist.
124
+ * Test-only surface: lets `claude-code-core.test.ts` exercise the slim
125
+ * prompt loader without reaching into module internals via `as any` casts.
126
+ * Re-exports the shared loaders (hoisted to `core/slim-system-prompt-loader.ts`)
127
+ * so the existing fetch_window test import path keeps working.
106
128
  */
107
129
  export const _testInternals = {
108
130
  loadFetchWindowSystemPrompt,
@@ -129,9 +151,81 @@ export const _testInternals = {
129
151
  * question based on the CLI / SDK's own startup cost, not on
130
152
  * pattern-matching against one of the existing three.
131
153
  */
154
+ // ── Partial-spend recovery (PREPASS_COST_REDUCTION_PLAN.md N1) ────────────
155
+ //
156
+ // The SDK populates authoritative usage/cost only on the terminal `result`
157
+ // stream message. When the stream aborts before that message arrives —
158
+ // the SDK's `max_budget_usd` kill, a wall-clock timeout, a transport
159
+ // failure — the run's spend would otherwise be unrecoverable: the thrown
160
+ // error carries no usage, and the dispatcher's post-hoc audit writer
161
+ // (`recordPostHocBudgetSpend`) drops payload-less errors. The accumulator
162
+ // below sums per-assistant-message usage during `consumeStream` so a
163
+ // partial figure exists at throw time; `executeOnce` / `executeResumeOnce`
164
+ // stamp the snapshot onto the propagating error via a symbol property,
165
+ // and `classifyExecutionError` / `toBackendQuotaError` lift it onto the
166
+ // classified `BackendQuotaError` / `BackendDecisiveFailure`.
167
+ /** Carrier property for the partial-spend snapshot on a propagating error. */
168
+ const PARTIAL_SPEND_PROP = Symbol("aitne.claudePartialSpend");
169
+ function createPartialUsageAccumulator() {
170
+ return {
171
+ usage: {
172
+ inputTokens: 0,
173
+ outputTokens: 0,
174
+ cacheCreationInputTokens: 0,
175
+ cacheReadInputTokens: 0,
176
+ },
177
+ numTurns: 0,
178
+ };
179
+ }
180
+ /**
181
+ * Fold one SDK assistant message's API-call usage into the accumulator.
182
+ * The SDK reports usage per API call on each assistant message; summing
183
+ * them approximates the run's total the same way the terminal result
184
+ * message would have.
185
+ */
186
+ function recordAssistantUsage(acc, rawUsage) {
187
+ acc.numTurns += 1;
188
+ if (typeof rawUsage !== "object" || rawUsage === null)
189
+ return;
190
+ const u = rawUsage;
191
+ const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
192
+ acc.usage.inputTokens += num(u.input_tokens);
193
+ acc.usage.outputTokens += num(u.output_tokens);
194
+ acc.usage.cacheCreationInputTokens += num(u.cache_creation_input_tokens);
195
+ acc.usage.cacheReadInputTokens += num(u.cache_read_input_tokens);
196
+ }
197
+ function accumulatorSawUsage(acc) {
198
+ return (acc.usage.inputTokens > 0
199
+ || acc.usage.outputTokens > 0
200
+ || acc.usage.cacheCreationInputTokens > 0
201
+ || acc.usage.cacheReadInputTokens > 0);
202
+ }
203
+ function attachPartialSpend(error, spend) {
204
+ if (typeof error !== "object" || error === null)
205
+ return;
206
+ try {
207
+ Object.defineProperty(error, PARTIAL_SPEND_PROP, {
208
+ value: spend,
209
+ enumerable: false,
210
+ configurable: true,
211
+ });
212
+ }
213
+ catch {
214
+ // Frozen/sealed error object — losing the snapshot is acceptable;
215
+ // the row simply stays payload-less like before N1.
216
+ }
217
+ }
218
+ /** Visible for testing. */
219
+ export function getAttachedPartialSpend(error) {
220
+ if (typeof error !== "object" || error === null)
221
+ return null;
222
+ const value = error[PARTIAL_SPEND_PROP];
223
+ return value ? value : null;
224
+ }
132
225
  export class ClaudeCodeCore {
133
226
  config;
134
227
  writeTracker;
228
+ priceFetcher;
135
229
  backendId = "claude";
136
230
  static RETRY_DELAY_MS = 5 * 60 * 1000;
137
231
  static MAX_RETRIES = 1;
@@ -176,12 +270,26 @@ export class ClaudeCodeCore {
176
270
  * Shared AgentWriteTracker. When present, the Write/Edit PreToolUse hook
177
271
  * pre-marks vault-scoped writes so the ObsidianWatcher attributes the
178
272
  * resulting chokidar event to `actor='agent'` instead of `'user'`. Without
179
- * this wiring, the hourly_check dispatcher would re-discover the agent's
273
+ * this wiring, the activity_scan dispatcher would re-discover the agent's
180
274
  * own vault writes every cycle and loop.
181
275
  */
182
- writeTracker) {
276
+ writeTracker,
277
+ /**
278
+ * PREPASS_COST_REDUCTION_PLAN.md N1 — used only to estimate the
279
+ * dollar figure of a partial spend snapshot when the SDK stream
280
+ * terminates abnormally (budget abort, timeout, transport failure).
281
+ * Success-path cost still comes from the SDK's own metering.
282
+ * Defaulted like the CLI cores' fetchers so bootstrap stays
283
+ * unchanged; guarded on `dataDir` because several unit tests
284
+ * construct the core with a partial config — those fall back to
285
+ * cap-floor-only estimation in `stampPartialSpend`.
286
+ */
287
+ priceFetcher = config.dataDir
288
+ ? new PriceFetcher(config.dataDir)
289
+ : undefined) {
183
290
  this.config = config;
184
291
  this.writeTracker = writeTracker;
292
+ this.priceFetcher = priceFetcher;
185
293
  this.warnOnMissingCriticalTools();
186
294
  this.cliPathCache = new CliPathCache("claude");
187
295
  }
@@ -332,26 +440,27 @@ export class ClaudeCodeCore {
332
440
  };
333
441
  }
334
442
  buildSystemPrompt(processKey) {
335
- // docs/design/appendices/fetch-window-cost-reduction.md Phase 1 — `routine.fetch_window`
336
- // pays the full preset prompt cost on every fan-out (~30 K cache_create
337
- // tokens per session, see §1.2). The fetcher does not use Skill / Read /
338
- // Write / Edit / Glob / Grep / Task / WebFetch / WebSearch / NotebookEdit
339
- // / EnterPlanMode / ScheduleWakeup, the memory-system documentation, or
340
- // the skills index — every byte of preset for those is wasted cache
341
- // creation amortized over only ~3 turns. Replace the preset with a
342
- // small custom string for this one process key. Operational rules
343
- // (acquisition-plan iteration, observations POST contract, hard
344
- // guardrails) still ship via the per-cwd CLAUDE.md the SkillsCompiler
345
- // materializes from `agent-assets/agent-profiles/routine-fetch-window.md`
346
- // and the task-flow body in `agent-assets/task-flows/routine.fetch_window.md`.
443
+ // Slim process keys (RESEARCH_CLUSTER_COST_FIX_PLAN.md F4 generalizes the
444
+ // fetch-window-cost-reduction.md Phase 1 precedent) pay the full preset
445
+ // prompt cost on every dispatch (~30 K cache_create tokens per session).
446
+ // These keys never use Skill / Read / Write / Edit / Glob / Grep / Task /
447
+ // WebFetch / WebSearch / NotebookEdit / EnterPlanMode / ScheduleWakeup,
448
+ // the memory-system documentation, or the skills index — every byte of
449
+ // preset for those is wasted cache creation amortized over only a few
450
+ // turns. Replace the preset with a small custom string sourced from the
451
+ // shared registry (`core/slim-system-prompt-loader.ts`); the SkillsCompiler
452
+ // materializes the byte-identical body into AGENTS.md / GEMINI.md for CLI
453
+ // parity. Operational rules still ship via the per-cwd CLAUDE.md profile +
454
+ // task-flow body the SkillsCompiler materializes per session.
347
455
  //
348
456
  // Trade-off — `excludeDynamicSections` is a no-op when systemPrompt is
349
457
  // a string (per SDK 0.2.98 docs: "Has no effect when systemPrompt is a
350
458
  // string (custom prompt)"), but the entire string IS byte-stable
351
459
  // across sessions, so the prompt-cache prefix is naturally cacheable
352
460
  // on the same axis without needing the flag.
353
- if (processKey === FETCH_WINDOW_PROCESS_KEY) {
354
- return loadFetchWindowSystemPrompt();
461
+ const slimSystemPrompt = loadSlimSystemPrompt(processKey);
462
+ if (slimSystemPrompt !== null) {
463
+ return slimSystemPrompt;
355
464
  }
356
465
  // Character is NOT appended here — Phase 2 of the Character feature
357
466
  // (see docs/design/15-character.md §15.4.3) moved the injection into
@@ -383,6 +492,33 @@ export class ClaudeCodeCore {
383
492
  excludeDynamicSections: true,
384
493
  };
385
494
  }
495
+ /**
496
+ * Resolve the SDK `settingSources` for a session. Returns `["project"]`
497
+ * for `USER_SCOPE_SHED_PROCESS_KEYS` (dropping the daemon user's `~/.claude`
498
+ * scope — plugin SKILL.md tree + claude.ai connector schemas — from the
499
+ * prompt-cache prefix) and the default `["user", "project"]` otherwise. A
500
+ * fresh array per call: the SDK option type is mutable `SettingSource[]`.
501
+ *
502
+ * Applied at both `query()` sites (`executeOnce`, `executeResumeOnce`).
503
+ * Resume carries no `processKey` (it is always a reactive DM continuation,
504
+ * never a slim routine), so it always resolves to the full default.
505
+ */
506
+ resolveSettingSources(processKey) {
507
+ if (processKey !== undefined && USER_SCOPE_SHED_PROCESS_KEYS.has(processKey)) {
508
+ return ["project"];
509
+ }
510
+ return [...CLAUDE_SDK_SETTING_SOURCES];
511
+ }
512
+ /**
513
+ * Whether to force `strictMcpConfig` for a session — true exactly for the
514
+ * `USER_SCOPE_SHED_PROCESS_KEYS`, as defense-in-depth on top of the
515
+ * `settingSources` drop (shuts out settings-file-sourced MCP servers; the
516
+ * daemon's own servers are passed programmatically and unaffected). Resume
517
+ * carries no `processKey`, so it never qualifies.
518
+ */
519
+ resolveStrictMcpConfig(processKey) {
520
+ return processKey !== undefined && USER_SCOPE_SHED_PROCESS_KEYS.has(processKey);
521
+ }
386
522
  /**
387
523
  * Expand CLI-style aliases ("opus", "sonnet") to their current canonical
388
524
  * API IDs. Unrecognised strings pass through unchanged so custom or
@@ -452,6 +588,9 @@ export class ClaudeCodeCore {
452
588
  nativeToolCount: nativeTools.length,
453
589
  sessionDeniedToolCount: sessionDeniedTools.length,
454
590
  }, "Agent execute started");
591
+ // Declared outside the try so the catch can stamp a partial-spend
592
+ // snapshot onto the propagating error (PREPASS_COST_REDUCTION_PLAN.md N1).
593
+ const partialUsage = createPartialUsageAccumulator();
455
594
  try {
456
595
  const allowMode = this.config.claudeExecutionPermissionMode === "allow";
457
596
  // P22 §3.4 step 4 — when the dispatcher pins a per-execute
@@ -462,7 +601,7 @@ export class ClaudeCodeCore {
462
601
  // ALWAYS_DISALLOWED_TOOLS layer still applies.
463
602
  //
464
603
  // An EMPTY array (`[]`) is a deliberate "no tools" clamp — used by
465
- // `routine.hourly_check.triage` (JSON-only triage spawn) and Stage B
604
+ // `routine.activity_scan.triage` (JSON-only triage spawn) and Stage B
466
605
  // of the morning-routine pipeline (daily-journal-daemon-write.md §3
467
606
  // corollary). A pre-2026-05-24 version of this gate required
468
607
  // `length > 0`, which silently fell through to the default `dontAsk`
@@ -540,7 +679,14 @@ export class ClaudeCodeCore {
540
679
  const mcpServers = this.composeMcpServers(mcp.claudeMcpServers);
541
680
  return mcpServers ? { mcpServers } : {};
542
681
  })(),
543
- settingSources: [...CLAUDE_SDK_SETTING_SOURCES],
682
+ // RESEARCH_CLUSTER_COST_FIX_PLAN.md F4 — `USER_SCOPE_SHED_PROCESS_KEYS`
683
+ // drop to `["project"]` (+ `strictMcpConfig`) to shed the daemon
684
+ // user's `~/.claude` scope from the prompt-cache prefix; all other
685
+ // keys keep `["user", "project"]`.
686
+ settingSources: this.resolveSettingSources(params.processKey),
687
+ ...(this.resolveStrictMcpConfig(params.processKey)
688
+ ? { strictMcpConfig: true }
689
+ : {}),
544
690
  // When the per-execute clamp is active we already swapped Allow
545
691
  // mode back to strict `dontAsk` + an explicit allowedTools list.
546
692
  // The PreToolUse hooks must follow the same posture: keeping
@@ -553,11 +699,12 @@ export class ClaudeCodeCore {
553
699
  ...this.buildAdvisorSettings(),
554
700
  },
555
701
  });
556
- const result = await this.withTimeout(stream, () => this.consumeStream(stream, actualModelId, startMs, streamCallbacks, event.type), this.config.executeTimeoutMinutes);
702
+ const result = await this.withTimeout(stream, () => this.consumeStream(stream, actualModelId, startMs, streamCallbacks, event.type, partialUsage), this.config.executeTimeoutMinutes);
557
703
  logger.info({ eventType: event.type, model: actualModelId, durationMs: result.durationMs, costUsd: result.costUsd, numTurns: result.numTurns, isError: result.isError }, "Agent execute completed");
558
704
  return result;
559
705
  }
560
706
  catch (err) {
707
+ this.stampPartialSpend(err, partialUsage, actualModelId, startMs, maxBudgetUsd);
561
708
  logger.error({ err, eventType: event.type, model: actualModelId, durationMs: Date.now() - startMs }, "Agent execute failed");
562
709
  throw err;
563
710
  }
@@ -646,13 +793,24 @@ export class ClaudeCodeCore {
646
793
  const mcpServers = this.composeMcpServers(mcp.claudeMcpServers);
647
794
  return mcpServers ? { mcpServers } : {};
648
795
  })(),
649
- settingSources: [...CLAUDE_SDK_SETTING_SOURCES],
796
+ // Resume is always a reactive DM continuation (no `processKey`), so
797
+ // `resolveSettingSources()` returns the full `["user", "project"]`;
798
+ // routed through the helper for a single source of truth with the
799
+ // execute path (RESEARCH_CLUSTER_COST_FIX_PLAN.md F4).
800
+ settingSources: this.resolveSettingSources(),
650
801
  hooks: this.getSecurityHooks(allowMode),
651
802
  includePartialMessages: !!streamCallbacks,
652
803
  ...this.buildAdvisorSettings(),
653
804
  },
654
805
  });
655
- return await this.withTimeout(stream, () => this.consumeStream(stream, actualModelId, startMs, streamCallbacks, "message.received"), this.config.executeTimeoutMinutes);
806
+ const partialUsage = createPartialUsageAccumulator();
807
+ try {
808
+ return await this.withTimeout(stream, () => this.consumeStream(stream, actualModelId, startMs, streamCallbacks, "message.received", partialUsage), this.config.executeTimeoutMinutes);
809
+ }
810
+ catch (err) {
811
+ this.stampPartialSpend(err, partialUsage, actualModelId, startMs, maxBudgetUsd);
812
+ throw err;
813
+ }
656
814
  }
657
815
  async runWithRetry(fn, context) {
658
816
  let lastError;
@@ -700,6 +858,61 @@ export class ClaudeCodeCore {
700
858
  }
701
859
  return ClaudeCodeCore.NETWORK_ERROR_MESSAGE_PATTERN.test(this.getErrorMessage(error));
702
860
  }
861
+ /**
862
+ * PREPASS_COST_REDUCTION_PLAN.md N1 — build a spend snapshot from the
863
+ * stream's partial-usage accumulator and stamp it onto the propagating
864
+ * error so `classifyExecutionError` / `toBackendQuotaError` (which only
865
+ * see the error object) can lift it onto the classified failure.
866
+ *
867
+ * Dollar figure: estimated from the accumulated tokens via the shared
868
+ * price fetcher. For the SDK's `max_budget_usd` abort the figure is
869
+ * additionally floored at the cap — the SDK's own metering crossed it,
870
+ * so any lower estimate (e.g. usage observed only for the first few
871
+ * messages) would under-report what was actually billed. `costSource`
872
+ * is `"sdk_partial"` to mark the figure as a partial reconstruction.
873
+ *
874
+ * No-op when nothing recordable exists: no usage observed AND the
875
+ * error is not a budget abort with a known cap.
876
+ */
877
+ stampPartialSpend(error, acc, modelId, startMs, maxBudgetUsd) {
878
+ try {
879
+ if (error instanceof BackendQuotaError
880
+ || error instanceof BackendDecisiveFailure) {
881
+ // Already classified upstream (carries its own spend or lack of
882
+ // one) — re-stamping could only disagree with the classified
883
+ // payload.
884
+ return;
885
+ }
886
+ const isBudgetAbort = isClaudeCodeMaxBudgetError(error);
887
+ const sawUsage = accumulatorSawUsage(acc);
888
+ if (!sawUsage && !(isBudgetAbort && typeof maxBudgetUsd === "number")) {
889
+ return;
890
+ }
891
+ const estimated = sawUsage && this.priceFetcher
892
+ ? this.priceFetcher.estimateUsageCost({
893
+ backendId: this.backendId,
894
+ modelId,
895
+ usage: acc.usage,
896
+ fallbackModel: findRegisteredModel(this.backendId, modelId),
897
+ }).costUsd
898
+ : 0;
899
+ const costUsd = isBudgetAbort
900
+ ? Math.max(estimated, maxBudgetUsd ?? 0)
901
+ : estimated;
902
+ attachPartialSpend(error, {
903
+ usage: { ...acc.usage },
904
+ costUsd,
905
+ modelId,
906
+ numTurns: acc.numTurns,
907
+ durationMs: Date.now() - startMs,
908
+ costSource: "sdk_partial",
909
+ });
910
+ }
911
+ catch (stampErr) {
912
+ // Best-effort telemetry — never mask the original failure.
913
+ logger.warn({ err: stampErr, modelId }, "Failed to stamp partial spend onto Claude execution error");
914
+ }
915
+ }
703
916
  /** Visible for testing. */
704
917
  classifyExecutionError(error) {
705
918
  if (error instanceof BackendQuotaError ||
@@ -710,23 +923,28 @@ export class ClaudeCodeCore {
710
923
  if (quotaError) {
711
924
  return quotaError;
712
925
  }
926
+ const partialSpend = getAttachedPartialSpend(error);
713
927
  if (error instanceof AgentTimeoutError) {
714
- return new BackendDecisiveFailure(this.backendId, "timeout", error);
928
+ return new BackendDecisiveFailure(this.backendId, "timeout", error, partialSpend);
715
929
  }
716
930
  if (this.isAuthError(error)) {
717
- return new BackendDecisiveFailure(this.backendId, "auth", error);
931
+ return new BackendDecisiveFailure(this.backendId, "auth", error, partialSpend);
718
932
  }
719
- return new BackendDecisiveFailure(this.backendId, "other_non_retryable", error);
933
+ return new BackendDecisiveFailure(this.backendId, "other_non_retryable", error, partialSpend);
720
934
  }
721
935
  toBackendQuotaError(error) {
722
936
  if (isClaudeCodeMaxBudgetError(error)) {
723
- return new BackendQuotaError(this.backendId, "max_budget_usd", null, this.getErrorMessage(error));
937
+ // The partial-spend snapshot stamped by `stampPartialSpend` is the
938
+ // only usage figure that exists for a budget abort — the SDK kills
939
+ // the stream before the terminal `result` message
940
+ // (PREPASS_COST_REDUCTION_PLAN.md N1).
941
+ return new BackendQuotaError(this.backendId, "max_budget_usd", null, this.getErrorMessage(error), getAttachedPartialSpend(error));
724
942
  }
725
943
  if (!isClaudeCodeQuotaError(error)) {
726
944
  return null;
727
945
  }
728
946
  const hint = extractClaudeCodeQuotaResetHint(error);
729
- return new BackendQuotaError(this.backendId, this.getErrorCode(error) ?? "rate_limited", hint ? this.toBackendQuotaResetHint(hint) : null, this.getErrorMessage(error));
947
+ return new BackendQuotaError(this.backendId, this.getErrorCode(error) ?? "rate_limited", hint ? this.toBackendQuotaResetHint(hint) : null, this.getErrorMessage(error), getAttachedPartialSpend(error));
730
948
  }
731
949
  toBackendQuotaResetHint(hint) {
732
950
  return {
@@ -914,7 +1132,14 @@ export class ClaudeCodeCore {
914
1132
  const models = [...configuredModels, ...getModelsForBackend(this.backendId)];
915
1133
  return models.filter((model, index, list) => list.findIndex((candidate) => candidate.modelId === model.modelId) === index);
916
1134
  }
917
- async consumeStream(stream, model, startMs, streamCallbacks, eventType) {
1135
+ async consumeStream(stream, model, startMs, streamCallbacks, eventType,
1136
+ /**
1137
+ * PREPASS_COST_REDUCTION_PLAN.md N1 — live per-message usage sink the
1138
+ * caller keeps a reference to. When the stream throws before the
1139
+ * terminal `result` message, the caller stamps a spend snapshot built
1140
+ * from this accumulator onto the propagating error.
1141
+ */
1142
+ partialUsage) {
918
1143
  let output = "";
919
1144
  let streamedOutput = "";
920
1145
  let sessionId = null;
@@ -966,6 +1191,9 @@ export class ClaudeCodeCore {
966
1191
  // Track Bash tool_use blocks that hit the context API + server-side
967
1192
  // advisor tool invocations.
968
1193
  const assistantMsg = message;
1194
+ if (partialUsage) {
1195
+ recordAssistantUsage(partialUsage, assistantMsg.message?.usage);
1196
+ }
969
1197
  const blocks = assistantMsg.message?.content;
970
1198
  if (Array.isArray(blocks)) {
971
1199
  for (const block of blocks) {
@@ -25,7 +25,7 @@
25
25
  * message must update BOTH detectors.
26
26
  * Forcing those shapes through this module would be over-abstraction.
27
27
  */
28
- import type { BackendId } from "@aitne/shared";
28
+ import type { BackendId, BackendUsage } from "@aitne/shared";
29
29
  import { BackendDecisiveFailure, BackendQuotaError, type BackendQuotaSpend } from "../agent-core.js";
30
30
  import type { PriceFetcher } from "./price-fetcher.js";
31
31
  /**
@@ -66,6 +66,24 @@ export declare function assertPromptCostWithinMaxBudget(params: {
66
66
  modelId: string;
67
67
  priceFetcher: PriceFetcher;
68
68
  }): void;
69
+ /**
70
+ * PREPASS_COST_REDUCTION_PLAN.md N1 — recover a best-effort spend payload
71
+ * for a CLI run that failed after the provider already billed tokens.
72
+ * Returns `null` when the JSONL stream never surfaced usage (failure
73
+ * before the first `turn.completed` / stats event), so callers can pass
74
+ * the result straight to `classifyCliFailure` without an empty-usage
75
+ * guard. The dollar figure is a price-fetcher estimate from the observed
76
+ * token totals — same path the success branch uses — so a failed and a
77
+ * successful run with identical usage report identical cost.
78
+ */
79
+ export declare function recoverCliFailureSpend(params: {
80
+ backendId: BackendId;
81
+ priceFetcher: PriceFetcher;
82
+ usage: BackendUsage;
83
+ modelId: string;
84
+ numTurns: number;
85
+ durationMs: number;
86
+ }): BackendQuotaSpend | null;
69
87
  /**
70
88
  * Optional pre-auth classifier — given a failure `message`, returns a
71
89
  * `BackendDecisiveFailure` when it owns the message, else `null` to fall
@@ -97,4 +115,14 @@ export declare function classifyCliFailure(params: {
97
115
  rateLimitPattern: RegExp;
98
116
  authPattern: RegExp;
99
117
  extraClassifier?: CliFailureExtraClassifier;
118
+ /**
119
+ * PREPASS_COST_REDUCTION_PLAN.md N1 — best-effort spend recovered from
120
+ * the failed run's JSONL stream (usage totals + price-fetcher
121
+ * estimate). Attached to every error constructed here so the
122
+ * dispatcher's post-hoc audit writer can record what the provider
123
+ * already billed for a turn that produced no `AgentResult`. Errors
124
+ * returned by `extraClassifier` keep their own (usually absent) spend
125
+ * — the classifier owns the full construction of those.
126
+ */
127
+ spend?: BackendQuotaSpend | null;
100
128
  }): BackendQuotaError | BackendDecisiveFailure;
@@ -53,6 +53,40 @@ export function assertPromptCostWithinMaxBudget(params) {
53
53
  }
54
54
  throw new BackendQuotaError(backendId, "max_budget_usd", null, `${label} estimated prompt cost $${costUsd.toFixed(4)} exceeded the per-turn budget limit $${maxBudgetUsd.toFixed(2)} for ${modelId}.`);
55
55
  }
56
+ /**
57
+ * PREPASS_COST_REDUCTION_PLAN.md N1 — recover a best-effort spend payload
58
+ * for a CLI run that failed after the provider already billed tokens.
59
+ * Returns `null` when the JSONL stream never surfaced usage (failure
60
+ * before the first `turn.completed` / stats event), so callers can pass
61
+ * the result straight to `classifyCliFailure` without an empty-usage
62
+ * guard. The dollar figure is a price-fetcher estimate from the observed
63
+ * token totals — same path the success branch uses — so a failed and a
64
+ * successful run with identical usage report identical cost.
65
+ */
66
+ export function recoverCliFailureSpend(params) {
67
+ const { backendId, priceFetcher, usage, modelId, numTurns, durationMs } = params;
68
+ const sawUsage = usage.inputTokens > 0
69
+ || usage.outputTokens > 0
70
+ || usage.cacheCreationInputTokens > 0
71
+ || usage.cacheReadInputTokens > 0;
72
+ if (!sawUsage) {
73
+ return null;
74
+ }
75
+ const { costUsd, costSource } = priceFetcher.estimateUsageCost({
76
+ backendId,
77
+ modelId,
78
+ usage,
79
+ fallbackModel: findRegisteredModel(backendId, modelId),
80
+ });
81
+ return {
82
+ usage,
83
+ costUsd,
84
+ modelId,
85
+ numTurns: numTurns || 1,
86
+ durationMs,
87
+ costSource,
88
+ };
89
+ }
56
90
  /**
57
91
  * Shared failure-classification skeleton for the CLI backends. Maps a raw
58
92
  * failure `message` to the dispatcher's failover signals in a fixed order:
@@ -72,24 +106,25 @@ export function assertPromptCostWithinMaxBudget(params) {
72
106
  */
73
107
  export function classifyCliFailure(params) {
74
108
  const { backendId, message, rateLimitPattern, authPattern, extraClassifier } = params;
109
+ const spend = params.spend ?? null;
75
110
  if (isMaxBudgetMessage(message)) {
76
- return new BackendQuotaError(backendId, "max_budget_usd", null, message);
111
+ return new BackendQuotaError(backendId, "max_budget_usd", null, message, spend);
77
112
  }
78
113
  if (rateLimitPattern.test(message)) {
79
114
  // Best-effort reset-time extraction so the dashboard can surface
80
115
  // "quota resets at HH:MM (TZ)" instead of a bare "rate_limited" tag.
81
116
  // Falls through to null when no reset-time pattern matches.
82
- return new BackendQuotaError(backendId, "rate_limited", extractGenericQuotaResetHint(message), message);
117
+ return new BackendQuotaError(backendId, "rate_limited", extractGenericQuotaResetHint(message), message, spend);
83
118
  }
84
119
  const extra = extraClassifier?.(message, backendId);
85
120
  if (extra) {
86
121
  return extra;
87
122
  }
88
123
  if (authPattern.test(message)) {
89
- return new BackendDecisiveFailure(backendId, "auth", new Error(message));
124
+ return new BackendDecisiveFailure(backendId, "auth", new Error(message), spend);
90
125
  }
91
126
  if (/timed out|timeout/i.test(message)) {
92
- return new BackendDecisiveFailure(backendId, "timeout", new Error(message));
127
+ return new BackendDecisiveFailure(backendId, "timeout", new Error(message), spend);
93
128
  }
94
- return new BackendDecisiveFailure(backendId, "other_non_retryable", new Error(message));
129
+ return new BackendDecisiveFailure(backendId, "other_non_retryable", new Error(message), spend);
95
130
  }
@@ -63,6 +63,12 @@ export declare class CodexCore implements IAgentCore {
63
63
  private buildArgs;
64
64
  private pickSummaryModel;
65
65
  private classifyFailure;
66
+ /**
67
+ * PREPASS_COST_REDUCTION_PLAN.md N1 — spend recovered from the failed
68
+ * run's JSONL usage so terminal errors carry what the provider already
69
+ * billed. Null when the stream never reported usage.
70
+ */
71
+ private recoverFailureSpend;
66
72
  private assertWithinMaxBudget;
67
73
  private assertPromptWithinMaxBudget;
68
74
  /**