@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
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Stage-0 signal compute for the three-stage activity_scan gate
3
+ * (cost-reduction-structural §B). Pure DB-read shape: every value is
4
+ * derived from existing tables (`observations`, `mail_messages_index`,
5
+ * `agent_schedule`, `agent_actions`) plus the optional today.md content
6
+ * passed in by the caller. No LLM call, no filesystem I/O of its own —
7
+ * the dispatcher injects the today.md snapshot (when available) so this
8
+ * module stays unit-testable from a synthetic Database handle.
9
+ *
10
+ * The shape mirrors the design doc 1:1 so the gate (Stage 1) can be
11
+ * a pure function over `ActivityScanSignals` + a config block.
12
+ */
13
+ import { buildSourcePrefixFilter, } from "@aitne/shared";
14
+ /**
15
+ * Build a `(source LIKE 'a:%' OR source LIKE 'b:%' ...)` clause covering
16
+ * every direct-poller and pre-pass-partial prefix for the given kinds.
17
+ * Sourced from `INTEGRATION_DESCRIPTORS` so a new integration shipping
18
+ * with `prePassPartial: "<kind>-acquire.<key>.md"` auto-extends the
19
+ * gate's view of the world. Empty `kinds` yields `(1=0)` (defensive).
20
+ *
21
+ * CLAUDE.md: "Never hardcode an integration reference outside the
22
+ * registry."
23
+ */
24
+ function sourceMatchClause(kinds) {
25
+ return buildSourcePrefixFilter(kinds);
26
+ }
27
+ /** All-kinds union — every observation source the gate cares about. */
28
+ const ALL_KINDS = [
29
+ "mail",
30
+ "calendar",
31
+ "notion",
32
+ "vault",
33
+ "repo",
34
+ ];
35
+ export function computeActivityScanSignals(db, options = {}) {
36
+ const now = options.now ?? new Date();
37
+ const vipSenders = (options.vipMailSenders ?? []).map((s) => s.toLowerCase());
38
+ const calendarHorizonMs = (options.calendarHorizonHours ?? 24) * 60 * 60 * 1000;
39
+ const scheduleHorizonHours = options.scheduleHorizonHours ?? 6;
40
+ // Gate filters by `source` (mail/calendar/notion/vault/repo), NEVER by
41
+ // `actor`. Delegated-sync-worker and the routine.fetch_window pre-pass
42
+ // both POST `actor='agent'` rows; the gate must see them as real
43
+ // activity. The 30-min `pre_pass_last_run:<key>` freshness window in
44
+ // `ActivityScanCoordinator.harvestForGate` structurally prevents a
45
+ // single pre-pass row from being re-counted across ticks.
46
+ const allFilter = sourceMatchClause(ALL_KINDS);
47
+ const pending = db
48
+ .prepare(`SELECT COUNT(*) AS count
49
+ FROM observations
50
+ WHERE consumed_at IS NULL
51
+ AND ${allFilter.clause}`)
52
+ .get(...allFilter.values);
53
+ const noveltyMaxRow = db
54
+ .prepare(`SELECT MAX(novelty_score) AS max_score
55
+ FROM observations
56
+ WHERE consumed_at IS NULL
57
+ AND summary_status = 'done'
58
+ AND novelty_score IS NOT NULL
59
+ AND ${allFilter.clause}`)
60
+ .get(...allFilter.values);
61
+ const distributionRows = db
62
+ .prepare(`SELECT novelty_score AS score, COUNT(*) AS count
63
+ FROM observations
64
+ WHERE consumed_at IS NULL
65
+ AND summary_status = 'done'
66
+ AND novelty_score IS NOT NULL
67
+ AND ${allFilter.clause}
68
+ GROUP BY novelty_score`)
69
+ .all(...allFilter.values);
70
+ const noveltyDistribution = { low: 0, mid: 0, high: 0 };
71
+ for (const row of distributionRows) {
72
+ if (row.score <= 1)
73
+ noveltyDistribution.low += row.count;
74
+ else if (row.score === 2)
75
+ noveltyDistribution.mid += row.count;
76
+ else if (row.score >= 3)
77
+ noveltyDistribution.high += row.count;
78
+ }
79
+ const vipMailUnreadCount = countVipUnreadMail(db, vipSenders);
80
+ const calendarFilter = sourceMatchClause(["calendar"]);
81
+ const calendarRow = db
82
+ .prepare(`SELECT COUNT(*) AS count
83
+ FROM observations
84
+ WHERE consumed_at IS NULL
85
+ AND ${calendarFilter.clause}`)
86
+ .get(...calendarFilter.values);
87
+ const calendarHas24hChange = calendarRow.count > 0;
88
+ const calendarHasConflict = detectCalendarConflict(db, now, calendarHorizonMs);
89
+ const agentPlanOverdueCount = countOverdueAgentPlanRows(options.todayMd ?? null, now, options.agentTimezone);
90
+ const horizon = new Date(now.getTime() + scheduleHorizonHours * 60 * 60 * 1000);
91
+ const scheduleRow = db
92
+ .prepare(`SELECT COUNT(*) AS count
93
+ FROM agent_schedule
94
+ WHERE status = 'pending'
95
+ AND scheduled_for >= ?
96
+ AND scheduled_for < ?`)
97
+ .get(toSqliteUtc(now), toSqliteUtc(horizon));
98
+ const hoursSinceLastStage3Run = computeHoursSinceLastStage3(db, now);
99
+ return {
100
+ pendingObsCount: pending.count,
101
+ maxNoveltyScore: noveltyMaxRow.max_score,
102
+ noveltyDistribution,
103
+ vipMailUnreadCount,
104
+ calendarHas24hChange,
105
+ calendarHasConflict,
106
+ agentPlanOverdueCount,
107
+ scheduleApproachingCount: scheduleRow.count,
108
+ hoursSinceLastStage3Run,
109
+ };
110
+ }
111
+ function countVipUnreadMail(db, vipSenders) {
112
+ if (vipSenders.length === 0)
113
+ return 0;
114
+ // Primary path (delegated / native / direct-with-pre-pass):
115
+ // unread VIP mail rides on `observations` with source prefixes
116
+ // `gmail:%` / `outlook_mail:%` (pre-pass) or `mail:%` (direct
117
+ // aggregate; only carries lifecycle metadata). The pre-pass payload
118
+ // is normalized at the `/api/observations` POST chokepoint to surface
119
+ // `is_read=0` + `from_email=<lowercased>` so this query can read a
120
+ // single canonical shape regardless of provider. We treat a row as
121
+ // VIP-unread when EITHER:
122
+ // (a) the normalized `is_read=0` + `from_email` keys are present
123
+ // and match, OR
124
+ // (b) `is_read` is absent (pre-normalization payload or non-mail
125
+ // lifecycle row) but `payload.raw.from` is a substring match
126
+ // for one of the VIP addresses — captures the legacy case
127
+ // where the partial doesn't emit the normalized keys.
128
+ const mailFilter = sourceMatchClause(["mail"]);
129
+ const senderPlaceholders = vipSenders.map(() => "?").join(",");
130
+ const observationsRow = db
131
+ .prepare(`SELECT COUNT(*) AS count
132
+ FROM observations
133
+ WHERE consumed_at IS NULL
134
+ AND ${mailFilter.clause}
135
+ AND COALESCE(json_extract(payload, '$.is_read'), 0) = 0
136
+ AND LOWER(
137
+ COALESCE(
138
+ json_extract(payload, '$.from_email'),
139
+ json_extract(payload, '$.raw.from'),
140
+ json_extract(payload, '$.from'),
141
+ ''
142
+ )
143
+ ) IN (${senderPlaceholders})`)
144
+ .get(...mailFilter.values, ...vipSenders);
145
+ let observationsCount = observationsRow.count;
146
+ // Substring fallback for `payload.raw.from` shapes like
147
+ // "Foo Bar <foo@bar.com>" — the IN-match above only catches exact
148
+ // address fields. Defensive cap (50 rows) keeps the per-tick cost
149
+ // bounded; tens of unread VIP mails in one window is already a
150
+ // hard-escalate signal regardless of exact count.
151
+ if (observationsCount === 0) {
152
+ const candidateRows = db
153
+ .prepare(`SELECT json_extract(payload, '$.raw.from') AS rawFrom
154
+ FROM observations
155
+ WHERE consumed_at IS NULL
156
+ AND ${mailFilter.clause}
157
+ AND COALESCE(json_extract(payload, '$.is_read'), 0) = 0
158
+ LIMIT 50`)
159
+ .all(...mailFilter.values);
160
+ for (const row of candidateRows) {
161
+ if (!row.rawFrom)
162
+ continue;
163
+ const haystack = row.rawFrom.toLowerCase();
164
+ if (vipSenders.some((s) => haystack.includes(s))) {
165
+ observationsCount += 1;
166
+ }
167
+ }
168
+ }
169
+ // Fallback path (direct mode pre-pre-pass installs whose observations
170
+ // are aggregate `mail:lifecycle` rows without per-message detail).
171
+ // The `mail_messages_index` table carries the canonical `is_read` +
172
+ // `from_email` columns and the MailPoller writes to it on every poll.
173
+ const tableExists = db
174
+ .prepare(`SELECT 1 AS present
175
+ FROM sqlite_master
176
+ WHERE type = 'table' AND name = 'mail_messages_index'`)
177
+ .get();
178
+ let tableCount = 0;
179
+ if (tableExists) {
180
+ const row = db
181
+ .prepare(`SELECT COUNT(*) AS count
182
+ FROM mail_messages_index
183
+ WHERE is_read = 0
184
+ AND deleted_at_utc IS NULL
185
+ AND LOWER(COALESCE(from_email, '')) IN (${senderPlaceholders})`)
186
+ .get(...vipSenders);
187
+ tableCount = row.count;
188
+ }
189
+ return Math.max(observationsCount, tableCount);
190
+ }
191
+ function detectCalendarConflict(db, now, horizonMs) {
192
+ // Pull pending calendar observation payloads. We tolerate the common
193
+ // shapes (`start`, `end`, `start.dateTime`, `end.dateTime`) and bail
194
+ // gracefully when neither is recognizable. A "conflict" is two events
195
+ // whose [start, end) ranges intersect within the lookahead window.
196
+ const calendarFilter = sourceMatchClause(["calendar"]);
197
+ const rows = db
198
+ .prepare(`SELECT payload
199
+ FROM observations
200
+ WHERE consumed_at IS NULL
201
+ AND payload IS NOT NULL
202
+ AND ${calendarFilter.clause}`)
203
+ .all(...calendarFilter.values);
204
+ const ranges = [];
205
+ const horizon = now.getTime() + horizonMs;
206
+ for (const row of rows) {
207
+ if (!row.payload)
208
+ continue;
209
+ let parsed;
210
+ try {
211
+ parsed = JSON.parse(row.payload);
212
+ }
213
+ catch {
214
+ continue;
215
+ }
216
+ const range = extractRange(parsed);
217
+ if (!range)
218
+ continue;
219
+ if (range.end <= now.getTime())
220
+ continue;
221
+ if (range.start >= horizon)
222
+ continue;
223
+ ranges.push(range);
224
+ }
225
+ if (ranges.length < 2)
226
+ return false;
227
+ ranges.sort((a, b) => a.start - b.start);
228
+ for (let i = 1; i < ranges.length; i++) {
229
+ if (ranges[i].start < ranges[i - 1].end) {
230
+ return true;
231
+ }
232
+ }
233
+ return false;
234
+ }
235
+ function extractRange(payload) {
236
+ if (!payload || typeof payload !== "object")
237
+ return null;
238
+ const record = payload;
239
+ const start = parseTimestamp(record["start"] ??
240
+ (typeof record["startTime"] === "string" ? record["startTime"] : undefined) ??
241
+ readNested(record, "start", "dateTime") ??
242
+ readNested(record, "start", "date"));
243
+ const end = parseTimestamp(record["end"] ??
244
+ (typeof record["endTime"] === "string" ? record["endTime"] : undefined) ??
245
+ readNested(record, "end", "dateTime") ??
246
+ readNested(record, "end", "date"));
247
+ if (start === null || end === null || end <= start)
248
+ return null;
249
+ return { start, end };
250
+ }
251
+ function readNested(record, key, inner) {
252
+ const child = record[key];
253
+ if (!child || typeof child !== "object")
254
+ return undefined;
255
+ return child[inner];
256
+ }
257
+ function parseTimestamp(value) {
258
+ if (typeof value !== "string" || value.length === 0)
259
+ return null;
260
+ const ts = Date.parse(value);
261
+ return Number.isFinite(ts) ? ts : null;
262
+ }
263
+ const AGENT_PLAN_HEADING = /^##\s+Agent Plan\b/i;
264
+ const NEXT_HEADING = /^##\s+/;
265
+ const PLAN_ROW_TIME = /^[\s>*-]*?(\d{1,2}):(\d{2})\b/;
266
+ function countOverdueAgentPlanRows(todayMd, now, agentTimezone) {
267
+ if (!todayMd)
268
+ return 0;
269
+ const lines = todayMd.split(/\r?\n/);
270
+ let inPlan = false;
271
+ let count = 0;
272
+ const nowMin = minutesOfDayInTimezone(now, agentTimezone);
273
+ for (const line of lines) {
274
+ if (AGENT_PLAN_HEADING.test(line)) {
275
+ inPlan = true;
276
+ continue;
277
+ }
278
+ if (inPlan && NEXT_HEADING.test(line)) {
279
+ break;
280
+ }
281
+ if (!inPlan)
282
+ continue;
283
+ const match = PLAN_ROW_TIME.exec(line);
284
+ if (!match)
285
+ continue;
286
+ const hh = Number(match[1]);
287
+ const mm = Number(match[2]);
288
+ if (!Number.isFinite(hh) || !Number.isFinite(mm))
289
+ continue;
290
+ if (hh > 23 || mm > 59)
291
+ continue;
292
+ const planMin = hh * 60 + mm;
293
+ if (planMin < nowMin)
294
+ count += 1;
295
+ }
296
+ return count;
297
+ }
298
+ /**
299
+ * Convert a wall-clock instant into "minutes-of-day in the given IANA
300
+ * timezone". Falls back to the JS engine's local timezone when no zone
301
+ * is supplied or the zone string is invalid — the latter avoids the
302
+ * gate going dark on an operator typo.
303
+ */
304
+ function minutesOfDayInTimezone(now, timezone) {
305
+ if (!timezone) {
306
+ return now.getHours() * 60 + now.getMinutes();
307
+ }
308
+ try {
309
+ const formatter = new Intl.DateTimeFormat("en-GB", {
310
+ hour: "2-digit",
311
+ minute: "2-digit",
312
+ hour12: false,
313
+ timeZone: timezone,
314
+ });
315
+ const parts = formatter.formatToParts(now);
316
+ const hh = Number(parts.find((p) => p.type === "hour")?.value ?? "0");
317
+ const mm = Number(parts.find((p) => p.type === "minute")?.value ?? "0");
318
+ if (!Number.isFinite(hh) || !Number.isFinite(mm)) {
319
+ return now.getHours() * 60 + now.getMinutes();
320
+ }
321
+ return hh * 60 + mm;
322
+ }
323
+ catch {
324
+ return now.getHours() * 60 + now.getMinutes();
325
+ }
326
+ }
327
+ function computeHoursSinceLastStage3(db, now) {
328
+ // Prefer the gate-emitted row (`activity_scan.gate` with
329
+ // stage_reached='stage3') — that matches the design doc's "since the
330
+ // last Stage 3 run" semantics. Fall back to the legacy
331
+ // `routine.activity_scan` action-type for installs deployed before
332
+ // the gate was wired. (The `stage3_shadow` marker was removed when
333
+ // the gateMode enum collapsed in HOURLY_CHECK_GATE_REDESIGN_PLAN.md
334
+ // Phase 4 — only the canonical `stage3` is ever written now.)
335
+ // The `hourly_check.*` legacy action types are pre-v0.1.11 rename rows —
336
+ // keep them in the union so the heartbeat window doesn't falsely report
337
+ // "no recent Stage 3" on the first post-upgrade ticks. Safe to drop once
338
+ // the heartbeat lookback (≤48 h) has fully aged past an upgrade.
339
+ const gateRow = db
340
+ .prepare(`SELECT started_at
341
+ FROM agent_actions
342
+ WHERE action_type IN ('activity_scan.gate', 'hourly_check.gate')
343
+ AND json_extract(detail, '$.stage_reached') = 'stage3'
344
+ ORDER BY started_at DESC
345
+ LIMIT 1`)
346
+ .get();
347
+ let lastStarted = gateRow?.started_at ?? null;
348
+ if (!lastStarted) {
349
+ const fallbackRow = db
350
+ .prepare(`SELECT started_at
351
+ FROM agent_actions
352
+ WHERE action_type IN ('routine.activity_scan', 'routine.hourly_check')
353
+ AND result IN ('success', 'partial', 'failed')
354
+ ORDER BY started_at DESC
355
+ LIMIT 1`)
356
+ .get();
357
+ lastStarted = fallbackRow?.started_at ?? null;
358
+ }
359
+ if (!lastStarted)
360
+ return Number.POSITIVE_INFINITY;
361
+ const lastUtc = parseSqliteUtc(lastStarted);
362
+ if (lastUtc === null)
363
+ return Number.POSITIVE_INFINITY;
364
+ const diffMs = now.getTime() - lastUtc;
365
+ if (diffMs <= 0)
366
+ return 0;
367
+ return diffMs / (60 * 60 * 1000);
368
+ }
369
+ function toSqliteUtc(date) {
370
+ // "YYYY-MM-DD HH:MM:SS" — matches CURRENT_TIMESTAMP / datetime('now').
371
+ return date.toISOString().replace("T", " ").slice(0, 19);
372
+ }
373
+ function parseSqliteUtc(value) {
374
+ // Tolerate both "YYYY-MM-DD HH:MM:SS" and ISO 8601.
375
+ const iso = value.includes("T") ? value : value.replace(" ", "T") + "Z";
376
+ const ts = Date.parse(iso);
377
+ return Number.isFinite(ts) ? ts : null;
378
+ }
@@ -1,5 +1,6 @@
1
1
  import type Database from "better-sqlite3";
