@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
@@ -113,18 +113,13 @@ export declare class DispatcherErrorRouter {
113
113
  */
114
114
  notifyDashboardError(event: Event, message: string): void;
115
115
  /**
116
- * Write a `result='failed'` agent_actions row carrying the actual spend
117
- * for a turn the backend completed before the daemon's per-turn budget
118
- * cap rejected it. Codex / Gemini enforce `max_budget_usd` post-hoc, so
119
- * the provider has already billed by the time we land here. Without
120
- * this row the dashboard's cost telemetry under-reports real spend by
121
- * the size of every budget-rejected turn (in this repo it was a $2.26
122
- * morning_routine that left the daily total at $0).
123
- *
124
- * Best-effort: a logging failure must not mask the original quota error
125
- * — we catch and warn instead of rethrowing.
116
+ * The distinct per-backend failures hiding behind a thrown error.
117
+ * `BackendRouterHandledError` carries up to two (main + fallback
118
+ * both may have billed before failing); any other error is its own
119
+ * single entry. Identity-deduped because `cause` usually aliases one
120
+ * of `mainFailure` / `fallbackFailure`.
126
121
  */
127
- private recordPostHocBudgetSpend;
122
+ private collectSpendFailures;
128
123
  extractQuotaError(error: unknown): BackendQuotaError | null;
129
124
  formatQuotaMessage(quotaError: BackendQuotaError): string;
130
125
  formatBackendLabel(backendId: BackendQuotaError["backendId"]): string;
@@ -41,6 +41,7 @@
41
41
  import { isMessageEvent, isScheduledEvent } from "@aitne/shared";
42
42
  import { BackendDecisiveFailure, BackendQuotaError, } from "./agent-core.js";
43
43
  import { BackendRouterHandledError } from "./backends/backend-router.js";
44
+ import { extractFailureSpendInfo, recordFailureSpendRow, } from "./backends/failure-spend.js";
44
45
  import { consultDelegatedConnectorHealth, markSignoutWarned, renderSignoutDm, } from "./delegated-connector-health.js";
45
46
  import { compareLocalDateParts, getLocalDateParts, localDateTimeToUtcMs, } from "./dispatcher-date-utils.js";
46
47
  import { createLogger } from "../logging.js";
@@ -137,16 +138,27 @@ export class DispatcherErrorRouter {
137
138
  }
138
139
  async handleError(event, error) {
139
140
  logger.error({ event: event.type, error: error.message }, "Event processing error");
140
- // Cost visibility for post-hoc budget rejections Codex / Gemini CLI
141
- // run a turn to completion before the daemon's per-turn cap can
142
- // reject. The user has already been billed by the provider, so write
143
- // a `result='failed'` agent_actions row carrying the actual usage so
144
- // the dashboard's cost dials reflect reality. Without this the
145
- // success-only audit path (`logAction` in ResultProcessor) silently
146
- // drops budget-rejected spend.
147
- const quotaForSpend = this.extractQuotaError(error);
148
- if (quotaForSpend?.spend) {
149
- this.recordPostHocBudgetSpend(event, quotaForSpend, error.message);
141
+ // Cost visibility for failed turns that already billed the provider
142
+ // post-hoc budget rejections (Codex / Gemini run the turn to
143
+ // completion before the cap can reject; Claude's SDK aborts mid-
144
+ // stream with a partial-usage snapshot stamped by the core) and
145
+ // non-quota terminal errors that carried usage (auth-mid-run,
146
+ // timeout, transport failure). Write a `result='failed'`
147
+ // agent_actions row per distinct backend failure so the dashboard's
148
+ // cost dials reflect reality. Without this the success-only audit
149
+ // path (`logAction` in ResultProcessor) silently drops the spend.
150
+ // PREPASS_COST_REDUCTION_PLAN.md N1.
151
+ //
152
+ // `BackendRouterHandledError` is unwrapped into its per-backend
153
+ // failures (main + fallback can BOTH have billed); a raw failover
154
+ // signal is recorded as-is. Pre-N1 this looked only at the top-level
155
+ // error, so a no-fallback quota kill — which the router wraps —
156
+ // recorded nothing.
157
+ for (const failure of this.collectSpendFailures(error)) {
158
+ const spendInfo = extractFailureSpendInfo(failure);
159
+ if (spendInfo) {
160
+ recordFailureSpendRow(this.db, event, spendInfo, error.message);
161
+ }
150
162
  }
151
163
  // Defense-in-depth cleanup of the notify-dedup marker — processResult
152
164
  // is the primary collection point, but if execution threw before
@@ -208,60 +220,24 @@ export class DispatcherErrorRouter {
208
220
  this.getDashboardStream()?.sendError?.(event.channel, message);
209
221
  }
210
222
  /**
211
- * Write a `result='failed'` agent_actions row carrying the actual spend
212
- * for a turn the backend completed before the daemon's per-turn budget
213
- * cap rejected it. Codex / Gemini enforce `max_budget_usd` post-hoc, so
214
- * the provider has already billed by the time we land here. Without
215
- * this row the dashboard's cost telemetry under-reports real spend by
216
- * the size of every budget-rejected turn (in this repo it was a $2.26
217
- * morning_routine that left the daily total at $0).
218
- *
219
- * Best-effort: a logging failure must not mask the original quota error
220
- * — we catch and warn instead of rethrowing.
223
+ * The distinct per-backend failures hiding behind a thrown error.
224
+ * `BackendRouterHandledError` carries up to two (main + fallback
225
+ * both may have billed before failing); any other error is its own
226
+ * single entry. Identity-deduped because `cause` usually aliases one
227
+ * of `mainFailure` / `fallbackFailure`.
221
228
  */
222
- recordPostHocBudgetSpend(event, quotaError, errorMessage) {
223
- const spend = quotaError.spend;
224
- if (!spend)
225
- return;
226
- try {
227
- const columns = [
228
- "event_id",
229
- "action_type",
230
- "model_used",
231
- "cost_usd",
232
- "tokens_input",
233
- "tokens_output",
234
- "duration_ms",
235
- "num_turns",
236
- "result",
237
- "backend",
238
- "cost_source",
239
- "error",
240
- "completed_at",
241
- ];
242
- const placeholders = columns.map(() => "?").join(", ");
243
- const values = [
244
- event.correlationId,
245
- event.type,
246
- spend.modelId,
247
- spend.costUsd,
248
- spend.usage.inputTokens,
249
- spend.usage.outputTokens,
250
- spend.durationMs,
251
- spend.numTurns,
252
- "failed",
253
- quotaError.backendId,
254
- spend.costSource ?? null,
255
- errorMessage.slice(0, 4096),
256
- new Date().toISOString(),
257
- ];
258
- this.db
259
- .prepare(`INSERT INTO agent_actions (${columns.join(", ")}) VALUES (${placeholders})`)
260
- .run(...values);
261
- }
262
- catch (err) {
263
- logger.warn({ err, eventType: event.type, backendId: quotaError.backendId }, "Failed to record post-hoc budget spend in agent_actions");
229
+ collectSpendFailures(error) {
230
+ if (error instanceof BackendRouterHandledError) {
231
+ const failures = [error.mainFailure];
232
+ if (error.fallbackFailure && error.fallbackFailure !== error.mainFailure) {
233
+ failures.push(error.fallbackFailure);
234
+ }
235
+ if (error.cause && !failures.includes(error.cause)) {
236
+ failures.push(error.cause);
237
+ }
238
+ return failures;
264
239
  }
240
+ return [error];
265
241
  }
266
242
  extractQuotaError(error) {
267
243
  if (error instanceof BackendQuotaError) {
@@ -62,6 +62,8 @@ import { parseStage2Verdict } from "./dispatcher-types.js";
62
62
  import { morningRoutineRanToday } from "../bootstrap/schedule-helpers.js";
63
63
  import { prePassLastRunRuntimeStateKey } from "./pre-pass-freshness.js";
64
64
  import { readRuntimeState } from "../db/runtime-state.js";
65
+ import { getRuntimeWindow } from "../db/agents-store.js";
66
+ import { resolveHourlyCadence } from "./agents/hourly-cadence.js";
65
67
  import { createLogger } from "../logging.js";
66
68
  const logger = createLogger("dispatcher-hourly-check");
67
69
  export class HourlyCheckCoordinator {
@@ -99,7 +101,10 @@ export class HourlyCheckCoordinator {
99
101
  }
100
102
  async trigger(source, options = {}) {
101
103
  const forced = options.force === true;
102
- const minObservations = this.config.hourlyCheckMinObservations;
104
+ // Observation threshold comes from the hourly-check agent row's
105
+ // runtime_window, with the legacy `hourlyCheckMinObservations` config key
106
+ // as fallback (AGENTS_HUB_REDESIGN_PLAN.md §2).
107
+ const minObservations = resolveHourlyCadence(getRuntimeWindow(this.db, "hourly-check"), this.config).minObservations;
103
108
  // C1 fix: atomic check-and-set on hourlyCheckInProgress BEFORE any await
104
109
  // boundary. Previously `await this.isMorningRoutineActive()` yielded to
105
110
  // the microtask queue, allowing cron + /api/agent/run-now arriving in
@@ -110,6 +110,14 @@ export interface MessageHandlerDeps {
110
110
  * either kind on the affected channel.
111
111
  */
112
112
  getFinalConfirmHandler?: () => import("../services/browser-history/automation/final-confirm-handler.js").FinalConfirmHandler | null;
113
+ /**
114
+ * BACKGROUND_TASK_RUNNER_DESIGN.md Phase 4 — lazily-injected runner
115
+ * accessors for the per-task `!stop <id>` bang command. Null in tests
116
+ * and before the runners are wired at startup; the `!stop` handler tells
117
+ * the owner the runner is unavailable rather than dropping the request.
118
+ */
119
+ getBackgroundTaskRunner?: () => import("../services/background-task/background-task-runner.js").BackgroundTaskRunner | null;
120
+ getBrowserTaskRunner?: () => import("../services/browser-task/browser-task-runner.js").BrowserTaskRunner | null;
113
121
  /** Live getter for the dispatcher's `currentSetupMode` flag. */
114
122
  getCurrentSetupMode: () => SetupMode | null;
115
123
  /** Forward into the dispatcher's `beginSetupMode` so persistence stays
@@ -147,6 +155,8 @@ export declare class MessageHandler {
147
155
  private readonly getBangCommandRegistry;
148
156
  private readonly getPurchaseHandler;
149
157
  private readonly getFinalConfirmHandler;
158
+ private readonly getBackgroundTaskRunner;
159
+ private readonly getBrowserTaskRunner;
150
160
  private readonly getCurrentSetupMode;
151
161
  private readonly beginSetupMode;
152
162
  private readonly lookupCustomBangCommandForEvent;
@@ -81,6 +81,8 @@ export class MessageHandler {
81
81
  getBangCommandRegistry;
82
82
  getPurchaseHandler;
83
83
  getFinalConfirmHandler;
84
+ getBackgroundTaskRunner;
85
+ getBrowserTaskRunner;
84
86
  getCurrentSetupMode;
85
87
  beginSetupMode;
86
88
  lookupCustomBangCommandForEvent;
@@ -116,6 +118,10 @@ export class MessageHandler {
116
118
  this.getPurchaseHandler = deps.getPurchaseHandler ?? (() => null);
117
119
  this.getFinalConfirmHandler =
118
120
  deps.getFinalConfirmHandler ?? (() => null);
121
+ this.getBackgroundTaskRunner =
122
+ deps.getBackgroundTaskRunner ?? (() => null);
123
+ this.getBrowserTaskRunner =
124
+ deps.getBrowserTaskRunner ?? (() => null);
119
125
  this.getCurrentSetupMode = deps.getCurrentSetupMode;
120
126
  this.beginSetupMode = deps.beginSetupMode;
121
127
  this.lookupCustomBangCommandForEvent = deps.lookupCustomBangCommandForEvent;
@@ -492,6 +498,17 @@ export class MessageHandler {
492
498
  enqueueBrowserResearchEvent: async (researchEvent) => {
493
499
  await this.eventBus.put(researchEvent);
494
500
  },
501
+ // Per-task `!stop <id>` — abort one detached worker. Resolved
502
+ // lazily from the live runner instances (which hold the in-memory
503
+ // handles the abort must reach); null until the runners are wired.
504
+ cancelBackgroundTask: async (taskId, reason) => {
505
+ const runner = this.getBackgroundTaskRunner();
506
+ return runner ? runner.cancel(taskId, reason) : false;
507
+ },
508
+ cancelBrowserTask: async (taskId, reason) => {
509
+ const runner = this.getBrowserTaskRunner();
510
+ return runner ? runner.cancel(taskId, reason) : false;
511
+ },
495
512
  enqueueWikiApproval: async (approvalInput) => {
496
513
  // WIKI_BUILDER_DESIGN.md §5.5 / §P2.E — escalate to Approve tier
497
514
  // via the existing agent_schedule approval queue. The dashboard
@@ -26,7 +26,7 @@
26
26
  * and bridges into a handful of dispatcher / coordinator methods that
27
27
  * are still owned elsewhere (`rotateDayFiles`,
28
28
  * `diagnoseTodayMdState`, `isRoadmapStale`, `emitRoadmapRefresh`,
29
- * `triggerHourlyCheck`).
29
+ * `triggerActivityScan`).
30
30
  *
31
31
  * Dispatcher entry points served:
32
32
  * - `EventDispatcher.dispatch` routes `routine === "morning_routine"`
@@ -38,7 +38,7 @@
38
38
  * Shared-state references held:
39
39
  * - `setMorningRoutineInProgress` — setter callback. The flag is
40
40
  * flipped to true at the start of the execute → executeWithRetry
41
- * wrapper and reset in `finally` so the hourly check can resume.
41
+ * wrapper and reset in `finally` so the activity scan can resume.
42
42
  */
43
43
  import type Database from "better-sqlite3";
44
44
  import type { Event } from "@aitne/shared";
@@ -104,10 +104,10 @@ export interface MorningRoutineRunnerDeps {
104
104
  /** Bridges into the dispatcher's roadmap-refresh emit. */
105
105
  emitRoadmapRefresh: (source: string) => void;
106
106
  /**
107
- * Bridges into `EventDispatcher.triggerHourlyCheck` so the deferred
108
- * post-morning hourly_check can fire from `emitPostMorningCatchups`.
107
+ * Bridges into `EventDispatcher.triggerActivityScan` so the deferred
108
+ * post-morning activity_scan can fire from `emitPostMorningCatchups`.
109
109
  */
110
- triggerHourlyCheck: (source: string) => Promise<unknown>;
110
+ triggerActivityScan: (source: string) => Promise<unknown>;
111
111
  /**
112
112
  * morning-routine-optimization.md Phase 5/6/7 — the split-stage
113
113
  * pipeline orchestrator owns Stage A (today.md) + Stage B (daily
@@ -131,7 +131,7 @@ export declare class MorningRoutineRunner {
131
131
  private readonly diagnoseTodayMdState;
132
132
  private readonly isRoadmapStale;
133
133
  private readonly emitRoadmapRefresh;
134
- private readonly triggerHourlyCheck;
134
+ private readonly triggerActivityScan;
135
135
  private readonly pipelineOrchestrator;
136
136
  constructor(deps: MorningRoutineRunnerDeps);
137
137
  /**
@@ -26,7 +26,7 @@
26
26
  * and bridges into a handful of dispatcher / coordinator methods that
27
27
  * are still owned elsewhere (`rotateDayFiles`,
28
28
  * `diagnoseTodayMdState`, `isRoadmapStale`, `emitRoadmapRefresh`,
29
- * `triggerHourlyCheck`).
29
+ * `triggerActivityScan`).
30
30
  *
31
31
  * Dispatcher entry points served:
32
32
  * - `EventDispatcher.dispatch` routes `routine === "morning_routine"`
@@ -38,7 +38,7 @@
38
38
  * Shared-state references held:
39
39
  * - `setMorningRoutineInProgress` — setter callback. The flag is
40
40
  * flipped to true at the start of the execute → executeWithRetry
41
- * wrapper and reset in `finally` so the hourly check can resume.
41
+ * wrapper and reset in `finally` so the activity scan can resume.
42
42
  */
43
43
  import { EventPriority, createEvent, formatSqliteDatetime, } from "@aitne/shared";
44
44
  import { flushPendingTodayRefresh } from "./drift-effects.js";
@@ -68,7 +68,7 @@ export class MorningRoutineRunner {
68
68
  diagnoseTodayMdState;
69
69
  isRoadmapStale;
70
70
  emitRoadmapRefresh;
71
- triggerHourlyCheck;
71
+ triggerActivityScan;
72
72
  pipelineOrchestrator;
73
73
  constructor(deps) {
74
74
  this.db = deps.db;
@@ -82,7 +82,7 @@ export class MorningRoutineRunner {
82
82
  this.diagnoseTodayMdState = deps.diagnoseTodayMdState;
83
83
  this.isRoadmapStale = deps.isRoadmapStale;
84
84
  this.emitRoadmapRefresh = deps.emitRoadmapRefresh;
85
- this.triggerHourlyCheck = deps.triggerHourlyCheck;
85
+ this.triggerActivityScan = deps.triggerActivityScan;
86
86
  this.pipelineOrchestrator = deps.pipelineOrchestrator;
87
87
  }
88
88
  /**
@@ -141,15 +141,15 @@ export class MorningRoutineRunner {
141
141
  logger.info({ promptKey, roadmapStale: roadmapStaleBeforeMorning, isRetry, retryCount, requestedTier: requestedTier ?? "default" }, "Morning routine dispatch");
142
142
  // B2 fix + docs/design/appendices/routine-data-acquisition.md Phase 4 / D2 race fix:
143
143
  // flip `morningRoutineInProgress=true` BEFORE the pre-pass fires so
144
- // hourly_check can't squeeze through the cold-start window
145
- // (`hourlyCheck.trigger` skips when `isMorningRoutineActive()`
144
+ // activity_scan can't squeeze through the cold-start window
145
+ // (`activityScan.trigger` skips when `isMorningRoutineActive()`
146
146
  // returns true). The original B2 fix already widened the flag to
147
147
  // span the whole retry chain; the pre-pass shifts the boundary
148
148
  // earlier so the same guard covers context build + binding resolve
149
149
  // + Haiku fetcher cold-start, all of which precede the executor.
150
150
  // The flag is reset in `finally` regardless of whether the pre-pass,
151
151
  // context build, or main session throws — so a partial-failure path
152
- // cannot leave the flag stuck `true` and starve hourly_check forever.
152
+ // cannot leave the flag stuck `true` and starve activity_scan forever.
153
153
  this.setMorningRoutineInProgress(true);
154
154
  // `pipelineRun` is non-null when the orchestrator completed and
155
155
  // Stage A produced an `AgentResult`. A null value means Stage A
@@ -214,7 +214,7 @@ export class MorningRoutineRunner {
214
214
  // `routine.morning_routine` success audit row is durable opens a
215
215
  // window where, for a cron-triggered (non-wake) run, both
216
216
  // `isMorningRoutineActive()` and `morningRoutineRanToday()` read
217
- // false — an hourly_check tick would then take the
217
+ // false — an activity_scan tick would then take the
218
218
  // `morning_routine_pending_for_today` branch and enqueue a
219
219
  // spurious morning_routine wake. The flag is instead cleared in
220
220
  // the post-finally try/finally below, AFTER the journal append +
@@ -368,7 +368,7 @@ export class MorningRoutineRunner {
368
368
  // audit row is durable (journal append + emitParentAuditRow above).
369
369
  // Reached on every post-finally exit path — success/catchup,
370
370
  // retry-scheduled, and the `pipelineRun === null` branch — so a
371
- // Stage-A failure cannot leak the flag and wedge hourly_check.
371
+ // Stage-A failure cannot leak the flag and wedge activity_scan.
372
372
  this.setMorningRoutineInProgress(false);
373
373
  }
374
374
  }
@@ -387,9 +387,9 @@ export class MorningRoutineRunner {
387
387
  routine,
388
388
  });
389
389
  }
390
- if (event.data?.postCatchupHourlyCheck === true) {
391
- logger.info("Triggering deferred hourly_check after morning catchup");
392
- await this.triggerHourlyCheck("post_morning_catchup");
390
+ if (event.data?.postCatchupActivityScan === true) {
391
+ logger.info("Triggering deferred activity_scan after morning catchup");
392
+ await this.triggerActivityScan("post_morning_catchup");
393
393
  }
394
394
  }
395
395
  /**
@@ -456,7 +456,7 @@ export class MorningRoutineRunner {
456
456
  postCatchupRoutines: Array.isArray(event.data?.postCatchupRoutines)
457
457
  ? event.data.postCatchupRoutines
458
458
  : [],
459
- postCatchupHourlyCheck: event.data?.postCatchupHourlyCheck === true,
459
+ postCatchupActivityScan: event.data?.postCatchupActivityScan === true,
460
460
  importance: "low",
461
461
  });
462
462
  // M1: dedup + INSERT in a single transaction so two concurrent
@@ -41,6 +41,7 @@
41
41
  import type Database from "better-sqlite3";
42
42
  import type { Event, MessageEvent, AgentResult } from "@aitne/shared";
43
43
  import type { AgentConfig } from "../config.js";
44
+ import { type ProactiveForwardType } from "./channel-timeline.js";
44
45
  import type { IAuditLogger, INotificationManager, ISessionManager } from "./dispatcher-types.js";
45
46
  import type { AgentExecutionOutcome } from "./agents/agent-execution-tracker.js";
46
47
  /**
@@ -161,6 +162,34 @@ export declare class ResultProcessor {
161
162
  * contextUpdated observability log in processResult.
162
163
  */
163
164
  isObserverEvent(event: Event): boolean;
165
+ /**
166
+ * RESEARCH_CLUSTER_COST_FIX_PLAN F5 — was the cluster journal actually
167
+ * written during this `routine.research_cluster_update` run?
168
+ *
169
+ * Detection is the on-disk journal file itself — the authoritative,
170
+ * path-specific, backend-agnostic signal. The daemon writes the journal
171
+ * via the context-API chokepoint (`writeFileAtomically`), so the file's
172
+ * mtime reflects the real write time; a write that landed inside the run
173
+ * window (`[min(event enqueue, now − durationMs) − buffer, now]`) proves
174
+ * the agent executed the append rather than narrating success.
175
+ *
176
+ * Why not the `context_write` audit row (the plan's first idea): that
177
+ * row is emitted only inside `notifyPromptContextChanged`, which fires
178
+ * solely for prompt-refreshing paths (`shouldRefreshPromptContext`).
179
+ * `research/*` is intentionally a *quiet* namespace — it never triggers
180
+ * a refresh — so a research write records **no** `context_write` row.
181
+ * (The plan's §1 "zero context_write rows ⇒ zero writes" inference was
182
+ * therefore unsound for this namespace; the on-disk check has none of
183
+ * that coupling.) F1 guarantees ≤ 1 cluster_update run/cluster/day, so
184
+ * the run window cannot collide with another run writing the same file;
185
+ * the sibling research flows write *different* files
186
+ * (`<slug>-assistance-<date>.md`, `<slug>-wiki.md`), never `<slug>.md`.
187
+ *
188
+ * Returns `true` (treat as written — do NOT downgrade) when the slug is
189
+ * absent/invalid or the stat fails for any reason other than ENOENT: a
190
+ * verification fault must never relabel a genuinely-successful run.
191
+ */
192
+ private researchClusterJournalWritten;
164
193
  /**
165
194
  * Confirm that a `wiki.ingest_url` session actually wrote a raw note via
166
195
  * the Wiki API before letting the agent's claimed success DM reach the
@@ -185,3 +214,7 @@ export declare class ResultProcessor {
185
214
  */
186
215
  private verifyWikiIngestWriteOrRewrite;
187
216
  }
217
+ export declare function readTaskDeliveryRecord(event: Event): {
218
+ notificationType: ProactiveForwardType;
219
+ metadata: Record<string, unknown>;
220
+ } | null;