@aitne/daemon 0.1.9 → 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 (333) 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.d.ts +1 -0
  15. package/dist/api/env-writer.js +17 -7
  16. package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
  17. package/dist/api/helpers/agent-errors-registry.js +5 -5
  18. package/dist/api/routes/agent-schedule.js +5 -1
  19. package/dist/api/routes/agent.js +33 -12
  20. package/dist/api/routes/agents/index.js +75 -16
  21. package/dist/api/routes/agents/views.d.ts +37 -2
  22. package/dist/api/routes/agents/views.js +64 -2
  23. package/dist/api/routes/apple-calendar.js +4 -1
  24. package/dist/api/routes/background-task.d.ts +22 -0
  25. package/dist/api/routes/background-task.js +338 -0
  26. package/dist/api/routes/browser-history.js +9 -1
  27. package/dist/api/routes/calendar.js +12 -2
  28. package/dist/api/routes/context/path-resolve.js +6 -1
  29. package/dist/api/routes/context/permissions.js +12 -2
  30. package/dist/api/routes/context/snapshots.js +0 -3
  31. package/dist/api/routes/context/write.js +3 -17
  32. package/dist/api/routes/dashboard/config.js +58 -12
  33. package/dist/api/routes/dashboard/cost-approvals.js +66 -0
  34. package/dist/api/routes/dashboard/notifications.js +9 -9
  35. package/dist/api/routes/dashboard/oauth-google.js +5 -3
  36. package/dist/api/routes/feedback.d.ts +3 -0
  37. package/dist/api/routes/feedback.js +349 -0
  38. package/dist/api/routes/git.js +10 -3
  39. package/dist/api/routes/github.js +5 -1
  40. package/dist/api/routes/integrations/crud-patch.js +5 -1
  41. package/dist/api/routes/integrations-reconcile.js +2 -2
  42. package/dist/api/routes/mcp.js +65 -13
  43. package/dist/api/routes/notion.d.ts +1 -1
  44. package/dist/api/routes/observations.js +7 -7
  45. package/dist/api/routes/obsidian.d.ts +1 -1
  46. package/dist/api/routes/receipts.js +5 -1
  47. package/dist/api/routes/setup-migrate.js +1 -1
  48. package/dist/api/routes/setup.js +1 -1
  49. package/dist/api/routes/task-flows.d.ts +1 -1
  50. package/dist/api/routes/task-flows.js +1 -1
  51. package/dist/api/routes/tuning.d.ts +29 -0
  52. package/dist/api/routes/tuning.js +304 -0
  53. package/dist/api/server.d.ts +44 -16
  54. package/dist/api/server.js +12 -0
  55. package/dist/bootstrap/adapters.d.ts +19 -0
  56. package/dist/bootstrap/adapters.js +61 -0
  57. package/dist/bootstrap/api.d.ts +5 -3
  58. package/dist/bootstrap/api.js +45 -13
  59. package/dist/bootstrap/catchup.d.ts +1 -1
  60. package/dist/bootstrap/catchup.js +11 -11
  61. package/dist/bootstrap/event-pipeline.d.ts +11 -0
  62. package/dist/bootstrap/event-pipeline.js +246 -8
  63. package/dist/bootstrap/observers.js +9 -6
  64. package/dist/bootstrap/schedule-helpers.d.ts +104 -6
  65. package/dist/bootstrap/schedule-helpers.js +172 -19
  66. package/dist/config.js +32 -12
  67. package/dist/core/agent-core.d.ts +33 -1
  68. package/dist/core/agent-core.js +36 -1
  69. package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
  70. package/dist/core/agents/activity-scan-cadence.js +127 -0
  71. package/dist/core/agents/agent-route-override.d.ts +53 -0
  72. package/dist/core/agents/agent-route-override.js +69 -0
  73. package/dist/core/agents/builtin-registry.d.ts +51 -14
  74. package/dist/core/agents/builtin-registry.js +92 -15
  75. package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
  76. package/dist/core/agents/config-gate-reconcile.js +51 -0
  77. package/dist/core/agents/cron-substitute.d.ts +1 -1
  78. package/dist/core/agents/cron-substitute.js +1 -1
  79. package/dist/core/agents/custom-routine-migration.d.ts +60 -0
  80. package/dist/core/agents/custom-routine-migration.js +149 -0
  81. package/dist/core/agents/firing-blocked.d.ts +1 -1
  82. package/dist/core/agents/hourly-cadence.d.ts +102 -0
  83. package/dist/core/agents/hourly-cadence.js +126 -0
  84. package/dist/core/agents/loader-boot.js +23 -0
  85. package/dist/core/agents/loader.d.ts +19 -0
  86. package/dist/core/agents/loader.js +34 -2
  87. package/dist/core/agents/override-merge.d.ts +1 -1
  88. package/dist/core/agents/override-merge.js +9 -1
  89. package/dist/core/agents/recurrence-convert.d.ts +1 -1
  90. package/dist/core/agents/recurrence-convert.js +1 -1
  91. package/dist/core/agents/recurring-schedule-adapter.js +8 -0
  92. package/dist/core/alerts.js +6 -6
  93. package/dist/core/backends/auth-health-monitor.d.ts +2 -2
  94. package/dist/core/backends/auth-health-monitor.js +1 -1
  95. package/dist/core/backends/backend-router.d.ts +27 -1
  96. package/dist/core/backends/backend-router.js +165 -1
  97. package/dist/core/backends/claude-code-core.d.ts +71 -31
  98. package/dist/core/backends/claude-code-core.js +282 -54
  99. package/dist/core/backends/cli-quota-guards.d.ts +29 -1
  100. package/dist/core/backends/cli-quota-guards.js +40 -5
  101. package/dist/core/backends/codex-core.d.ts +6 -0
  102. package/dist/core/backends/codex-core.js +22 -6
  103. package/dist/core/backends/failure-spend.d.ts +58 -0
  104. package/dist/core/backends/failure-spend.js +137 -0
  105. package/dist/core/backends/gemini-cli-core.d.ts +6 -0
  106. package/dist/core/backends/gemini-cli-core.js +38 -6
  107. package/dist/core/backends/model-registry.d.ts +1 -1
  108. package/dist/core/backends/model-registry.js +4 -4
  109. package/dist/core/backends/opencode-core.d.ts +1 -1
  110. package/dist/core/backends/opencode-core.js +5 -5
  111. package/dist/core/backends/plan-presets.js +47 -18
  112. package/dist/core/bang-commands/commands-cost.js +3 -1
  113. package/dist/core/bang-commands/commands-report.js +4 -3
  114. package/dist/core/bang-commands/commands-research.js +4 -1
  115. package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
  116. package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
  117. package/dist/core/bang-commands/commands-stop-start.js +3 -3
  118. package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
  119. package/dist/core/bang-commands/commands-task-control.js +147 -0
  120. package/dist/core/bang-commands/commands-wiki.js +5 -5
  121. package/dist/core/bang-commands/index.d.ts +2 -0
  122. package/dist/core/bang-commands/index.js +12 -0
  123. package/dist/core/bang-commands/registry.d.ts +12 -0
  124. package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
  125. package/dist/core/browser-history/research-cluster-fanout.js +39 -16
  126. package/dist/core/channel-timeline.d.ts +5 -1
  127. package/dist/core/channel-timeline.js +13 -0
  128. package/dist/core/context/index-reconciler.js +5 -2
  129. package/dist/core/context/policy-index-reconciler.d.ts +6 -4
  130. package/dist/core/context/policy-index-runner.js +25 -6
  131. package/dist/core/context-builder-calendar.js +10 -2
  132. package/dist/core/context-builder-conversation.d.ts +8 -1
  133. package/dist/core/context-builder-conversation.js +41 -7
  134. package/dist/core/context-builder-yesterday.js +4 -3
  135. package/dist/core/context-builder.d.ts +7 -2
  136. package/dist/core/context-builder.js +193 -5
  137. package/dist/core/context-file-serializer.d.ts +1 -1
  138. package/dist/core/context-file-serializer.js +1 -1
  139. package/dist/core/context-health.js +2 -2
  140. package/dist/core/context-paths.d.ts +11 -1
  141. package/dist/core/context-paths.js +17 -1
  142. package/dist/core/context-validation/prepare-write.js +1 -1
  143. package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
  144. package/dist/core/context-vault-aliases.d.ts +0 -13
  145. package/dist/core/context-vault-aliases.js +37 -0
  146. package/dist/core/custom-routines.d.ts +99 -0
  147. package/dist/core/custom-routines.js +187 -0
  148. package/dist/core/daemon-api-cli.js +50 -1
  149. package/dist/core/day-boundary.d.ts +46 -0
  150. package/dist/core/day-boundary.js +40 -0
  151. package/dist/core/dispatcher-activity-scan.d.ts +221 -0
  152. package/dist/core/dispatcher-activity-scan.js +775 -0
  153. package/dist/core/dispatcher-error-handling.d.ts +6 -11
  154. package/dist/core/dispatcher-error-handling.js +38 -62
  155. package/dist/core/dispatcher-hourly-check.js +6 -1
  156. package/dist/core/dispatcher-message-handler.d.ts +10 -0
  157. package/dist/core/dispatcher-message-handler.js +24 -0
  158. package/dist/core/dispatcher-morning-routine.d.ts +6 -6
  159. package/dist/core/dispatcher-morning-routine.js +13 -13
  160. package/dist/core/dispatcher-result-processor.d.ts +33 -0
  161. package/dist/core/dispatcher-result-processor.js +167 -11
  162. package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
  163. package/dist/core/dispatcher-scheduled-background-task.js +89 -0
  164. package/dist/core/dispatcher-scheduled-tasks.d.ts +104 -1
  165. package/dist/core/dispatcher-scheduled-tasks.js +480 -8
  166. package/dist/core/dispatcher-task-delivery.d.ts +105 -0
  167. package/dist/core/dispatcher-task-delivery.js +555 -0
  168. package/dist/core/dispatcher-types.d.ts +48 -9
  169. package/dist/core/dispatcher-types.js +3 -3
  170. package/dist/core/dispatcher.d.ts +112 -31
  171. package/dist/core/dispatcher.js +297 -60
  172. package/dist/core/dm-freshness-metrics.d.ts +1 -1
  173. package/dist/core/drift-effects.js +2 -2
  174. package/dist/core/feedback/consolidation-prep.d.ts +94 -0
  175. package/dist/core/feedback/consolidation-prep.js +254 -0
  176. package/dist/core/feedback/eviction-scorer.d.ts +81 -0
  177. package/dist/core/feedback/eviction-scorer.js +136 -0
  178. package/dist/core/feedback/lesson-format.d.ts +79 -0
  179. package/dist/core/feedback/lesson-format.js +199 -0
  180. package/dist/core/feedback/lesson-injection.d.ts +98 -0
  181. package/dist/core/feedback/lesson-injection.js +174 -0
  182. package/dist/core/feedback/lesson-merge.d.ts +51 -0
  183. package/dist/core/feedback/lesson-merge.js +88 -0
  184. package/dist/core/feedback/lesson-store-overview.d.ts +46 -0
  185. package/dist/core/feedback/lesson-store-overview.js +42 -0
  186. package/dist/core/feedback/promotion-gate.d.ts +69 -0
  187. package/dist/core/feedback/promotion-gate.js +117 -0
  188. package/dist/core/feedback/regeneralization-prep.d.ts +87 -0
  189. package/dist/core/feedback/regeneralization-prep.js +152 -0
  190. package/dist/core/feedback/scope-parser.d.ts +86 -0
  191. package/dist/core/feedback/scope-parser.js +141 -0
  192. package/dist/core/feedback/self-performance-prep.d.ts +186 -0
  193. package/dist/core/feedback/self-performance-prep.js +541 -0
  194. package/dist/core/feedback/tuning-actuator.d.ts +198 -0
  195. package/dist/core/feedback/tuning-actuator.js +432 -0
  196. package/dist/core/feedback/tuning-recommender.d.ts +247 -0
  197. package/dist/core/feedback/tuning-recommender.js +580 -0
  198. package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
  199. package/dist/core/feedback/tuning-revert-monitor.js +213 -0
  200. package/dist/core/health-monitor.d.ts +6 -0
  201. package/dist/core/health-monitor.js +1 -1
  202. package/dist/core/injection-policy.d.ts +83 -1
  203. package/dist/core/injection-policy.js +61 -3
  204. package/dist/core/integration-main-backend.js +4 -0
  205. package/dist/core/management-md.d.ts +2 -2
  206. package/dist/core/management-md.js +51 -13
  207. package/dist/core/morning/orchestrator.d.ts +2 -2
  208. package/dist/core/morning/orchestrator.js +2 -2
  209. package/dist/core/notification-gate.d.ts +64 -0
  210. package/dist/core/notification-gate.js +51 -0
  211. package/dist/core/notification-rate-limit.d.ts +40 -0
  212. package/dist/core/notification-rate-limit.js +50 -0
  213. package/dist/core/policy-files.d.ts +1 -1
  214. package/dist/core/policy-files.js +2 -2
  215. package/dist/core/pre-pass-freshness.d.ts +4 -4
  216. package/dist/core/retention.d.ts +5 -0
  217. package/dist/core/retention.js +20 -4
  218. package/dist/core/review-context.d.ts +1 -1
  219. package/dist/core/review-context.js +10 -5
  220. package/dist/core/roadmap-write-lock.d.ts +2 -1
  221. package/dist/core/roadmap-write-lock.js +15 -10
  222. package/dist/core/routine-acquisition-plan.d.ts +47 -1
  223. package/dist/core/routine-acquisition-plan.js +78 -20
  224. package/dist/core/routine-fetch-window-retry.js +7 -4
  225. package/dist/core/routine-fetch-window-runner.d.ts +39 -3
  226. package/dist/core/routine-fetch-window-runner.js +264 -13
  227. package/dist/core/routine-windows.d.ts +2 -2
  228. package/dist/core/routine-windows.js +8 -5
  229. package/dist/core/scheduler.d.ts +175 -16
  230. package/dist/core/scheduler.js +559 -102
  231. package/dist/core/signal-detector.d.ts +51 -1
  232. package/dist/core/signal-detector.js +321 -24
  233. package/dist/core/skills-compiler-denied-tools.js +2 -2
  234. package/dist/core/skills-compiler-skill-index.d.ts +2 -2
  235. package/dist/core/skills-compiler-skill-index.js +2 -2
  236. package/dist/core/skills-compiler-variants.d.ts +1 -1
  237. package/dist/core/skills-compiler-variants.js +8 -0
  238. package/dist/core/skills-compiler.d.ts +29 -26
  239. package/dist/core/skills-compiler.js +117 -81
  240. package/dist/core/skills-manifest.d.ts +37 -0
  241. package/dist/core/skills-manifest.js +73 -2
  242. package/dist/core/sleep-inhibitor.d.ts +79 -0
  243. package/dist/core/sleep-inhibitor.js +132 -0
  244. package/dist/core/slim-system-prompt-loader.d.ts +77 -0
  245. package/dist/core/slim-system-prompt-loader.js +141 -0
  246. package/dist/core/spawn-gates.d.ts +126 -0
  247. package/dist/core/spawn-gates.js +180 -0
  248. package/dist/core/today-direct-writer.d.ts +60 -14
  249. package/dist/core/today-direct-writer.js +90 -13
  250. package/dist/core/today-write-lock.d.ts +4 -2
  251. package/dist/core/today-write-lock.js +30 -20
  252. package/dist/core/wake-detector.d.ts +55 -0
  253. package/dist/core/wake-detector.js +80 -0
  254. package/dist/core/wiki/compile-lock.d.ts +1 -1
  255. package/dist/core/wiki/compile-lock.js +1 -1
  256. package/dist/core/wiki/wiki-fts.js +13 -6
  257. package/dist/core/workdir.js +15 -6
  258. package/dist/db/activity-scan-signals.d.ts +77 -0
  259. package/dist/db/activity-scan-signals.js +378 -0
  260. package/dist/db/agents-store.d.ts +28 -0
  261. package/dist/db/agents-store.js +62 -0
  262. package/dist/db/background-task-clarifications-store.d.ts +81 -0
  263. package/dist/db/background-task-clarifications-store.js +152 -0
  264. package/dist/db/background-task-store.d.ts +207 -0
  265. package/dist/db/background-task-store.js +380 -0
  266. package/dist/db/browser-history-store.d.ts +39 -6
  267. package/dist/db/browser-history-store.js +51 -7
  268. package/dist/db/browser-task-clarifications-store.d.ts +12 -0
  269. package/dist/db/browser-task-clarifications-store.js +35 -5
  270. package/dist/db/browser-task-store.d.ts +3 -0
  271. package/dist/db/browser-task-store.js +29 -4
  272. package/dist/db/deferred-dm.d.ts +86 -0
  273. package/dist/db/deferred-dm.js +199 -0
  274. package/dist/db/feedback-signals-store.d.ts +77 -0
  275. package/dist/db/feedback-signals-store.js +144 -0
  276. package/dist/db/migrations.js +380 -0
  277. package/dist/db/observations.d.ts +2 -2
  278. package/dist/db/observations.js +3 -3
  279. package/dist/db/schema.js +260 -22
  280. package/dist/db/voice-transcripts-store.d.ts +1 -1
  281. package/dist/index.js +86 -29
  282. package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
  283. package/dist/messaging/browser-task-mcp-notifier.js +30 -151
  284. package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
  285. package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
  286. package/dist/observers/delegated-sync-worker.d.ts +6 -6
  287. package/dist/observers/delegated-sync-worker.js +10 -10
  288. package/dist/observers/git-delegated-cron.d.ts +1 -1
  289. package/dist/observers/git-delegated-cron.js +2 -2
  290. package/dist/observers/github-poller-classifier.d.ts +3 -3
  291. package/dist/observers/github-poller-classifier.js +3 -3
  292. package/dist/observers/imminent-event-scheduler.d.ts +1 -1
  293. package/dist/observers/imminent-event-scheduler.js +1 -1
  294. package/dist/observers/mail-poller.d.ts +1 -0
  295. package/dist/observers/mail-poller.js +42 -3
  296. package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
  297. package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
  298. package/dist/observers/observation-summarizer/worker.d.ts +2 -2
  299. package/dist/observers/observation-summarizer/worker.js +4 -4
  300. package/dist/observers/obsidian-watcher.d.ts +1 -1
  301. package/dist/observers/obsidian-watcher.js +1 -1
  302. package/dist/safety/agent-write-tracker.d.ts +4 -4
  303. package/dist/safety/agent-write-tracker.js +4 -4
  304. package/dist/safety/always-disallowed.d.ts +1 -1
  305. package/dist/safety/always-disallowed.js +39 -0
  306. package/dist/safety/audit.d.ts +43 -5
  307. package/dist/safety/audit.js +86 -18
  308. package/dist/safety/risk-classifier.d.ts +6 -0
  309. package/dist/safety/risk-classifier.js +97 -18
  310. package/dist/scheduler/activity-scan-gate.d.ts +86 -0
  311. package/dist/scheduler/activity-scan-gate.js +132 -0
  312. package/dist/services/background-task/background-task-budget.d.ts +80 -0
  313. package/dist/services/background-task/background-task-budget.js +91 -0
  314. package/dist/services/background-task/background-task-driver.d.ts +105 -0
  315. package/dist/services/background-task/background-task-driver.js +416 -0
  316. package/dist/services/background-task/background-task-runner.d.ts +96 -0
  317. package/dist/services/background-task/background-task-runner.js +673 -0
  318. package/dist/services/background-task/background-task-tools.d.ts +84 -0
  319. package/dist/services/background-task/background-task-tools.js +247 -0
  320. package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
  321. package/dist/services/background-task/background-task-transition-events.js +54 -0
  322. package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
  323. package/dist/services/browser-history/automation/egress-denylist.js +34 -8
  324. package/dist/services/browser-history/lifecycle/platform.js +44 -2
  325. package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
  326. package/dist/services/browser-task/browser-task-runner.js +53 -8
  327. package/dist/services/mcp/probe.js +30 -8
  328. package/dist/services/observations-batch.d.ts +1 -1
  329. package/dist/services/observations-batch.js +2 -2
  330. package/dist/settings/runtime-settings.d.ts +45 -12
  331. package/dist/settings/runtime-settings.js +215 -40
  332. package/dist/settings/settings-store.js +11 -3
  333. package/package.json +4 -4
