@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
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * docs/design/appendices/routine-data-acquisition.md §6.1.1 + docs/design/appendices/pre-pass-fan-out.md
5
5
  * — every routine dispatcher (morning_routine, today_refresh,
6
- * hourly_check, evening / weekly / monthly review) calls this runner
6
+ * activity_scan, evening / weekly / monthly review) calls this runner
7
7
  * immediately before dispatching the parent session. The runner:
8
8
  *
9
9
  * 1. Reads the per-routine plan from `ROUTINE_WINDOWS` and the current
@@ -27,7 +27,7 @@
27
27
  * 4. The caller grafts the block into the **parent** routine event's
28
28
  * `event.data.fetchReportBlock`. ContextBuilder injects it verbatim
29
29
  * into the parent session's prompt (mirrors the `<gate_decision>`
30
- * pattern used by hourly_check Stage 3).
30
+ * pattern used by activity_scan Stage 3).
31
31
  *
32
32
  * Failure-mode contract (docs/design/appendices/pre-pass-fan-out.md §5):
33
33
  *
@@ -57,6 +57,7 @@ import type { IAgentRouter } from "./backends/backend-router.js";
57
57
  import type { IAuditLogger, IContextBuilder } from "./dispatcher-types.js";
58
58
  import type { PromptAssembler } from "./dispatcher-prompt.js";
59
59
  import { type RoutineWindowKey } from "./routine-windows.js";
60
+ import type { AutonomousSpawnGate } from "./spawn-gates.js";
60
61
  /**
61
62
  * Structured form of the agent's single-line JSON output. Matches the
62
63
  * contract in `agent-profiles/routine-fetch-window.md`:
@@ -142,7 +143,7 @@ export interface RoutineFetchWindowResult {
142
143
  /**
143
144
  * HOURLY_CHECK_GATE_REDESIGN_PLAN.md §3.4 — caller options for `run()`.
144
145
  *
145
- * The hourly_check coordinator passes `integrationKeyFilter` to restrict
146
+ * The activity_scan coordinator passes `integrationKeyFilter` to restrict
146
147
  * the fan-out to the subset of integrations whose freshness window has
147
148
  * elapsed. Morning_routine / evening_review / weekly_review call
148
149
  * `run()` without options so they fetch every integration the routine
@@ -194,6 +195,14 @@ export interface RoutineFetchWindowRunnerDeps {
194
195
  getEventBroadcaster?: () => {
195
196
  broadcastEvent: (data: unknown) => void;
196
197
  } | null;
198
+ /**
199
+ * PREPASS_COST_REDUCTION_PLAN.md N2 — offline/auth spawn gate shared
200
+ * with the dispatcher. Evaluated per integration sub-session (the
201
+ * hourly `harvestForGate` path spawns the runner directly, bypassing
202
+ * the dispatcher's own gate). Optional: when undefined the runner
203
+ * spawns unconditionally, exactly as pre-N2.
204
+ */
205
+ spawnGate?: AutonomousSpawnGate;
197
206
  }