2
2
  import type { AgentKind, ScheduleKind, StopWarning } from "@aitne/shared";
3
+ import { type RuntimeWindowOverride } from "../core/agents/activity-scan-cadence.js";
3
4
  /**
4
5
  * Agents store — durable identity layer for the Agent Definitions feature
5
6
  * (AGENT_DEFINITIONS_DESIGN.md §5.1). One `agents` row per built-in routine
@@ -37,6 +38,13 @@ export interface AgentMetadata {
37
38
  last_error?: string;
38
39
  /** Built-in field-level edits that must survive `npm i -g` (§6.4.1). */
39
40
  override_snapshot?: Record<string, unknown>;
41
+ /**
42
+ * Runtime-window cadence overrides for `activity-scan` (interval / active
43
+ * hours / observation threshold) — AGENTS_HUB_REDESIGN_PLAN.md §2. Written
44
+ * by `PATCH /api/agents/:slug` (`schedule_window`), preserved by the loader
45
+ * like `override_snapshot`, resolved by `core/agents/activity-scan-cadence.ts`.
46
+ */
47
+ runtime_window?: Record<string, unknown>;
40
48
  [key: string]: unknown;
41
49
  }
42
50
  /** Raw row as stored in SQLite (snake_case, JSON columns unparsed). */
