@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,105 @@
1
+ import type Database from "better-sqlite3";
2
+ import type { AgentTaskEvent, TaskDeliveryAsset, TaskDeliveryEvent } from "@aitne/shared";
3
+ import type { AgentConfig } from "../config.js";
4
+ import type { OutboundAttachmentRef } from "../adapters/types.js";
5
+ import type { INotificationManager } from "./dispatcher-types.js";
6
+ export declare const TASK_DELIVERY_GATE_KEYS: readonly ["owner_dm:owner", "dashboard_chat:dashboard"];
7
+ export interface BrowserTaskDeliveryEventInput {
8
+ taskId: string;
9
+ originatingChannel: string | null;
10
+ title: string;
11
+ }
12
+ export declare function createBrowserTaskResultDeliveryEvent(input: BrowserTaskDeliveryEventInput & {
13
+ report: string;
14
+ screenshotKeys?: readonly string[];
15
+ }): TaskDeliveryEvent;
16
+ export declare function createBrowserTaskClarificationDeliveryEvent(input: BrowserTaskDeliveryEventInput & {
17
+ clarificationId: string;
18
+ question: string;
19
+ contextSummary: string | null;
20
+ screenshotKey: string | null;
21
+ }): TaskDeliveryEvent;
22
+ export interface BackgroundTaskResultDeliveryInput {
23
+ taskId: string;
24
+ originatingChannel: string | null;
25
+ title: string;
26
+ /** Worker-authored summary — the idle-send body / active-turn grounding. */
27
+ draft: string;
28
+ /** Verbatim result — injected into the active delivery turn so the DM
29
+ * agent can weave full detail without a tool round-trip. */
30
+ report: string;
31
+ /** Worker-produced deliverable files (PDF / PPTX / PNG / docs) to attach
32
+ * to the result DM. Omit / empty when the task produced no files. */
33
+ assets?: readonly TaskDeliveryAsset[];
34
+ }
35
+ export declare function createBackgroundTaskResultDeliveryEvent(input: BackgroundTaskResultDeliveryInput): TaskDeliveryEvent;
36
+ export declare function createBackgroundTaskClarificationDeliveryEvent(input: {
37
+ taskId: string;
38
+ originatingChannel: string | null;
39
+ title: string;
40
+ clarificationId: string;
41
+ question: string;
42
+ contextSummary: string | null;
43
+ }): TaskDeliveryEvent;
44
+ /**
45
+ * BACKGROUND_TASK_RUNNER_DESIGN.md §2.3 / §13 Decision 4 (Phase 4, opt-in)
46
+ * — wrap a routine autonomous forward as a `task.delivery` event so it
47
+ * flows through the same gate + activity-branch machinery: an active owner
48
+ * gets a woven delivery turn, an idle owner the verbatim send + record.
49
+ * Carries no DB row (synthetic id, no `delivered_at` recovery — autonomous
50
+ * forwards are fire-and-forget, exactly as the verbatim path is today).
51
+ */
52
+ export declare function createAutonomousForwardDeliveryEvent(input: {
53
+ content: string;
54
+ originatingChannel: string | null;
55
+ title?: string;
56
+ correlationId?: string | null;
57
+ }): TaskDeliveryEvent;
58
+ export interface TaskDeliveryHandlerDeps {
59
+ db: Database.Database;
60
+ config: AgentConfig;
61
+ notificationMgr: INotificationManager;
62
+ executeScheduledTask(event: AgentTaskEvent): Promise<void>;
63
+ /**
64
+ * Resolve a task's deliverable assets (browser-task trace screenshots
65
+ * and/or worker-written files: PDF / PPTX / PNG / docs) to outbound
66
+ * attachments for the platform the DM lands on. Used by BOTH the idle
67
+ * direct-send and the active delivery turn so the owner receives the
68
+ * actual files inline (BACKGROUND_TASK_RUNNER_DESIGN.md Phase 1 — delivery
69
+ * assets). Injected from bootstrap (closure over `paDataDir` + the
70
+ * dashboard ingest hook); absent in unit tests, where assets are simply
71
+ * omitted. Best-effort — must resolve to `[]` rather than throw.
72
+ */
73
+ resolveAssets?(platform: string, assets: readonly TaskDeliveryAsset[]): Promise<readonly OutboundAttachmentRef[]>;
74
+ nowFn?: () => number;
75
+ }
76
+ /** The asset key the dispatch arm puts resolved refs under on the
77
+ * synthetic `scheduled.dm` event so the result processor can attach them
78
+ * to the woven reply. */
79
+ export declare const TASK_DELIVERY_ATTACHMENTS_KEY = "task_delivery_attachments";
80
+ export declare function handleTaskDeliveryInsideGate(deps: TaskDeliveryHandlerDeps, event: TaskDeliveryEvent): Promise<void>;
81
+ export declare function enqueueUndeliveredBrowserTaskDeliveries(params: {
82
+ db: Database.Database;
83
+ eventBus: {
84
+ put(event: TaskDeliveryEvent): Promise<void>;
85
+ };
86
+ nowMs?: number;
87
+ limit?: number;
88
+ }): Promise<number>;
89
+ /**
90
+ * BACKGROUND_TASK_RUNNER_DESIGN.md §10.2 — delivery recovery sweep for
91
+ * background tasks. Re-enqueues `task.delivery` events for completed
92
+ * notify=true artifacts whose DM was never sent/recorded
93
+ * (`delivered_at IS NULL`) plus undelivered open clarifications. Run on
94
+ * the housekeeping tick (boot + periodic). Idempotent against the
95
+ * in-gate message-existence dedup — a re-enqueue after a successful send
96
+ * only back-fills `delivered_at`, never double-sends (§4.4).
97
+ */
98
+ export declare function enqueueUndeliveredBackgroundTaskDeliveries(params: {
99
+ db: Database.Database;
100
+ eventBus: {
101
+ put(event: TaskDeliveryEvent): Promise<void>;
102
+ };
103
+ nowMs?: number;
104
+ limit?: number;
105
+ }): Promise<number>;
@@ -0,0 +1,555 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { basename } from "node:path";
3
+ import { EventPriority, createEvent, } from "@aitne/shared";
4
+ import { getBrowserTask, listUndeliveredBrowserTaskReports, markBrowserTaskDelivered, } from "../db/browser-task-store.js";
5
+ import { getClarification, listUndeliveredClarifications, markClarificationDelivered, } from "../db/browser-task-clarifications-store.js";
6
+ import { getBackgroundTask, listUndeliveredBackgroundTaskReports, markBackgroundTaskDelivered, } from "../db/background-task-store.js";
7
+ import { getClarification as getBackgroundClarification, listUndeliveredClarifications as listUndeliveredBackgroundClarifications, markClarificationDelivered as markBackgroundClarificationDelivered, } from "../db/background-task-clarifications-store.js";
8
+ import { parseChannelRef } from "../db/browser-automation-purchase-primary-channels-store.js";
9
+ import { isInQuietHoursAt } from "./quiet-hours.js";
10
+ import { DASHBOARD_CHAT_SCOPE, DASHBOARD_SCOPE_KEY, OWNER_DM_SCOPE, OWNER_SCOPE_KEY, } from "../messaging/constants.js";
11
+ import { classifyOwnerDmActivity, } from "./context-builder-conversation.js";
12
+ import { recordProactiveForwardDeliveries, } from "./channel-timeline.js";
13
+ import { createLogger } from "../logging.js";
14
+ const logger = createLogger("dispatcher-task-delivery");
15
+ export const TASK_DELIVERY_GATE_KEYS = [
16
+ `${OWNER_DM_SCOPE}:${OWNER_SCOPE_KEY}`,
17
+ `${DASHBOARD_CHAT_SCOPE}:${DASHBOARD_SCOPE_KEY}`,
18
+ ];
19
+ const REPORT_DRAFT_CAP = 3_800;
20
+ export function createBrowserTaskResultDeliveryEvent(input) {
21
+ const draft = input.report.length > REPORT_DRAFT_CAP
22
+ ? `${input.report.slice(0, REPORT_DRAFT_CAP)}\n\n[... truncated; open the browser-task detail page for the full report]`
23
+ : input.report;
24
+ return Object.assign(createEvent({
25
+ type: "task.delivery",
26
+ source: "browser_task",
27
+ priority: EventPriority.HIGH,
28
+ data: {},
29
+ }), {
30
+ taskContext: {
31
+ taskKind: "browser_task",
32
+ taskId: input.taskId,
33
+ deliveryType: "task_result",
34
+ title: input.title,
35
+ draft,
36
+ report: input.report,
37
+ originatingChannel: input.originatingChannel,
38
+ screenshotKeys: [...(input.screenshotKeys ?? [])],
39
+ },
40
+ });
41
+ }
42
+ export function createBrowserTaskClarificationDeliveryEvent(input) {
43
+ const draft = [
44
+ `Browser task "${input.title}" needs your input.`,
45
+ `Question: ${input.question}`,
46
+ input.contextSummary ? `Context: ${input.contextSummary}` : null,
47
+ "Reply here with the answer; I will pass it back to the task.",
48
+ ]
49
+ .filter((line) => line !== null && line.length > 0)
50
+ .join("\n");
51
+ return Object.assign(createEvent({
52
+ type: "task.delivery",
53
+ source: "browser_task",
54
+ priority: EventPriority.HIGH,
55
+ data: {},
56
+ }), {
57
+ taskContext: {
58
+ taskKind: "browser_task",
59
+ taskId: input.taskId,
60
+ deliveryType: "task_clarification",
61
+ title: input.title,
62
+ draft,
63
+ report: input.question,
64
+ originatingChannel: input.originatingChannel,
65
+ clarificationId: input.clarificationId,
66
+ contextSummary: input.contextSummary,
67
+ screenshotKeys: input.screenshotKey ? [input.screenshotKey] : [],
68
+ },
69
+ });
70
+ }
71
+ export function createBackgroundTaskResultDeliveryEvent(input) {
72
+ return Object.assign(createEvent({
73
+ type: "task.delivery",
74
+ source: "background_task",
75
+ priority: EventPriority.HIGH,
76
+ data: {},
77
+ }), {
78
+ taskContext: {
79
+ taskKind: "background_task",
80
+ taskId: input.taskId,
81
+ deliveryType: "task_result",
82
+ title: input.title,
83
+ // The worker-authored `draft` IS the natural-language summary;
84
+ // unlike browser_task (which truncates the raw report), the
85
+ // background worker already produced a clean draft + a separate
86
+ // verbatim report. Idle sends the draft; the active turn reads
87
+ // the full report (injected below) and weaves.
88
+ draft: input.draft,
89
+ report: input.report,
90
+ originatingChannel: input.originatingChannel,
91
+ screenshotKeys: [],
92
+ assets: [...(input.assets ?? [])],
93
+ },
94
+ });
95
+ }
96
+ export function createBackgroundTaskClarificationDeliveryEvent(input) {
97
+ const draft = [
98
+ `The background task "${input.title}" needs your input.`,
99
+ `Question: ${input.question}`,
100
+ input.contextSummary ? `Context: ${input.contextSummary}` : null,
101
+ "Reply here with the answer; I will pass it back to the task.",
102
+ ]
103
+ .filter((line) => line !== null && line.length > 0)
104
+ .join("\n");
105
+ return Object.assign(createEvent({
106
+ type: "task.delivery",
107
+ source: "background_task",
108
+ priority: EventPriority.HIGH,
109
+ data: {},
110
+ }), {
111
+ taskContext: {
112
+ taskKind: "background_task",
113
+ taskId: input.taskId,
114
+ deliveryType: "task_clarification",
115
+ title: input.title,
116
+ draft,
117
+ report: input.question,
118
+ originatingChannel: input.originatingChannel,
119
+ clarificationId: input.clarificationId,
120
+ contextSummary: input.contextSummary,
121
+ screenshotKeys: [],
122
+ },
123
+ });
124
+ }
125
+ /**
126
+ * BACKGROUND_TASK_RUNNER_DESIGN.md §2.3 / §13 Decision 4 (Phase 4, opt-in)
127
+ * — wrap a routine autonomous forward as a `task.delivery` event so it
128
+ * flows through the same gate + activity-branch machinery: an active owner
129
+ * gets a woven delivery turn, an idle owner the verbatim send + record.
130
+ * Carries no DB row (synthetic id, no `delivered_at` recovery — autonomous
131
+ * forwards are fire-and-forget, exactly as the verbatim path is today).
132
+ */
133
+ export function createAutonomousForwardDeliveryEvent(input) {
134
+ return Object.assign(createEvent({
135
+ type: "task.delivery",
136
+ source: "autonomous_forward",
137
+ priority: EventPriority.HIGH,
138
+ ...(input.correlationId ? { correlationId: input.correlationId } : {}),
139
+ data: {},
140
+ }), {
141
+ taskContext: {
142
+ taskKind: "autonomous_forward",
143
+ taskId: randomUUID(),
144
+ deliveryType: "task_result",
145
+ title: input.title ?? "update",
146
+ // The forward content is both the idle-send body and the active
147
+ // turn's grounding — there is no separate verbatim/summary split.
148
+ draft: input.content,
149
+ report: input.content,
150
+ originatingChannel: input.originatingChannel,
151
+ screenshotKeys: [],
152
+ },
153
+ });
154
+ }
155
+ /** The asset key the dispatch arm puts resolved refs under on the
156
+ * synthetic `scheduled.dm` event so the result processor can attach them
157
+ * to the woven reply. */
158
+ export const TASK_DELIVERY_ATTACHMENTS_KEY = "task_delivery_attachments";
159
+ /**
160
+ * The canonical deliverable-asset list for a payload. Prefers an explicit
161
+ * `assets` manifest (background-task workers populate it); falls back to
162
+ * folding legacy browser-task `screenshotKeys` into screenshot assets so
163
+ * the Phase-1 source keeps working unchanged. Empty ⇒ nothing to present.
164
+ */
165
+ function effectiveAssets(payload) {
166
+ if (payload.assets && payload.assets.length > 0) {
167
+ return payload.assets;
168
+ }
169
+ return (payload.screenshotKeys ?? []).map((key) => ({
170
+ filename: basename(key),
171
+ kind: "screenshot",
172
+ screenshotKey: key,
173
+ }));
174
+ }
175
+ /**
176
+ * The agent-facing slice of the asset list — filename / kind / label only.
177
+ * Internal locations (screenshotKey, absolute path) are deliberately
178
+ * withheld from the LLM context: the daemon attaches the bytes, and the
179
+ * agent references assets by name/kind. Detail/paths for a later re-send
180
+ * come from the artifact API, not the conversation.
181
+ */
182
+ function assetManifest(assets) {
183
+ return assets.map((a) => ({
184
+ filename: a.filename,
185
+ kind: a.kind,
186
+ ...(a.label ? { label: a.label } : {}),
187
+ }));
188
+ }
189
+ async function resolveDeliveryAttachments(deps, platform, payload) {
190
+ const assets = effectiveAssets(payload);
191
+ if (assets.length === 0 || !deps.resolveAssets)
192
+ return [];
193
+ try {
194
+ return await deps.resolveAssets(platform, assets);
195
+ }
196
+ catch (err) {
197
+ // Assets are a best-effort enrichment of the text draft — a resolver
198
+ // failure must never block the result/clarification DM itself.
199
+ logger.warn({ err, taskId: payload.taskId, deliveryType: payload.deliveryType }, "task.delivery asset resolution failed; sending text only");
200
+ return [];
201
+ }
202
+ }
203
+ export async function handleTaskDeliveryInsideGate(deps, event) {
204
+ const payload = event.taskContext;
205
+ if (payload.taskKind !== "browser_task"
206
+ && payload.taskKind !== "background_task"
207
+ && payload.taskKind !== "autonomous_forward") {
208
+ logger.warn({ taskKind: payload.taskKind, taskId: payload.taskId }, "task.delivery skipped: unsupported task kind");
209
+ return;
210
+ }
211
+ const now = deps.nowFn?.() ?? Date.now();
212
+ // `autonomous_forward` carries no DB row, so the row-keyed dedup /
213
+ // delivered_at checks below do not apply — it is fire-and-forget (the
214
+ // verbatim path it replaces had no recovery either). Browser/background
215
+ // task deliveries keep the full idempotency contract.
216
+ if (payload.taskKind !== "autonomous_forward") {
217
+ if (await backfillDeliveredAtIfMessageExists(deps.db, payload, now)) {
218
+ return;
219
+ }
220
+ if (isDeliveryAlreadyMarked(deps.db, payload)) {
221
+ return;
222
+ }
223
+ }
224
+ // No deliverable channel ⇒ the report/clarification is still filed on
225
+ // the row, but there is nowhere to DM it (`browser_task.originating_channel`
226
+ // is nullable — e.g. synthetic / channel-less runs). Mark delivered so
227
+ // the boot/periodic recovery sweep (which selects `delivered_at IS NULL`)
228
+ // does not re-enqueue this task on every 30 s tick, and so the active
229
+ // branch never spends a full LLM turn on something it cannot deliver.
230
+ // Matches the pre-Phase-1 notifier's "skip once" behaviour, minus the
231
+ // churn. Logged as a degrade per the project's "no silent drops" posture.
232
+ const channel = resolveDeliveryChannel(payload);
233
+ if (!channel) {
234
+ logger.warn({
235
+ taskId: payload.taskId,
236
+ deliveryType: payload.deliveryType,
237
+ originatingChannel: payload.originatingChannel,
238
+ }, "task.delivery dropped: no parseable originating channel; report "
239
+ + "filed on the row, marking delivered to stop recovery churn");
240
+ markDeliveredAt(deps.db, payload, now);
241
+ return;
242
+ }
243
+ const activity = classifyOwnerDmActivity({ db: deps.db, config: deps.config }, now);
244
+ if (activity === "active") {
245
+ await deliverActive(deps, event, activity, channel);
246
+ return;
247
+ }
248
+ // Idle/asleep + quiet hours ⇒ defer. The design (§4.5 / §10.6) routes
249
+ // the idle branch through the proactive quiet-hours suppression so a
250
+ // task finishing at 03:00 does not ping a sleeping owner. The idle send
251
+ // uses `replyTo` (to hit the originating channel), which bypasses
252
+ // NotificationManager's own quiet-hours gate — so we gate here instead.
253
+ // Leaving `delivered_at` NULL lets the boot/periodic recovery sweep
254
+ // re-deliver once the window lifts (and re-classify: if the owner is
255
+ // active by then, it weaves into the live thread instead).
256
+ if (isQuietHoursNow(deps.config, now)) {
257
+ logger.info({
258
+ taskId: payload.taskId,
259
+ deliveryType: payload.deliveryType,
260
+ }, "task.delivery idle send deferred — quiet hours; recovery sweep will "
261
+ + "re-deliver after the window");
262
+ return;
263
+ }
264
+ await deliverIdle(deps, event, channel);
265
+ }
266
+ function isQuietHoursNow(config, nowMs) {
267
+ return isInQuietHoursAt(new Date(nowMs), {
268
+ start: config.quietHoursStart,
269
+ end: config.quietHoursEnd,
270
+ timezone: config.timezone || undefined,
271
+ });
272
+ }
273
+ async function deliverActive(deps, event, activity, channel) {
274
+ const payload = event.taskContext;
275
+ const attachments = await resolveDeliveryAttachments(deps, channel.platform, payload);
276
+ const scheduledEvent = createScheduledDmDeliveryEvent(event, activity, attachments, channel);
277
+ try {
278
+ await deps.executeScheduledTask(scheduledEvent);
279
+ }
280
+ catch (err) {
281
+ logger.warn({ err, taskId: payload.taskId, deliveryType: payload.deliveryType }, "task.delivery active turn failed; falling back to direct draft");
282
+ // Reuse the already-resolved attachments — re-resolving would
283
+ // re-ingest dashboard files and could diverge from what the active
284
+ // turn was given.
285
+ await deliverIdle(deps, event, channel, attachments);
286
+ return;
287
+ }
288
+ const now = deps.nowFn?.() ?? Date.now();
289
+ if (await backfillDeliveredAtIfMessageExists(deps.db, payload, now)) {
290
+ return;
291
+ }
292
+ logger.warn({ taskId: payload.taskId, deliveryType: payload.deliveryType }, "task.delivery active turn produced no tagged message; falling back to direct draft");
293
+ await deliverIdle(deps, event, channel, attachments);
294
+ }
295
+ async function deliverIdle(deps, event, channel, preResolvedAttachments) {
296
+ const payload = event.taskContext;
297
+ const attachments = preResolvedAttachments
298
+ ?? (await resolveDeliveryAttachments(deps, channel.platform, payload));
299
+ await deps.notificationMgr.send(payload.draft, event, {
300
+ priority: "normal",
301
+ category: "agent",
302
+ replyTo: {
303
+ platform: channel.platform,
304
+ channel: channel.channelId,
305
+ threadId: null,
306
+ },
307
+ ...(attachments.length > 0 ? { attachments } : {}),
308
+ });
309
+ const dispatchId = randomUUID();
310
+ const now = deps.nowFn?.() ?? Date.now();
311
+ const recordAndMark = deps.db.transaction(() => {
312
+ recordProactiveForwardDeliveries({
313
+ db: deps.db,
314
+ config: deps.config,
315
+ deliveries: [
316
+ {
317
+ platform: channel.platform,
318
+ channel: channel.channelId,
319
+ },
320
+ ],
321
+ content: payload.draft,
322
+ dispatchId,
323
+ dispatchIds: [dispatchId],
324
+ notificationType: notificationTypeFor(payload),
325
+ extraMetadata: taskDeliveryMetadata(payload, false),
326
+ });
327
+ markDeliveredAt(deps.db, payload, now);
328
+ });
329
+ recordAndMark();
330
+ }
331
+ function createScheduledDmDeliveryEvent(event, activity, attachments, channel) {
332
+ const payload = event.taskContext;
333
+ const manifest = assetManifest(effectiveAssets(payload));
334
+ return Object.assign(createEvent({
335
+ type: "scheduled.dm",
336
+ source: "task.delivery",
337
+ priority: EventPriority.HIGH,
338
+ correlationId: event.correlationId,
339
+ data: {
340
+ reply_target: {
341
+ platform: channel.platform,
342
+ channel: channel.channelId,
343
+ threadId: null,
344
+ // Audit-only label (not consumed for routing) — use the actual task
345
+ // kind so background_task / autonomous_forward weaves aren't all
346
+ // mislabelled as browser_task in telemetry.
347
+ sender: payload.taskKind,
348
+ },
349
+ task_delivery_record: {
350
+ notificationType: notificationTypeFor(payload),
351
+ metadata: taskDeliveryMetadata(payload, true),
352
+ },
353
+ // Resolved deliverable files — the result processor attaches these to
354
+ // the woven reply so the owner receives them inline (the active turn
355
+ // is a no-tool DM turn and cannot self-attach). Empty ⇒ omitted.
356
+ ...(attachments.length > 0
357
+ ? { [TASK_DELIVERY_ATTACHMENTS_KEY]: [...attachments] }
358
+ : {}),
359
+ },
360
+ }), {
361
+ task: `task delivery: ${payload.title}`,
362
+ taskContext: {
363
+ task_delivery: {
364
+ taskKind: payload.taskKind,
365
+ taskId: payload.taskId,
366
+ deliveryType: payload.deliveryType,
367
+ title: payload.title,
368
+ draft: payload.draft,
369
+ report: payload.report ?? payload.draft,
370
+ clarificationId: payload.clarificationId ?? null,
371
+ contextSummary: payload.contextSummary ?? null,
372
+ // Agent-facing asset manifest (filename/kind/label only). Present
373
+ // only when the task produced deliverables — the skill references
374
+ // them, and the daemon attaches the bytes. Empty ⇒ no asset block.
375
+ assets: manifest,
376
+ activity,
377
+ },
378
+ },
379
+ });
380
+ }
381
+ function resolveDeliveryChannel(payload) {
382
+ return payload.originatingChannel
383
+ ? parseChannelRef(payload.originatingChannel)
384
+ : null;
385
+ }
386
+ function notificationTypeFor(payload) {
387
+ if (payload.taskKind === "autonomous_forward")
388
+ return "proactive_forward";
389
+ return payload.deliveryType === "task_clarification"
390
+ ? "task_clarification"
391
+ : "task_result";
392
+ }
393
+ function taskDeliveryMetadata(payload, activeTurn) {
394
+ return {
395
+ taskKind: payload.taskKind,
396
+ taskId: payload.taskId,
397
+ ...(activeTurn ? { deliveredTaskId: payload.taskId } : {}),
398
+ deliveryType: payload.deliveryType,
399
+ ...(payload.clarificationId
400
+ ? { clarificationId: payload.clarificationId }
401
+ : {}),
402
+ };
403
+ }
404
+ function isDeliveryAlreadyMarked(db, payload) {
405
+ if (payload.deliveryType === "task_result") {
406
+ const deliveredAt = payload.taskKind === "background_task"
407
+ ? (getBackgroundTask(db, payload.taskId)?.deliveredAt ?? null)
408
+ : (getBrowserTask(db, payload.taskId)?.deliveredAt ?? null);
409
+ return deliveredAt !== null;
410
+ }
411
+ if (!payload.clarificationId)
412
+ return false;
413
+ const clarDeliveredAt = payload.taskKind === "background_task"
414
+ ? (getBackgroundClarification(db, payload.clarificationId)?.deliveredAt ?? null)
415
+ : (getClarification(db, payload.clarificationId)?.deliveredAt ?? null);
416
+ return clarDeliveredAt !== null;
417
+ }
418
+ async function backfillDeliveredAtIfMessageExists(db, payload, nowMs) {
419
+ if (!hasExistingTaskDeliveryMessage(db, payload))
420
+ return false;
421
+ markDeliveredAt(db, payload, nowMs);
422
+ return true;
423
+ }
424
+ function hasExistingTaskDeliveryMessage(db, payload) {
425
+ const params = [
426
+ notificationTypeFor(payload),
427
+ payload.taskKind,
428
+ payload.taskId,
429
+ payload.taskId,
430
+ ];
431
+ const clarificationClause = payload.clarificationId
432
+ ? "AND json_extract(m.metadata, '$.clarificationId') = ?"
433
+ : "";
434
+ if (payload.clarificationId) {
435
+ params.push(payload.clarificationId);
436
+ }
437
+ const row = db
438
+ .prepare(`SELECT 1 AS found
439
+ FROM messages m
440
+ WHERE m.role = 'assistant'
441
+ AND json_extract(m.metadata, '$.notificationType') = ?
442
+ AND json_extract(m.metadata, '$.taskKind') = ?
443
+ AND (
444
+ json_extract(m.metadata, '$.taskId') = ?
445
+ OR json_extract(m.metadata, '$.deliveredTaskId') = ?
446
+ )
447
+ ${clarificationClause}
448
+ LIMIT 1`)
449
+ .get(...params);
450
+ return row !== undefined;
451
+ }
452
+ function markDeliveredAt(db, payload, nowMs) {
453
+ // `autonomous_forward` is fire-and-forget with no backing task row
454
+ // (its `taskId` is a synthetic UUID). There is nothing to stamp
455
+ // `delivered_at` on — writing to `browser_task` here would be an
456
+ // UPDATE against the wrong table that matches zero rows.
457
+ if (payload.taskKind === "autonomous_forward")
458
+ return;
459
+ if (payload.deliveryType === "task_result") {
460
+ if (payload.taskKind === "background_task") {
461
+ markBackgroundTaskDelivered(db, payload.taskId, nowMs);
462
+ }
463
+ else {
464
+ markBrowserTaskDelivered(db, payload.taskId, nowMs);
465
+ }
466
+ return;
467
+ }
468
+ if (payload.clarificationId) {
469
+ if (payload.taskKind === "background_task") {
470
+ markBackgroundClarificationDelivered(db, payload.clarificationId, nowMs);
471
+ }
472
+ else {
473
+ markClarificationDelivered(db, payload.clarificationId, nowMs);
474
+ }
475
+ }
476
+ }
477
+ export async function enqueueUndeliveredBrowserTaskDeliveries(params) {
478
+ const nowMs = params.nowMs ?? Date.now();
479
+ const limit = params.limit ?? 20;
480
+ let enqueued = 0;
481
+ // Recovery is text-only for reports: the `finish`-tool screenshot keys
482
+ // are not persisted on the `browser_task` row, so a report re-delivered
483
+ // after a crash cannot re-attach its screenshots (clarifications can —
484
+ // their `screenshotKey` IS persisted, see below). Acceptable: recovery
485
+ // only fires on the rare write-then-crash-before-DM window, and the text
486
+ // report still reaches the owner. (Open clarifications keep their image.)
487
+ for (const row of listUndeliveredBrowserTaskReports(params.db, limit)) {
488
+ if (!row.report)
489
+ continue;
490
+ await params.eventBus.put(createBrowserTaskResultDeliveryEvent({
491
+ taskId: row.id,
492
+ originatingChannel: row.originatingChannel,
493
+ title: row.description,
494
+ report: row.report,
495
+ }));
496
+ enqueued += 1;
497
+ }
498
+ // The list query INNER JOINs `browser_task` (state='awaiting_user') and
499
+ // folds in the task's channel + description, so no second fetch — and no
500
+ // unreachable "task missing" guard — is needed.
501
+ for (const clarification of listUndeliveredClarifications(params.db, nowMs, limit)) {
502
+ await params.eventBus.put(createBrowserTaskClarificationDeliveryEvent({
503
+ taskId: clarification.taskId,
504
+ originatingChannel: clarification.taskOriginatingChannel,
505
+ title: clarification.taskDescription,
506
+ clarificationId: clarification.id,
507
+ question: clarification.question,
508
+ contextSummary: clarification.contextSummary,
509
+ screenshotKey: clarification.screenshotKey,
510
+ }));
511
+ enqueued += 1;
512
+ }
513
+ return enqueued;
514
+ }
515
+ /**
516
+ * BACKGROUND_TASK_RUNNER_DESIGN.md §10.2 — delivery recovery sweep for
517
+ * background tasks. Re-enqueues `task.delivery` events for completed
518
+ * notify=true artifacts whose DM was never sent/recorded
519
+ * (`delivered_at IS NULL`) plus undelivered open clarifications. Run on
520
+ * the housekeeping tick (boot + periodic). Idempotent against the
521
+ * in-gate message-existence dedup — a re-enqueue after a successful send
522
+ * only back-fills `delivered_at`, never double-sends (§4.4).
523
+ */
524
+ export async function enqueueUndeliveredBackgroundTaskDeliveries(params) {
525
+ const nowMs = params.nowMs ?? Date.now();
526
+ const limit = params.limit ?? 20;
527
+ let enqueued = 0;
528
+ for (const row of listUndeliveredBackgroundTaskReports(params.db, limit)) {
529
+ if (!row.draft)
530
+ continue;
531
+ await params.eventBus.put(createBackgroundTaskResultDeliveryEvent({
532
+ taskId: row.id,
533
+ originatingChannel: row.originatingChannel,
534
+ title: row.title ?? row.brief.slice(0, 80),
535
+ draft: row.draft,
536
+ report: row.report ?? row.draft,
537
+ }));
538
+ enqueued += 1;
539
+ }
540
+ // As above, the clarification list JOINs `background_task` and folds in
541
+ // the task's channel + title/brief, so the sweep needs no re-fetch and no
542
+ // unreachable "task missing" guard.
543
+ for (const clarification of listUndeliveredBackgroundClarifications(params.db, nowMs, limit)) {
544
+ await params.eventBus.put(createBackgroundTaskClarificationDeliveryEvent({
545
+ taskId: clarification.taskId,
546
+ originatingChannel: clarification.taskOriginatingChannel,
547
+ title: clarification.taskTitle ?? clarification.taskBrief.slice(0, 80),
548
+ clarificationId: clarification.id,
549
+ question: clarification.question,
550
+ contextSummary: clarification.contextSummary,
551
+ }));
552
+ enqueued += 1;
553
+ }
554
+ return enqueued;
555
+ }