@@ -143,6 +143,10 @@ export function aggregateByBilledModel(rows) {
143
143
  .map(([model, v]) => ({ model, ...v }))
144
144
  .sort((a, b) => b.total_cost - a.total_cost);
145
145
  }
146
+ // Today's spend-driver list is intentionally capped: beyond the top 15 the
147
+ // rows are long-tail noise (the by-process panel covers aggregate share),
148
+ // and an uncapped list would grow unbounded on chatty days.
149
+ const TODAY_TOP_ACTIONS_LIMIT = 15;
146
150
  // `bucketExpr` takes a single shift-modifier parameter (e.g. "+300 minutes")
147
151
  // so each query binds it explicitly — see COST_QUERIES for the rationale.
148
152
  const COST_PERIOD_SPECS = {
@@ -211,6 +215,61 @@ export function registerCostApprovalsRoutes(app, deps) {
211
215
  WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
212
216
  AND cost_usd IS NOT NULL`)
213
217
  .get(bounds.start, bounds.end);
218
+ // ── Today's spend drivers ──
219
+ // All scoped to the same agent-day bounds as the Today card so the
220
+ // numbers reconcile: Σ byEventType.total_cost === today.costUsd.
221
+ // The windowed byEventType above answers "what cost money this month";
222
+ // these answer "what is costing money *right now*" — the question the
223
+ // owner asks when the Today card looks unexpectedly high.
224
+ const todayTopActions = db
225
+ .prepare(`SELECT id, event_id, action_type, trigger, model_used, model_usage_json, cost_usd,
226
+ tokens_input, tokens_output,
227
+ cache_creation_tokens, cache_read_tokens,
228
+ duration_ms, num_turns,
229
+ result, detail, started_at, completed_at, error
230
+ FROM agent_actions
231
+ WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
232
+ AND cost_usd IS NOT NULL AND cost_usd > 0
233
+ ORDER BY cost_usd DESC, datetime(started_at) DESC
234
+ LIMIT ${TODAY_TOP_ACTIONS_LIMIT}`)
235
+ .all(bounds.start, bounds.end);
236
+ const todayByEventType = db
237
+ .prepare(`SELECT action_type as event_type,
238
+ SUM(cost_usd) as total_cost,
239
+ COUNT(*) as session_count
240
+ FROM agent_actions
241
+ WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
242
+ AND cost_usd IS NOT NULL
243
+ GROUP BY action_type
244
+ ORDER BY total_cost DESC`)
245
+ .all(bounds.start, bounds.end);
246
+ const todayByTrigger = db
247
+ .prepare(`SELECT COALESCE(trigger, 'unknown') as trigger,
248
+ SUM(cost_usd) as total_cost,
249
+ COUNT(*) as session_count
250
+ FROM agent_actions
251
+ WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
252
+ AND cost_usd IS NOT NULL
253
+ GROUP BY 1
254
+ ORDER BY total_cost DESC`)
255
+ .all(bounds.start, bounds.end);
256
+ const todayTokens = db
257
+ .prepare(`SELECT COALESCE(SUM(tokens_input), 0) as input,
258
+ COALESCE(SUM(tokens_output), 0) as output,
259
+ COALESCE(SUM(cache_read_tokens), 0) as cacheRead,
260
+ COALESCE(SUM(cache_creation_tokens), 0) as cacheCreation
261
+ FROM agent_actions
262
+ WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
263
+ AND cost_usd IS NOT NULL`)
264
+ .get(bounds.start, bounds.end);
265
+ const todayFailed = db
266
+ .prepare(`SELECT COALESCE(SUM(cost_usd), 0) as cost,
267
+ COUNT(*) as sessions
268
+ FROM agent_actions
269
+ WHERE datetime(started_at) >= ? AND datetime(started_at) < ?
270
+ AND cost_usd IS NOT NULL
271
+ AND result = 'failed'`)
272
+ .get(bounds.start, bounds.end);
214
273
  return c.json({
215
274
  period,
216
275
  today: { costUsd: today.cost, sessions: today.sessions },
@@ -219,6 +278,13 @@ export function registerCostApprovalsRoutes(app, deps) {
219
278
  byEventType,
220
279
  byBackend,
221
280
  byBackendPeriod,
281
+ todayBreakdown: {
282
+ topActions: todayTopActions,
283
+ byEventType: todayByEventType,
284
+ byTrigger: todayByTrigger,
285
+ tokens: todayTokens,
286
+ failed: { costUsd: todayFailed.cost, sessions: todayFailed.sessions },
287
+ },
222
288
  });
223
289
  });
224
290
  // ── Approvals API ──
@@ -11,28 +11,28 @@ function getLocalHourMinute(date, timeZone) {
11
11
  const minute = Number(parts.find((part) => part.type === "minute")?.value ?? "0");
12
12
  return { hour, minute };
13
13
  }
14
- function isHourlyCheckSlot(date, config) {
15
- if (!config.hourlyCheckEnabled)
14
+ function isActivityScanSlot(date, config) {
15
+ if (!config.activityScanEnabled)
16
16
  return false;
17
17
  const { hour, minute } = getLocalHourMinute(date, config.timezone || undefined);
18
18
  if (hour === config.dayBoundaryHour)
19
19
  return false;
20
- if (hour < config.hourlyCheckActiveStartHour || hour >= config.hourlyCheckActiveEndHour) {
20
+ if (hour < config.activityScanActiveStartHour || hour >= config.activityScanActiveEndHour) {
21
21
  return false;
22
22
  }
23
- return minute % config.hourlyCheckIntervalMinutes === 0;
23
+ return minute % config.activityScanIntervalMinutes === 0;
24
24
  }
25
- function getNextHourlyCheck(config) {
26
- if (!config.hourlyCheckEnabled) {
25
+ function getNextActivityScan(config) {
26
+ if (!config.activityScanEnabled) {
27
27
  return { active: false, nextRunAt: null };
28
28
  }
29
29
  const now = new Date();
30
- const active = isHourlyCheckSlot(now, config);
30
+ const active = isActivityScanSlot(now, config);
31
31
  const start = new Date(now.getTime() + 60_000);
32
32
  start.setSeconds(0, 0);
33
33
  for (let offset = 0; offset < 48 * 60; offset++) {
34
34
  const candidate = new Date(start.getTime() + offset * 60_000);
35
- if (isHourlyCheckSlot(candidate, config)) {
35
+ if (isActivityScanSlot(candidate, config)) {
36
36
  return { active, nextRunAt: candidate.toISOString() };
37
37
  }
38
38
  }
@@ -41,7 +41,7 @@ function getNextHourlyCheck(config) {
41
41
  export function registerNotificationsRoutes(app, deps) {
42
42
  const { db, config } = deps;
43
43
  app.get("/dashboard/next-check", (c) => {
44
- return c.json(getNextHourlyCheck(config));
44
+ return c.json(getNextActivityScan(config));
45
45
  });
46
46
  // STAGE-C-DM-FRESHNESS-PLAN §Task 4 — DM freshness aggregate. Powered
47
47
  // by `agent_actions.detail.dm_freshness.*` rows the DM dispatch path
@@ -168,8 +168,9 @@ export function registerOauthGoogleRoutes(app, deps) {
168
168
  catch {
169
169
  return c.json({ error: "googleapis package not installed" }, 500);
170
170
  }
171
- // Use daemon's own port for the OAuth callback
172
- const redirectUri = `http://localhost:${config.apiPort}/api/config/google-auth/callback`;
171
+ // Use daemon's own port for the OAuth callback.
172
+ // Use 127.0.0.1, not localhost: daemon binds IPv4 loopback only; on Windows localhost resolves to ::1 first and the callback would ECONNREFUSED. Google special-cases loopback redirects for installed-app clients.
173
+ const redirectUri = `http://127.0.0.1:${config.apiPort}/api/config/google-auth/callback`;
173
174
  const oauth2Client = new google.auth.OAuth2(clientConfig.client_id, clientConfig.client_secret, redirectUri);
174
175
  // Request scopes for Calendar + Gmail
175
176
  const scopes = [
@@ -255,7 +256,8 @@ export function registerOauthGoogleRoutes(app, deps) {
255
256
  throw new Error("Invalid credentials");
256
257
  const mod = await import("googleapis");
257
258
  const google = mod.google;
258
- const redirectUri = `http://localhost:${config.apiPort}/api/config/google-auth/callback`;
259
+ // Use 127.0.0.1, not localhost: must byte-match the auth-start redirect_uri (Google re-validates an exact string at getToken); daemon binds IPv4 loopback only, and on Windows localhost resolves to ::1 first.
260
+ const redirectUri = `http://127.0.0.1:${config.apiPort}/api/config/google-auth/callback`;
259
261
  const oauth2Client = new google.auth.OAuth2(clientConfig.client_id, clientConfig.client_secret, redirectUri);
260
262
  // Exchange authorization code for tokens
261
263
  const { tokens } = await oauth2Client.getToken(code);
@@ -0,0 +1,3 @@
1
+ import { Hono } from "hono";
2
+ import type { ApiDependencies } from "../server.js";
3
+ export declare function createFeedbackRoutes(deps: ApiDependencies): Hono;
@@ -0,0 +1,349 @@
1
+ import { Hono } from "hono";
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { redactSensitiveString } from "@aitne/shared";
5
+ import { readJsonBody } from "../json-body.js";
6
+ import { getAgent } from "../../db/agents-store.js";
7
+ import { consumeFeedbackSignals, countPendingFeedbackSignals, findRecentFeedbackSignal, recordFeedbackSignal, } from "../../db/feedback-signals-store.js";
8
+ import { getContextDir } from "../../config.js";
9
+ import { CONTEXT_RELATIVE_PATHS, agentLessonsPath, } from "../../core/context-paths.js";
10
+ import { GLOBAL_LESSON_ENTRY_CAP, PER_AGENT_LESSON_ENTRY_CAP, } from "../../core/feedback/consolidation-prep.js";
11
+ import { summarizeLessonStore } from "../../core/feedback/lesson-store-overview.js";
12
+ import { isSafeAgentSlug } from "../../core/feedback/scope-parser.js";
13
+ import { createLogger } from "../../logging.js";
14
+ const logger = createLogger("feedback-api");
15
+ const DEDUP_TTL_SECONDS = 10 * 60;
16
+ const MAX_SUMMARY_CHARS = 280;
17
+ const MAX_SCOPE_REF_CHARS = 120;
18
+ const MAX_ACTION_REF_CHARS = 160;
19
+ const MAX_EVIDENCE_STRING_CHARS = 500;
20
+ const ALLOWED_SOURCES = new Set([
21
+ "explicit",
22
+ "self_critique",
23
+ ]);
24
+ const ALL_SOURCES = new Set([
25
+ "behavioral",
26
+ "explicit",
27
+ "self_critique",
28
+ ]);
29
+ const VALENCES = new Set([
30
+ "positive",
31
+ "negative",
32
+ "neutral",
33
+ "correction",
34
+ ]);
35
+ const API_SCOPE_TYPES = new Set([
36
+ "user",
37
+ "agent",
38
+ "agent_slug",
39
+ ]);
40
+ const ACTION_KINDS = new Set([
41
+ "notification",
42
+ "agent_execution",
43
+ "vault_write",
44
+ "dm_reply",
45
+ ]);
46
+ const KINDS = new Set([
47
+ "preference",
48
+ "correction",
49
+ "do-more",
50
+ "do-less",
51
+ "constraint",
52
+ ]);
53
+ function isRecord(value) {
54
+ return value !== null && typeof value === "object" && !Array.isArray(value);
55
+ }
56
+ function truncate(value, maxChars) {
57
+ return value.length <= maxChars ? value : value.slice(0, maxChars);
58
+ }
59
+ function sanitizeString(value, maxChars = MAX_EVIDENCE_STRING_CHARS) {
60
+ return redactSensitiveString(truncate(value.replace(/[\u0000-\u001f\u007f]/g, " "), maxChars)).trim();
61
+ }
62
+ function sanitizeEvidence(value, depth = 0) {
63
+ if (depth > 4)
64
+ return "[truncated]";
65
+ if (typeof value === "string")
66
+ return sanitizeString(value);
67
+ if (typeof value === "number" || typeof value === "boolean" || value === null)
68
+ return value;
69
+ if (Array.isArray(value)) {
70
+ return value.slice(0, 20).map((entry) => sanitizeEvidence(entry, depth + 1));
71
+ }
72
+ if (!isRecord(value))
73
+ return null;
74
+ const out = {};
75
+ for (const [key, entry] of Object.entries(value).slice(0, 50)) {
76
+ out[sanitizeString(key, 80)] = sanitizeEvidence(entry, depth + 1);
77
+ }
78
+ return out;
79
+ }
80
+ function describeType(value) {
81
+ if (value === undefined)
82
+ return "missing";
83
+ if (value === null)
84
+ return "null";
85
+ if (Array.isArray(value))
86
+ return "array";
87
+ return typeof value;
88
+ }
89
+ function buildLessonStoreEntry(contextDir, scope, relPath, caps) {
90
+ const full = join(contextDir, relPath);
91
+ if (!existsSync(full)) {
92
+ return {
93
+ scope,
94
+ path: relPath,
95
+ exists: false,
96
+ lastModified: null,
97
+ bytes: 0,
98
+ capBytes: caps.capBytes,
99
+ entries: 0,
100
+ maxEntries: caps.maxEntries,
101
+ active: 0,
102
+ provisional: 0,
103
+ overCap: false,
104
+ };
105
+ }
106
+ const summary = summarizeLessonStore(readFileSync(full, "utf-8"), caps);
107
+ return {
108
+ scope,
109
+ path: relPath,
110
+ exists: true,
111
+ lastModified: statSync(full).mtime.toISOString(),
112
+ ...summary,
113
+ };
114
+ }
115
+ export function createFeedbackRoutes(deps) {
116
+ const app = new Hono();
117
+ const { db, config } = deps;
118
+ /**
119
+ * GET /feedback/lessons — read-only overview of the consolidated lesson
120
+ * stores for the dashboard "view/edit lessons and tune caps/threshold"
121
+ * surface (FEEDBACK_LEARNING_LOOP_DESIGN.md §9 Phase 5). Lists the global
122
+ * `agent` store (always, so its cap shows even before first write) plus every
123
+ * per-agent `agent:<slug>` store that exists on disk, each with cap-utilisation
124
+ * metrics. The file bodies are read/edited through the existing
125
+ * `GET/PUT /api/context/<path>` chokepoint — this endpoint only enumerates +
126
+ * summarises. `RiskTier.Autonomous` (read-only, no secrets — lesson prose was
127
+ * redaction-scrubbed at capture).
128
+ */
129
+ app.get("/feedback/lessons", (c) => {
130
+ const contextDir = getContextDir(config, db);
131
+ const globalCaps = {
132
+ capBytes: config.feedbackLessonMaxBytesGlobal,
133
+ maxEntries: GLOBAL_LESSON_ENTRY_CAP,
134
+ };
135
+ const perAgentCaps = {
136
+ capBytes: config.feedbackLessonMaxBytesPerAgent,
137
+ maxEntries: PER_AGENT_LESSON_ENTRY_CAP,
138
+ };
139
+ const stores = [
140
+ buildLessonStoreEntry(contextDir, "agent", CONTEXT_RELATIVE_PATHS.agentLessons, globalCaps),
141
+ ];
142
+ const agentsDir = join(contextDir, "policies", "agents");
143
+ if (existsSync(agentsDir)) {
144
+ const slugs = readdirSync(agentsDir, { withFileTypes: true })
145
+ .filter((entry) => entry.isDirectory() && isSafeAgentSlug(entry.name))
146
+ .map((entry) => entry.name)
147
+ .sort();
148
+ for (const slug of slugs) {
149
+ const rel = agentLessonsPath(slug);
150
+ if (!existsSync(join(contextDir, rel)))
151
+ continue;
152
+ stores.push(buildLessonStoreEntry(contextDir, `agent:${slug}`, rel, perAgentCaps));
153
+ }
154
+ }
155
+ return c.json({
156
+ enabled: config.feedbackLearningEnabled !== false,
157
+ promotionThreshold: config.feedbackPromotionThreshold,
158
+ pendingSignals: countPendingFeedbackSignals(db),
159
+ stores,
160
+ });
161
+ });
162
+ app.post("/feedback", async (c) => {
163
+ // Master kill-switch (FEEDBACK_LEARNING_LOOP_DESIGN.md §7). When the loop is
164
+ // disabled the daemon-side behavioral sink already short-circuits
165
+ // (`SignalDetector`); mirror that here so explicit / self_critique captures
166
+ // from the always-included DM + review task-flows are dropped too. Otherwise
167
+ // unconsumed rows would accumulate unbounded — the nightly consolidation that
168
+ // would consume them is gated off, and the retention sweep only deletes
169
+ // already-consumed rows. Returns 200 so the calling turn neither errors nor
170
+ // retries. `config` is optional in some test harnesses → default-on.
171
+ if (config?.feedbackLearningEnabled === false) {
172
+ return c.json({ disabled: true });
173
+ }
174
+ const parsedBody = await readJsonBody(c);
175
+ if (!parsedBody.ok)
176
+ return parsedBody.response;
177
+ const body = parsedBody.body;
178
+ if (!isRecord(body)) {
179
+ return c.json({
180
+ error: "validation_error",
181
+ message: "Body must be a JSON object",
182
+ }, 400);
183
+ }
184
+ const issues = [];
185
+ const rawSource = body.source;
186
+ const source = typeof rawSource === "string" ? rawSource : "";
187
+ if (!ALL_SOURCES.has(source)) {
188
+ issues.push({
189
+ field: "source",
190
+ expected: "'explicit' | 'self_critique'",
191
+ got: describeType(rawSource),
192
+ });
193
+ }
194
+ else if (!ALLOWED_SOURCES.has(source)) {
195
+ issues.push({
196
+ field: "source",
197
+ expected: "'explicit' | 'self_critique'",
198
+ got: "'behavioral'",
199
+ hint: "Behavioral feedback is daemon-only and is written by SignalDetector.",
200
+ });
201
+ }
202
+ const summary = typeof body.summary === "string"
203
+ ? sanitizeString(body.summary, MAX_SUMMARY_CHARS)
204
+ : "";
205
+ if (summary.length === 0) {
206
+ issues.push({
207
+ field: "summary",
208
+ expected: "non-empty string",
209
+ got: describeType(body.summary),
210
+ });
211
+ }
212
+ const rawValence = body.valence;
213
+ const valence = typeof rawValence === "string" ? rawValence : null;
214
+ if (valence === null || !VALENCES.has(valence)) {
215
+ issues.push({
216
+ field: "valence",
217
+ expected: "'positive' | 'negative' | 'neutral' | 'correction'",
218
+ got: describeType(rawValence),
219
+ });
220
+ }
221
+ const rawKind = body.kind;
222
+ const kind = typeof rawKind === "string" ? rawKind : null;
223
+ if (kind !== null && !KINDS.has(kind)) {
224
+ issues.push({
225
+ field: "kind",
226
+ expected: "'preference' | 'correction' | 'do-more' | 'do-less' | 'constraint' (optional)",
227
+ got: kind,
228
+ });
229
+ }
230
+ const rawScopeType = body.scope_type;
231
+ const scopeType = typeof rawScopeType === "string" ? rawScopeType : "";
232
+ if (!API_SCOPE_TYPES.has(scopeType)) {
233
+ issues.push({
234
+ field: "scope_type",
235
+ expected: "'user' | 'agent' | 'agent_slug'",
236
+ got: describeType(rawScopeType),
237
+ });
238
+ }
239
+ const scopeRef = typeof body.scope_ref === "string"
240
+ ? sanitizeString(body.scope_ref, MAX_SCOPE_REF_CHARS)
241
+ : null;
242
+ let agentId = null;
243
+ if (scopeType === "agent_slug") {
244
+ if (!scopeRef) {
245
+ issues.push({
246
+ field: "scope_ref",
247
+ expected: "existing agent slug when scope_type='agent_slug'",
248
+ got: describeType(body.scope_ref),
249
+ });
250
+ }
251
+ else if (!getAgent(db, scopeRef)) {
252
+ issues.push({
253
+ field: "scope_ref",
254
+ expected: "existing agent slug",
255
+ got: scopeRef,
256
+ });
257
+ }
258
+ else {
259
+ agentId = scopeRef;
260
+ }
261
+ }
262
+ const rawActionKind = body.action_kind;
263
+ const actionKind = typeof rawActionKind === "string" ? rawActionKind : null;
264
+ if (actionKind !== null
265
+ && !ACTION_KINDS.has(actionKind)) {
266
+ issues.push({
267
+ field: "action_kind",
268
+ expected: "'notification' | 'agent_execution' | 'vault_write' | 'dm_reply' (optional)",
269
+ got: actionKind,
270
+ });
271
+ }
272
+ const actionRef = typeof body.action_ref === "string"
273
+ ? sanitizeString(body.action_ref, MAX_ACTION_REF_CHARS)
274
+ : null;
275
+ if (issues.length > 0) {
276
+ return c.json({
277
+ error: "validation_error",
278
+ message: "Request body failed schema validation",
279
+ issues,
280
+ }, 400);
281
+ }
282
+ const normalizedScopeRef = scopeType === "agent_slug" ? scopeRef : null;
283
+ const deduped = findRecentFeedbackSignal(db, {
284
+ scopeType: scopeType,
285
+ scopeRef: normalizedScopeRef,
286
+ summary,
287
+ withinSeconds: DEDUP_TTL_SECONDS,
288
+ });
289
+ if (deduped) {
290
+ return c.json({ id: deduped.id, deduped: true });
291
+ }
292
+ const evidence = sanitizeEvidence(body.evidence);
293
+ const id = recordFeedbackSignal(db, {
294
+ source: source,
295
+ valence: valence,
296
+ scopeType: scopeType,
297
+ scopeRef: normalizedScopeRef,
298
+ actionKind: actionKind,
299
+ actionRef,
300
+ agentId,
301
+ summary,
302
+ evidence: {
303
+ ...(isRecord(evidence)
304
+ ? evidence
305
+ : evidence === null
306
+ ? {}
307
+ : { value: evidence }),
308
+ ...(kind ? { kind } : {}),
309
+ },
310
+ });
311
+ logger.info({ id, source, scopeType, scopeRef: normalizedScopeRef }, "Feedback signal recorded");
312
+ return c.json({ id });
313
+ });
314
+ app.post("/feedback/consume", async (c) => {
315
+ const parsedBody = await readJsonBody(c);
316
+ if (!parsedBody.ok)
317
+ return parsedBody.response;
318
+ const body = parsedBody.body;
319
+ if (!isRecord(body)) {
320
+ return c.json({
321
+ error: "validation_error",
322
+ message: "Body must be a JSON object",
323
+ expectedShape: '{"ids": number[], "lessonRef"?: string}',
324
+ }, 400);
325
+ }
326
+ if (!Array.isArray(body.ids)) {
327
+ return c.json({
328
+ error: "validation_error",
329
+ message: "'ids' must be an array of integer feedback signal ids",
330
+ expectedShape: '{"ids": number[], "lessonRef"?: string}',
331
+ }, 400);
332
+ }
333
+ const nonInt = body.ids.find((id) => typeof id !== "number" || !Number.isInteger(id));
334
+ if (nonInt !== undefined) {
335
+ return c.json({
336
+ error: "validation_error",
337
+ message: "'ids' must contain only integers",
338
+ got: JSON.stringify(nonInt),
339
+ }, 400);
340
+ }
341
+ const lessonRef = typeof body.lessonRef === "string"
342
+ ? sanitizeString(body.lessonRef, 240)
343
+ : null;
344
+ const result = consumeFeedbackSignals(db, body.ids, lessonRef);
345
+ logger.info({ consumed: result.consumed }, "Feedback signals consumed");
346
+ return c.json(result);
347
+ });
348
+ return app;
349
+ }
@@ -113,8 +113,12 @@ export function createGitRoutes(deps) {
113
113
  }
114
114
  // Use || (not ??) so explicit ?ref= (empty string) falls back to default
115
115
  const ref = c.req.query("ref") || "HEAD~1..HEAD";
116
- // Sanitize ref — reject shell metacharacters and flag-like arguments
117
- if (/[;&|`$]/.test(ref) || ref.startsWith("-")) {
116
+ // Sanitize ref — reject shell metacharacters and flag-like arguments.
117
+ // Also reject `:` — `git diff <rev>:<path>` is blob/tree syntax that reads
118
+ // arbitrary committed file content, beyond this endpoint's commit-diff
119
+ // purpose. No legitimate commit/range ref (`HEAD~1..HEAD`, `origin/main`)
120
+ // contains a colon. (execFile already prevents shell injection.)
121
+ if (/[;&|`$]/.test(ref) || ref.startsWith("-") || ref.includes(":")) {
118
122
  return respondWithAgentError(c, 400, [
119
123
  composeIssue("git.invalid_ref", {
120
124
  field: "ref",
@@ -149,7 +153,10 @@ export function createGitRoutes(deps) {
149
153
  ], { legacyErrorCode: "invalid or missing repo" });
150
154
  }
151
155
  const hash = c.req.query("hash") || "HEAD";
152
- if (/[;&|`$]/.test(hash) || hash.startsWith("-")) {
156
+ // Reject `:` for the same reason as /git/diff `git show <rev>:<path>`
157
+ // prints arbitrary committed file content, beyond this endpoint's
158
+ // commit-show purpose. A commit hash / ref never contains a colon.
159
+ if (/[;&|`$]/.test(hash) || hash.startsWith("-") || hash.includes(":")) {
153
160
  return respondWithAgentError(c, 400, [
154
161
  composeIssue("git.invalid_hash", {
155
162
  field: "hash",
@@ -224,7 +224,11 @@ export function createGitHubRoutes(deps) {
224
224
  }
225
225
  const event = parseGitHubEvent(eventType, payload, resolved.repositoryId);
226
226
  if (event) {
227
- eventBus.put(event);
227
+ // `EventBus.put` is async; awaiting it (like the repositories routes do)
228
+ // ensures the event is enqueued before the webhook returns `accepted`
229
+ // and surfaces any enqueue rejection instead of dropping it as an
230
+ // unhandled promise rejection.
231
+ await eventBus.put(event);
228
232
  logger.info({ eventType, action: payload.action, repositoryId: resolved.repositoryId }, "GitHub webhook event received");
229
233
  }
230
234
  // Notify GitWatcher that webhook is alive (adjusts polling frequency)
@@ -368,6 +368,9 @@ export async function handleIntegrationPatch(c, deps) {
368
368
  const finalNativeSyncEnabled = parsed.data.nativeSyncEnabled === undefined
369
369
  ? previous.nativeSyncEnabled
370
370
  : parsed.data.nativeSyncEnabled;
371
+ const finalFetchTargets = parsed.data.fetchTargets === undefined
372
+ ? (previous.fetchTargets ?? [])
373
+ : parsed.data.fetchTargets;
371
374
  // §14.7 — synchronously consult the cached probe before committing a
372
375
  // mode flip to delegated/native. Per §14.7 the PATCH response path
373
376
  // intentionally never spawns a live probe ("no blocking subprocess");
@@ -470,6 +473,7 @@ export async function handleIntegrationPatch(c, deps) {
470
473
  ...(finalNativeSyncEnabled === false
471
474
  ? { nativeSyncEnabled: false }
472
475
  : {}),
476
+ fetchTargets: finalFetchTargets,
473
477
  deniedTools: finalDeniedTools,
474
478
  lastChangedAt: stamped,
475
479
  });
@@ -501,7 +505,7 @@ export async function handleIntegrationPatch(c, deps) {
501
505
  // re-evaluation — the predicate
502
506
  // (`hasActiveDelegatedSyncIntegration`) ignores `nativeSyncEnabled`
503
507
  // because the worker has no role in native mode (see appendix
504
- // §"Polling, observers, and the hourly-check threshold"). The
508
+ // §"Polling, observers, and the activity-scan threshold"). The
505
509
  // `nativeSyncEnabled` field is retained on the state row for
506
510
  // schema compatibility but toggling it is inert today.
507
511
  const syncChanged = (previous.delegatedSyncEnabled ?? true)
@@ -174,7 +174,7 @@ export function createIntegrationReconcileRoutes(deps) {
174
174
  * callers bypass the route and write any window key directly. Plan §6.0
175
175
  * defense layer 2.
176
176
  *
177
- * Calendar-only by design (post-Phase-5 review): the LLM hourly_check
177
+ * Calendar-only by design (post-Phase-5 review): the LLM activity_scan
178
178
  * Step 0b fetches the same `[now-15min, now+60min)` and `[now, now+24h)`
179
179
  * windows the daemon's `delegated-sync-worker` uses for `primary:imminent`
180
180
  * and `primary:24h`, so an LLM POST and a worker POST land in the same
@@ -188,7 +188,7 @@ export function createIntegrationReconcileRoutes(deps) {
188
188
  * `reconcile.ts:319-345`). The fix is to keep gmail/notion authoring
189
189
  * inside the daemon worker only; the LLM consumes drift signals via
190
190
  * `GET /api/observations`. The corresponding Step 0a / 0c blocks of
191
- * `routine.hourly_check.delegated.<backend>.md` were rewritten to
191
+ * `routine.activity_scan.delegated.<backend>.md` were rewritten to
192
192
  * forbid the POST and document the rationale; this allowlist is the
193
193
  * defense-in-depth backstop that catches a future overlay rewrite that
194
194
  * silently re-introduces the LLM call.