198
207
  /**
199
208
  * Compose the per-execute `allowedToolsOverride` for the pre-pass. The
@@ -340,6 +349,7 @@ export declare class RoutineFetchWindowRunner {
340
349
  private readonly prompt;
341
350
  private readonly getActiveMailAccounts;
342
351
  private readonly getEventBroadcaster;
352
+ private readonly spawnGate;
343
353
  constructor(deps: RoutineFetchWindowRunnerDeps);
344
354
  /**
345
355
  * Broadcast a single pre-pass progress event to the dashboard SSE
@@ -426,6 +436,32 @@ export declare class RoutineFetchWindowRunner {
426
436
  private budgetCapAttemptRecord;
427
437
  private didExhaustRetries;
428
438
  private backoffForAttempt;
439
+ /**
440
+ * PREPASS_COST_REDUCTION_PLAN.md N2 — evaluate the offline/auth spawn
441
+ * gate for one integration's sub-session. Candidates are the
442
+ * pre-resolved binding's main + fallback backends; when pre-resolve
443
+ * failed, the sub-plan's `requiredBackend` is the only candidate the
444
+ * attempt loop could use. Fail-open on every error path (returns null).
445
+ */
446
+ private evaluateSpawnGate;
447
+ /**
448
+ * Build the `skipped` SubReport for a spawn-gate skip and write its
449
+ * audit row. Mirrors the empty-attempts synthetic record (attempt 0,
450
+ * no SSE sub-session emits — no session was spawned). The audit row is
451
+ * `result='skipped'` with `detail.prePass.skipReason` carrying N2's
452
+ * `offline` / `auth_unhealthy`, matching the N3 plan-drop row shape so
453
+ * all pre-pass skip telemetry is queryable through one path.
454
+ */
455
+ private spawnGateSkippedSubReport;
456
+ /**
457
+ * PREPASS_COST_REDUCTION_PLAN.md N3 — one `skipped` audit row per
458
+ * (integration × reason) group of plan-assembly drops, with the
459
+ * dropped windows listed in `detail.prePass.windows`. Grouped at
460
+ * integration granularity because that is the unit a session would
461
+ * have been spawned for (and the unit the deferred R5 streak skip
462
+ * will key on). Observability only — no skip behavior changes here.
463
+ */
464
+ private logPlanAssemblyDrops;
429
465
  /**
430
466
  * Unified audit-row companion for every fan-out failure mode —
431
467
  * binding-resolve-failed, global-budget-cap, budget-cap (per-integration),
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * docs/design/appendices/routine-data-acquisition.md §6.1.1 + docs/design/appendices/pre-pass-fan-out.md
5
5
  * — every routine dispatcher (morning_routine, today_refresh,
6
- * hourly_check, evening / weekly / monthly review) calls this runner
6
+ * activity_scan, evening / weekly / monthly review) calls this runner
7
7
  * immediately before dispatching the parent session. The runner:
8
8
  *
9
9
  * 1. Reads the per-routine plan from `ROUTINE_WINDOWS` and the current
@@ -27,7 +27,7 @@
27
27
  * 4. The caller grafts the block into the **parent** routine event's
28
28
  * `event.data.fetchReportBlock`. ContextBuilder injects it verbatim
29
29
  * into the parent session's prompt (mirrors the `<gate_decision>`
30
- * pattern used by hourly_check Stage 3).
30
+ * pattern used by activity_scan Stage 3).
31
31
  *
32
32
  * Failure-mode contract (docs/design/appendices/pre-pass-fan-out.md §5):
33
33
  *
@@ -54,7 +54,9 @@ import { readIntegrations } from "../db/integrations-store.js";
54
54
  import { renderPartialForFanOut } from "./prompts.js";
55
55
  import { OBSERVATIONS_MCP_TOOL_NAME } from "../services/mcp/sdk-observations-server.js";
56
56
  import { ROUTINE_WINDOWS, routineHasWindows, WINDOW_QUERIES, } from "./routine-windows.js";
57
- import { buildAcquisitionTimestamps, splitAcquisitionPlanByIntegration, } from "./routine-acquisition-plan.js";
57
+ import { buildAcquisitionPlanAssembly, buildAcquisitionTimestamps, splitAcquisitionPlanByIntegration, } from "./routine-acquisition-plan.js";
58
+ import { extractBackendSpend } from "./agent-core.js";
59
+ import { BackendRouterHandledError } from "./backends/backend-router.js";
58
60
  import { RETRY_REASONS, buildPriorAttemptHintBlock, defaultRetryDecision, } from "./routine-fetch-window-retry.js";
59
61
  import { writeRuntimeState } from "../db/runtime-state.js";
60
62
  import { prePassLastRunRuntimeStateKey } from "./pre-pass-freshness.js";
@@ -118,7 +120,7 @@ const FETCH_WINDOW_INTEGRATION_PARTIAL_PLACEHOLDER = "{integration_partial}";
118
120
  function buildPrePassDaemonRestPatterns(apiPort, sessionBackend) {
119
121
  const root = `http://localhost:${apiPort}/api`;
120
122
  // Observations WRITE surface — the only write the pre-pass performs (GET
121
- // reads of pending observations live in the hourly-check session, never
123
+ // reads of pending observations live in the activity-scan session, never
122
124
  // here). For Claude the structured MCP tool
123
125
  // `mcp__aitne-observations__submit_observations` (added by
124
126
  // `composePrePassAllowedTools`) is the ONLY sanctioned write path, so we
@@ -774,6 +776,55 @@ async function runWithConcurrency(tasks, concurrency) {
774
776
  await Promise.all(workers);
775
777
  return results;
776
778
  }
779
+ function recoverRouterErrorSpend(error) {
780
+ const failures = [];
781
+ if (error instanceof BackendRouterHandledError) {
782
+ failures.push(error.mainFailure);
783
+ if (error.fallbackFailure && error.fallbackFailure !== error.mainFailure) {
784
+ failures.push(error.fallbackFailure);
785
+ }
786
+ if (error.cause && !failures.includes(error.cause)) {
787
+ failures.push(error.cause);
788
+ }
789
+ }
790
+ else {
791
+ failures.push(error);
792
+ }
793
+ let found = false;
794
+ let costUsd = 0;
795
+ let numTurns = 0;
796
+ let inputTokens = 0;
797
+ let outputTokens = 0;
798
+ let cacheCreationInputTokens = 0;
799
+ let cacheReadInputTokens = 0;
800
+ let costSource = null;
801
+ for (const failure of failures) {
802
+ const spend = extractBackendSpend(failure);
803
+ if (!spend)
804
+ continue;
805
+ found = true;
806
+ costUsd += spend.costUsd;
807
+ numTurns += spend.numTurns;
808
+ inputTokens += spend.usage.inputTokens;
809
+ outputTokens += spend.usage.outputTokens;
810
+ cacheCreationInputTokens += spend.usage.cacheCreationInputTokens;
811
+ cacheReadInputTokens += spend.usage.cacheReadInputTokens;
812
+ costSource = costSource ?? spend.costSource ?? null;
813
+ }
814
+ if (!found)
815
+ return null;
816
+ return {
817
+ costUsd,
818
+ numTurns,
819
+ costSource,
820
+ usage: {
821
+ inputTokens,
822
+ outputTokens,
823
+ cacheCreationInputTokens,
824
+ cacheReadInputTokens,
825
+ },
826
+ };
827
+ }
777
828
  // ── Runner ────────────────────────────────────────────────────────────────
778
829
  export class RoutineFetchWindowRunner {
779
830
  db;
@@ -784,6 +835,7 @@ export class RoutineFetchWindowRunner {
784
835
  prompt;
785
836
  getActiveMailAccounts;
786
837
  getEventBroadcaster;
838
+ spawnGate;
787
839
  constructor(deps) {
788
840
  this.db = deps.db;
789
841
  this.config = deps.config;
@@ -793,6 +845,7 @@ export class RoutineFetchWindowRunner {
793
845
  this.prompt = deps.prompt;
794
846
  this.getActiveMailAccounts = deps.getActiveMailAccounts;
795
847
  this.getEventBroadcaster = deps.getEventBroadcaster ?? null;
848
+ this.spawnGate = deps.spawnGate ?? null;
796
849
  }
797
850
  /**
798
851
  * Broadcast a single pre-pass progress event to the dashboard SSE
@@ -925,8 +978,13 @@ export class RoutineFetchWindowRunner {
925
978
  catch (err) {
926
979
  return this.fail(key, agentDay, parentEvent, "plan-assembly-failed", err);
927
980
  }
981
+ // PREPASS_COST_REDUCTION_PLAN.md N3 — surface plan-assembly drops as
982
+ // `skipped` audit rows before the empty-plan short-circuit below, so
983
+ // the all-cells-dropped case (the one the short-circuit hides) is
984
+ // recorded too. Observability only — no new skip behavior.
985
+ this.logPlanAssemblyDrops(parentEvent, key, planContext.drops);
928
986
  // HOURLY_CHECK_GATE_REDESIGN_PLAN.md §3.4 — when the caller is the
929
- // hourly_check coordinator, the freshness gate restricts pre-pass to
987
+ // activity_scan coordinator, the freshness gate restricts pre-pass to
930
988
  // a subset of integrations. `integrationKeyFilter` is honoured here
931
989
  // (before fan-out) so the runner only spawns sub-sessions for stale
932
990
  // integrations.
@@ -1010,7 +1068,7 @@ export class RoutineFetchWindowRunner {
1010
1068
  accounts,
1011
1069
  timestamps,
1012
1070
  };
1013
- const subPlans = splitAcquisitionPlanByIntegration(planInput);
1071
+ const { subPlans, drops } = buildAcquisitionPlanAssembly(planInput);
1014
1072
  // Observability: surface integrations whose `<fetch>` row will spawn
1015
1073
  // on a backend OTHER than the pre-pass default. Per-integration
1016
1074
  // backend routing (resolveIntegrationBackend +
@@ -1042,6 +1100,7 @@ export class RoutineFetchWindowRunner {
1042
1100
  subPlans,
1043
1101
  accounts,
1044
1102
  timestamps,
1103
+ drops,
1045
1104
  };
1046
1105
  }
1047
1106
  /**
@@ -1183,6 +1242,17 @@ export class RoutineFetchWindowRunner {
1183
1242
  return merged;
1184
1243
  }
1185
1244
  async runOneIntegrationWithRetry(input) {
1245
+ // PREPASS_COST_REDUCTION_PLAN.md N2 — offline/auth spawn gate, per
1246
+ // sub-session because each integration can route to a different
1247
+ // backend (`requiredBackend`). Skips only when EVERY candidate
1248
+ // backend is non-viable; the DNS verdict is cached (~60s) inside the
1249
+ // gate so an N-integration fan-out costs one lookup per host.
1250
+ // Freshness (`pre_pass_last_run:<key>`) is untouched by construction
1251
+ // — only `success` writes it — so the next tick retries.
1252
+ const gateDecision = await this.evaluateSpawnGate(input);
1253
+ if (gateDecision?.skip) {
1254
+ return this.spawnGateSkippedSubReport(input, gateDecision);
1255
+ }
1186
1256
  const attempts = [];
1187
1257
  // Per-integration budget cap is enforced at TWO complementary layers,
1188
1258
  // BOTH driven by `policy.perIntegrationBudgetUsd`:
@@ -1414,10 +1484,16 @@ export class RoutineFetchWindowRunner {
1414
1484
  record = this.attemptRecordFromResult(attempt, fetcherEvent, startedAt, result);
1415
1485
  }
1416
1486
  catch (err) {
1417
- input.globalBudget.commit(globalReservation, 0);
1418
- integrationBudget.commit(integrationReservation, 0);
1487
+ // PREPASS_COST_REDUCTION_PLAN.md N1 — the throw path can still
1488
+ // have billed the provider (post-hoc budget kill, partial stream
1489
+ // abort). Recover the spend the backend cores attached so the
1490
+ // budget guards account for real consumption and the attempt
1491
+ // record / audit row carry the cost instead of a silent 0.
1492
+ const failureSpend = recoverRouterErrorSpend(err);
1493
+ input.globalBudget.commit(globalReservation, failureSpend?.costUsd ?? 0);
1494
+ integrationBudget.commit(integrationReservation, failureSpend?.costUsd ?? 0);
1419
1495
  executeErr = err;
1420
- record = this.failedAttemptRecord(attempt, fetcherEvent.correlationId, startedAt, "agent-execute-failed", err);
1496
+ record = this.failedAttemptRecord(attempt, fetcherEvent.correlationId, startedAt, "agent-execute-failed", err, failureSpend);
1421
1497
  }
1422
1498
  attempts.push(record);
1423
1499
  const decision = retryOn(record, attempt, input.policy, attempts.slice(0, -1));
@@ -1439,6 +1515,7 @@ export class RoutineFetchWindowRunner {
1439
1515
  err: executeErr,
1440
1516
  binding: binding.main,
1441
1517
  startedAt,
1518
+ spend: recoverRouterErrorSpend(executeErr),
1442
1519
  });
1443
1520
  }
1444
1521
  this.emitSubSessionCompleted(input, fetcherEvent.correlationId, attempt, record, decision);
@@ -1470,7 +1547,7 @@ export class RoutineFetchWindowRunner {
1470
1547
  // timestamp on every successful integration completion. The
1471
1548
  // coordinator's `harvestForGate` reads this key to suppress
1472
1549
  // redundant pre-pass spawns within the configured freshness window
1473
- // (default 30 min). Shared across morning_routine / hourly_check /
1550
+ // (default 30 min). Shared across morning_routine / activity_scan /
1474
1551
  // evening_review / weekly_review / today_refresh by construction:
1475
1552
  // the runner has no notion of parent routine.
1476
1553
  //
@@ -1602,7 +1679,14 @@ export class RoutineFetchWindowRunner {
1602
1679
  numTurns: result.numTurns,
1603
1680
  };
1604
1681
  }
1605
- failedAttemptRecord(attempt, fetcherCorrelationId, startedAt, kind, err) {
1682
+ failedAttemptRecord(attempt, fetcherCorrelationId, startedAt, kind, err,
1683
+ /**
1684
+ * PREPASS_COST_REDUCTION_PLAN.md N1 — spend recovered from the
1685
+ * failure signal when the provider already billed the attempt.
1686
+ * Absent for pre-execute failures (binding/context), which are
1687
+ * genuinely zero-cost.
1688
+ */
1689
+ spend) {
1606
1690
  const message = err instanceof Error ? err.message : String(err);
1607
1691
  const endedAt = new Date().toISOString();
1608
1692
  return {
@@ -1615,8 +1699,8 @@ export class RoutineFetchWindowRunner {
1615
1699
  fetcherCorrelationId,
1616
1700
  startedAt,
1617
1701
  endedAt,
1618
- costUsd: 0,
1619
- numTurns: 0,
1702
+ costUsd: spend?.costUsd ?? 0,
1703
+ numTurns: spend?.numTurns ?? 0,
1620
1704
  };
1621
1705
  }
1622
1706
  budgetCapAttemptRecord(attempt, fetcherCorrelationId, startedAt, type, remaining) {
@@ -1663,6 +1747,156 @@ export class RoutineFetchWindowRunner {
1663
1747
  const last = policy.backoffMs[policy.backoffMs.length - 1];
1664
1748
  return typeof last === "number" ? Math.max(0, last) : 0;
1665
1749
  }
1750
+ /**
1751
+ * PREPASS_COST_REDUCTION_PLAN.md N2 — evaluate the offline/auth spawn
1752
+ * gate for one integration's sub-session. Candidates are the
1753
+ * pre-resolved binding's main + fallback backends; when pre-resolve
1754
+ * failed, the sub-plan's `requiredBackend` is the only candidate the
1755
+ * attempt loop could use. Fail-open on every error path (returns null).
1756
+ */
1757
+ async evaluateSpawnGate(input) {
1758
+ if (!this.spawnGate)
1759
+ return null;
1760
+ try {
1761
+ const binding = input.preResolvedBinding;
1762
+ const candidates = binding
1763
+ ? [binding.main.backendId]
1764
+ : [input.subPlan.requiredBackend];
1765
+ if (binding?.fallback
1766
+ && binding.fallback.backendId !== binding.main.backendId) {
1767
+ candidates.push(binding.fallback.backendId);
1768
+ }
1769
+ return await this.spawnGate.evaluate(candidates);
1770
+ }
1771
+ catch (err) {
1772
+ logger.warn({
1773
+ err,
1774
+ routine: input.key,
1775
+ integrationKey: input.subPlan.integrationKey,
1776
+ }, "Pre-pass spawn-gate evaluation failed — failing open");
1777
+ return null;
1778
+ }
1779
+ }
1780
+ /**
1781
+ * Build the `skipped` SubReport for a spawn-gate skip and write its
1782
+ * audit row. Mirrors the empty-attempts synthetic record (attempt 0,
1783
+ * no SSE sub-session emits — no session was spawned). The audit row is
1784
+ * `result='skipped'` with `detail.prePass.skipReason` carrying N2's
1785
+ * `offline` / `auth_unhealthy`, matching the N3 plan-drop row shape so
1786
+ * all pre-pass skip telemetry is queryable through one path.
1787
+ */
1788
+ spawnGateSkippedSubReport(input, decision) {
1789
+ const reason = decision.reason ?? "offline";
1790
+ const now = new Date().toISOString();
1791
+ const record = {
1792
+ attempt: 0,
1793
+ status: "skipped",
1794
+ fetched: 0,
1795
+ posted: 0,
1796
+ duplicates: 0,
1797
+ errors: [{ type: "spawn-gate-skipped", reason, attempt: 0 }],
1798
+ fetcherCorrelationId: input.parentEvent.correlationId,
1799
+ startedAt: now,
1800
+ endedAt: now,
1801
+ costUsd: 0,
1802
+ numTurns: 0,
1803
+ };
1804
+ try {
1805
+ const fetcherEvent = this.createFanOutFetcherEvent(input.parentEvent, input.key, input.subPlan, 0, input.policy.maxAttempts);
1806
+ this.audit.logSkip(fetcherEvent, reason, "autonomous", {
1807
+ prePass: {
1808
+ parentCorrelationId: input.parentEvent.correlationId,
1809
+ parentRoutine: input.key,
1810
+ integrationKey: input.subPlan.integrationKey,
1811
+ skipReason: reason,
1812
+ spawnGate: { backends: decision.backends },
1813
+ },
1814
+ });
1815
+ }
1816
+ catch (err) {
1817
+ logger.warn({
1818
+ err,
1819
+ routine: input.key,
1820
+ integrationKey: input.subPlan.integrationKey,
1821
+ reason,
1822
+ }, "Failed to log spawn-gate skip audit row");
1823
+ }
1824
+ logger.info({
1825
+ routine: input.key,
1826
+ integrationKey: input.subPlan.integrationKey,
1827
+ reason,
1828
+ backends: decision.backends,
1829
+ parentCorrelationId: input.parentEvent.correlationId,
1830
+ }, "Pre-pass sub-session skipped — spawn gate (offline / auth-unhealthy backends)");
1831
+ return {
1832
+ ...record,
1833
+ integrationKey: input.subPlan.integrationKey,
1834
+ attempts: [record],
1835
+ retriesExhausted: false,
1836
+ };
1837
+ }
1838
+ /**
1839
+ * PREPASS_COST_REDUCTION_PLAN.md N3 — one `skipped` audit row per
1840
+ * (integration × reason) group of plan-assembly drops, with the
1841
+ * dropped windows listed in `detail.prePass.windows`. Grouped at
1842
+ * integration granularity because that is the unit a session would
1843
+ * have been spawned for (and the unit the deferred R5 streak skip
1844
+ * will key on). Observability only — no skip behavior changes here.
1845
+ */
1846
+ logPlanAssemblyDrops(parentEvent, key, allDrops) {
1847
+ // `direct_inline_prefetch` is the catalog working as designed (the
1848
+ // daemon fetches that data inline; see the reason's doc comment) —
1849
+ // auditing it every run would bury the genuine drop signal R4/R5
1850
+ // need under deterministic noise.
1851
+ const drops = allDrops.filter((d) => d.reason !== "direct_inline_prefetch");
1852
+ if (drops.length === 0)
1853
+ return;
1854
+ try {
1855
+ const groups = new Map();
1856
+ for (const drop of drops) {
1857
+ const groupKey = `${drop.integration}|${drop.reason}`;
1858
+ const existing = groups.get(groupKey);
1859
+ if (existing) {
1860
+ existing.windows.push(drop.window);
1861
+ }
1862
+ else {
1863
+ groups.set(groupKey, {
1864
+ integration: drop.integration,
1865
+ reason: drop.reason,
1866
+ windows: [drop.window],
1867
+ });
1868
+ }
1869
+ }
1870
+ for (const group of groups.values()) {
1871
+ const dropEvent = {
1872
+ ...createEvent({
1873
+ type: FETCH_WINDOW_EVENT_TYPE,
1874
+ source: parentEvent.source,
1875
+ priority: EventPriority.NORMAL,
1876
+ correlationId: parentEvent.correlationId,
1877
+ }),
1878
+ routine: "fetch_window",
1879
+ };
1880
+ this.audit.logSkip(dropEvent, `plan_drop:${group.reason}`, "autonomous", {
1881
+ prePass: {
1882
+ parentCorrelationId: parentEvent.correlationId,
1883
+ parentRoutine: key,
1884
+ integrationKey: group.integration,
1885
+ skipReason: group.reason,
1886
+ windows: group.windows,
1887
+ },
1888
+ });
1889
+ }
1890
+ logger.debug({
1891
+ routine: key,
1892
+ parentCorrelationId: parentEvent.correlationId,
1893
+ drops,
1894
+ }, "Pre-pass plan-assembly drops recorded");
1895
+ }
1896
+ catch (err) {
1897
+ logger.warn({ err, routine: key, dropCount: drops.length }, "Failed to log pre-pass plan-assembly drop audit rows");
1898
+ }
1899
+ }
1666
1900
  /**
1667
1901
  * Unified audit-row companion for every fan-out failure mode —
1668
1902
  * binding-resolve-failed, global-budget-cap, budget-cap (per-integration),
@@ -1694,6 +1928,23 @@ export class RoutineFetchWindowRunner {
1694
1928
  ...(durationMs !== undefined ? { durationMs } : {}),
1695
1929
  ...(options.binding ? { backendId: options.binding.backendId } : {}),
1696
1930
  ...(options.binding ? { modelId: options.binding.modelId } : {}),
1931
+ ...(options.spend
1932
+ ? {
1933
+ costUsd: options.spend.costUsd,
1934
+ numTurns: options.spend.numTurns,
1935
+ ...(options.spend.costSource
1936
+ ? { costSource: options.spend.costSource }
1937
+ : {}),
1938
+ ...(options.spend.usage
1939
+ ? {
1940
+ tokensInput: options.spend.usage.inputTokens,
1941
+ tokensOutput: options.spend.usage.outputTokens,
1942
+ tokensCacheCreation: options.spend.usage.cacheCreationInputTokens,
1943
+ tokensCacheRead: options.spend.usage.cacheReadInputTokens,
1944
+ }
1945
+ : {}),
1946
+ }
1947
+ : {}),
1697
1948
  failureKind: options.failureKind,
1698
1949
  prePass: {
1699
1950
  parentCorrelationId: input.parentEvent.correlationId,
@@ -49,7 +49,7 @@ export type WindowSymbol = (typeof WINDOW_SYMBOLS)[number];
49
49
  * `routine.morning_routine`; the daemon-prepared `<roadmap_skeleton>`
50
50
  * block (not a window fetch) handles the first-run roadmap populate.
51
51
  */
52
- export declare const ROUTINE_WINDOW_KEYS: readonly ["routine.morning_routine", "routine.today_refresh", "routine.hourly_check", "routine.evening_review", "routine.weekly_review", "routine.monthly_review"];
52
+ export declare const ROUTINE_WINDOW_KEYS: readonly ["routine.morning_routine", "routine.today_refresh", "routine.activity_scan", "routine.evening_review", "routine.weekly_review", "routine.monthly_review"];
53
53
  export type RoutineWindowKey = (typeof ROUTINE_WINDOW_KEYS)[number];
54
54
  export interface RoutineWindowSpec {
55
55
  /** Fetch shape — drives partial selection (mail-acquire vs calendar-acquire vs notion-acquire). */
@@ -102,7 +102,7 @@ export interface RoutineWindowSpec {
102
102
  * differs). The dispatcher's `applySinceFilter` in
103
103
  * `db/observations.ts` ensures the main session reads back the
104
104
  * merged pending set uniformly.
105
- * - `hourly_check`: small, frequent. `unread_last_hour` typically
105
+ * - `activity_scan`: small, frequent. `unread_last_hour` typically
106
106
  * yields ≤1 mail item; `imminent_2h` ≤1 calendar item. Notion
107
107
  * `updated_1h` is similarly bounded.
108
108
  * - `evening_review`: ContextBuilder covers `<calendar_events_3d>`,
@@ -83,7 +83,7 @@ export const WINDOW_SYMBOLS = [
83
83
  export const ROUTINE_WINDOW_KEYS = [
84
84
  "routine.morning_routine",
85
85
  "routine.today_refresh",
86
- "routine.hourly_check",
86
+ "routine.activity_scan",
87
87
  "routine.evening_review",
88
88
  "routine.weekly_review",
89
89
  "routine.monthly_review",
@@ -112,7 +112,7 @@ export const ROUTINE_WINDOW_KEYS = [
112
112
  * differs). The dispatcher's `applySinceFilter` in
113
113
  * `db/observations.ts` ensures the main session reads back the
114
114
  * merged pending set uniformly.
115
- * - `hourly_check`: small, frequent. `unread_last_hour` typically
115
+ * - `activity_scan`: small, frequent. `unread_last_hour` typically
116
116
  * yields ≤1 mail item; `imminent_2h` ≤1 calendar item. Notion
117
117
  * `updated_1h` is similarly bounded.
118
118
  * - `evening_review`: ContextBuilder covers `<calendar_events_3d>`,
@@ -147,7 +147,7 @@ export const ROUTINE_WINDOWS = {
147
147
  "routine.today_refresh": [
148
148
  { kind: "calendar", window: "cal_next_24h_drift", perAccount: false },
149
149
  ],
150
- "routine.hourly_check": [
150
+ "routine.activity_scan": [
151
151
  { kind: "mail", window: "unread_last_hour", perAccount: true },
152
152
  { kind: "calendar", window: "imminent_2h", perAccount: false },
153
153
  { kind: "notion", window: "updated_1h", perAccount: false },
@@ -216,7 +216,7 @@ export const WINDOW_QUERIES = {
216
216
  // shell-fragile Unicode whitespace. Filtering at the FETCH boundary
217
217
  // means the agent never sees these messages at all: zero cost, zero
218
218
  // chance of a Unicode-whitespace-bearing curl body, zero observations
219
- // for the morning_routine / hourly_check skill to wade through.
219
+ // for the morning_routine / activity_scan skill to wade through.
220
220
  //
221
221
  // Sent-folder windows (today_outcomes) skip the filter — sent mail is
222
222
  // the user's own writing, not promotional.
@@ -334,6 +334,9 @@ export const WINDOW_QUERIES = {
334
334
  // (Outlook), so a pre-pass row would double-fetch. The dispatcher
335
335
  // skips rows whose `(symbol, integration, mode)` cell is undefined
336
336
  // (cf. `lookupQuery`); pre-pass only fires for delegated / native.
337
+ // Plan assembly classifies this pattern as `direct_inline_prefetch`
338
+ // (N3) — distinct from `no_window_query` (a genuine catalog hole) —
339
+ // and the runner excludes it from the drop-audit stream.
337
340
  cal_morning_7d: {
338
341
  google_calendar: {
339
342
  delegated: 'timeMin="{day_start_iso}" timeMax="{week_end_iso}" maxResults=100',
@@ -401,7 +404,7 @@ export function routineHasWindows(key) {
401
404
  return ROUTINE_WINDOWS[key].length > 0;
402
405
  }
403
406
  // Note: there is intentionally no `routineHasCalendarPrepass` helper
404
- // even though every morning_routine / hourly_check / today_refresh /
407
+ // even though every morning_routine / activity_scan / today_refresh /
405
408
  // weekly_review row carries a calendar entry. The decision a caller
406
409
  // (e.g. ContextBuilder.buildCalendarBlock) really wants to make is
407
410
  // "does the pre-pass own the window I'm about to emit," and that needs