@@ -170,6 +178,26 @@ export declare function deleteAgent(db: Database.Database, slug: string): boolea
170
178
  * or carries no snapshot, so callers can treat the result uniformly.
171
179
  */
172
180
  export declare function getOverrideSnapshot(db: Database.Database, slug: string): Record<string, unknown>;
181
+ /**
182
+ * Read just the `enabled` flag for a slug, with a caller-supplied fallback for
183
+ * a missing row (pass the registry's `defaultEnabled` for built-ins). Cheap
184
+ * single-column read for fire-time / catch-up gates outside the scheduler's
185
+ * cached `isAgentEnabledForFiring` path.
186
+ */
187
+ export declare function getAgentEnabled(db: Database.Database, slug: string, fallback: boolean): boolean;
188
+ /**
189
+ * Read the sanitized runtime-window override (`metadata_json.runtime_window`)
190
+ * for a slug. Returns `{}` when the Agent is missing or carries no override,
191
+ * so callers can hand the result straight to `resolveActivityScanCadence`.
192
+ */
193
+ export declare function getRuntimeWindow(db: Database.Database, slug: string): RuntimeWindowOverride;
194
+ /**
195
+ * Replace the runtime-window override, preserving every other `metadata_json`
196
+ * key. An empty override removes the `runtime_window` key entirely (all fields
197
+ * back on the config fallback). Returns the updated DTO, or `null` when no row
198
+ * matches.
199
+ */
200
+ export declare function setRuntimeWindow(db: Database.Database, slug: string, window: RuntimeWindowOverride, now?: number): AgentDTO | null;
173
201
  /**
174
202
  * Replace the override snapshot, preserving every other `metadata_json` key.
175
203
  * An empty snapshot removes the `override_snapshot` key entirely (so a fully
@@ -1,3 +1,4 @@
1
+ import { parseRuntimeWindowOverride, } from "../core/agents/activity-scan-cadence.js";
1
2
  // ── JSON parsing (defensive — store is the writer, but never trust bytes) ──
2
3
  function parseTags(json) {
3
4
  try {
@@ -240,6 +241,67 @@ export function getOverrideSnapshot(db, slug) {
240
241
  return {};
241
242
  return parseMetadata(row.metadata_json).override_snapshot ?? {};
242
243
  }
244
+ /**
245
+ * Read just the `enabled` flag for a slug, with a caller-supplied fallback for
246
+ * a missing row (pass the registry's `defaultEnabled` for built-ins). Cheap
247
+ * single-column read for fire-time / catch-up gates outside the scheduler's
248
+ * cached `isAgentEnabledForFiring` path.
249
+ */
250
+ export function getAgentEnabled(db, slug, fallback) {
251
+ try {
252
+ const row = db
253
+ .prepare("SELECT enabled FROM agents WHERE id = ?")
254
+ .get(slug);
255
+ return row ? row.enabled === 1 : fallback;
256
+ }
257
+ catch {
258
+ // Defensive: a DB without the agents table (minimal test fixtures,
259
+ // pre-applySchema boot edge) behaves like a missing row.
260
+ return fallback;
261
+ }
262
+ }
263
+ /**
264
+ * Read the sanitized runtime-window override (`metadata_json.runtime_window`)
265
+ * for a slug. Returns `{}` when the Agent is missing or carries no override,
266
+ * so callers can hand the result straight to `resolveActivityScanCadence`.
267
+ */
268
+ export function getRuntimeWindow(db, slug) {
269
+ try {
270
+ const row = db
271
+ .prepare("SELECT metadata_json FROM agents WHERE id = ?")
272
+ .get(slug);
273
+ if (!row)
274
+ return {};
275
+ return parseRuntimeWindowOverride(parseMetadata(row.metadata_json).runtime_window);
276
+ }
277
+ catch {
278
+ // Defensive: a DB without the agents table (minimal test fixtures,
279
+ // pre-applySchema boot edge) behaves like a missing row.
280
+ return {};
281
+ }
282
+ }
283
+ /**
284
+ * Replace the runtime-window override, preserving every other `metadata_json`
285
+ * key. An empty override removes the `runtime_window` key entirely (all fields
286
+ * back on the config fallback). Returns the updated DTO, or `null` when no row
287
+ * matches.
288
+ */
289
+ export function setRuntimeWindow(db, slug, window, now = Date.now()) {
290
+ const row = db
291
+ .prepare("SELECT metadata_json FROM agents WHERE id = ?")
292
+ .get(slug);
293
+ if (!row)
294
+ return null;
295
+ const metadata = parseMetadata(row.metadata_json);
296
+ if (Object.keys(window).length === 0) {
297
+ delete metadata.runtime_window;
298
+ }
299
+ else {
300
+ metadata.runtime_window = window;
301
+ }
302
+ db.prepare("UPDATE agents SET metadata_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(metadata), now, slug);
303
+ return getAgent(db, slug);
304
+ }
243
305
  /**
244
306
  * Replace the override snapshot, preserving every other `metadata_json` key.
245
307
  * An empty snapshot removes the `override_snapshot` key entirely (so a fully
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Background-task clarifications — BACKGROUND_TASK_RUNNER_DESIGN.md §6.
3
+ *
4
+ * One row per `ask_user` round-trip. The worker writes the row when it
5
+ * calls `ask_user`, the runner transitions the task to `awaiting_user`,
6
+ * and the delivery boundary surfaces the question to the owner (active
7
+ * delivery turn or idle draft send). The owner replies via DM; the DM
8
+ * agent forwards the answer through `POST /api/background-task/:id/clarify`
9
+ * which calls `resolveClarification` here.
10
+ *
11
+ * Deadline enforcement: a daemon housekeeping tick sweeps
12
+ * `WHERE resolved = 0 AND deadline_at < now()` via
13
+ * `listOverdueClarifications` and transitions the parent task to
14
+ * `timeout` (releasing the slot). The TTL is CONFIGURABLE
15
+ * (`backgroundTaskClarificationTtlMinutes`) and longer than
16
+ * browser-task's fixed 5 min — no browser resource is held while parked.
17
+ *
18
+ * I/O-bound. Excluded from the coverage gate.
19
+ */
20
+ import type Database from "better-sqlite3";
21
+ export interface BackgroundTaskClarificationRow {
22
+ id: string;
23
+ taskId: string;
24
+ question: string;
25
+ contextSummary: string | null;
26
+ askedAt: number;
27
+ deadlineAt: number;
28
+ deliveredAt: number | null;
29
+ answer: string | null;
30
+ answeredAt: number | null;
31
+ resolved: boolean;
32
+ }
33
+ export interface CreateClarificationInput {
34
+ id: string;
35
+ taskId: string;
36
+ question: string;
37
+ contextSummary: string | null;
38
+ askedAt: number;
39
+ /** TTL in ms — the deadline is computed here (asked_at + ttlMs) so the
40
+ * runner and the scanner cannot disagree. The caller reads
41
+ * `backgroundTaskClarificationTtlMinutes` and passes ms. */
42
+ ttlMs: number;
43
+ }
44
+ export declare function createClarification(db: Database.Database, input: CreateClarificationInput): BackgroundTaskClarificationRow;
45
+ export declare function getClarification(db: Database.Database, id: string): BackgroundTaskClarificationRow | null;
46
+ export declare function listClarificationsForTask(db: Database.Database, taskId: string): readonly BackgroundTaskClarificationRow[];
47
+ /** The most-recent unresolved clarification for a task — the one a
48
+ * /clarify reply without an explicit id resolves against. */
49
+ export declare function getOpenClarificationForTask(db: Database.Database, taskId: string): BackgroundTaskClarificationRow | null;
50
+ export interface ResolveClarificationResult {
51
+ ok: boolean;
52
+ row: BackgroundTaskClarificationRow | null;
53
+ reason?: "not_found" | "already_resolved" | "expired";
54
+ }
55
+ /** CAS-resolve a clarification with the owner's answer. Refuses if
56
+ * already resolved (idempotent forwarding) or past deadline (the
57
+ * deadline scanner owns the timeout transition). */
58
+ export declare function resolveClarification(db: Database.Database, input: {
59
+ id: string;
60
+ answer: string;
61
+ answeredAt: number;
62
+ }): ResolveClarificationResult;
63
+ /** Sweep target — every unresolved clarification whose deadline has
64
+ * passed. The scanner transitions each parent task to `timeout`. */
65
+ export declare function listOverdueClarifications(db: Database.Database, nowMs: number): readonly BackgroundTaskClarificationRow[];
66
+ /** Mark a clarification resolved without an answer (deadline path). */
67
+ export declare function expireClarification(db: Database.Database, id: string, nowMs: number): BackgroundTaskClarificationRow | null;
68
+ export declare function markClarificationDelivered(db: Database.Database, id: string, deliveredAt: number): BackgroundTaskClarificationRow | null;
69
+ /** A recovery-sweep clarification row enriched with the parent task's
70
+ * delivery fields. The list query already INNER JOINs `background_task`
71
+ * (on `state='awaiting_user'`), so these are folded in here — the sweep
72
+ * needs no second `getBackgroundTask` fetch and carries no unreachable
73
+ * "task missing" guard. */
74
+ export interface UndeliveredClarificationRow extends BackgroundTaskClarificationRow {
75
+ taskOriginatingChannel: string | null;
76
+ taskTitle: string | null;
77
+ taskBrief: string;
78
+ }
79
+ /** Delivery recovery target — undelivered, still-open clarifications
80
+ * whose parent task is parked, joined with that task's delivery fields. */
81
+ export declare function listUndeliveredClarifications(db: Database.Database, nowMs: number, limit?: number): readonly UndeliveredClarificationRow[];