@aitne/daemon 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/dist/adapters/adapter-watchdog.d.ts +70 -0
  2. package/dist/adapters/adapter-watchdog.js +115 -0
  3. package/dist/adapters/discord.d.ts +17 -1
  4. package/dist/adapters/discord.js +33 -0
  5. package/dist/adapters/notification-manager.d.ts +27 -1
  6. package/dist/adapters/notification-manager.js +54 -39
  7. package/dist/adapters/slack-adapter.d.ts +26 -1
  8. package/dist/adapters/slack-adapter.js +41 -0
  9. package/dist/adapters/telegram-adapter.d.ts +18 -1
  10. package/dist/adapters/telegram-adapter.js +41 -2
  11. package/dist/adapters/types.d.ts +20 -0
  12. package/dist/adapters/whatsapp-adapter.d.ts +26 -7
  13. package/dist/adapters/whatsapp-adapter.js +74 -21
  14. package/dist/api/env-writer.js +8 -5
  15. package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
  16. package/dist/api/helpers/agent-errors-registry.js +5 -5
  17. package/dist/api/routes/agent.js +33 -12
  18. package/dist/api/routes/agents/index.js +75 -16
  19. package/dist/api/routes/agents/views.d.ts +37 -2
  20. package/dist/api/routes/agents/views.js +64 -2
  21. package/dist/api/routes/background-task.d.ts +22 -0
  22. package/dist/api/routes/background-task.js +338 -0
  23. package/dist/api/routes/browser-history.js +9 -1
  24. package/dist/api/routes/context/permissions.js +3 -2
  25. package/dist/api/routes/context/snapshots.js +0 -3
  26. package/dist/api/routes/context/write.js +3 -17
  27. package/dist/api/routes/dashboard/config.js +48 -12
  28. package/dist/api/routes/dashboard/cost-approvals.js +66 -0
  29. package/dist/api/routes/dashboard/notifications.js +9 -9
  30. package/dist/api/routes/integrations/crud-patch.js +5 -1
  31. package/dist/api/routes/integrations-reconcile.js +2 -2
  32. package/dist/api/routes/notion.d.ts +1 -1
  33. package/dist/api/routes/observations.js +7 -7
  34. package/dist/api/routes/obsidian.d.ts +1 -1
  35. package/dist/api/routes/receipts.js +5 -1
  36. package/dist/api/routes/setup-migrate.js +1 -1
  37. package/dist/api/routes/setup.js +1 -1
  38. package/dist/api/routes/task-flows.d.ts +1 -1
  39. package/dist/api/routes/task-flows.js +1 -1
  40. package/dist/api/routes/tuning.d.ts +29 -0
  41. package/dist/api/routes/tuning.js +304 -0
  42. package/dist/api/server.d.ts +44 -16
  43. package/dist/api/server.js +9 -0
  44. package/dist/bootstrap/adapters.d.ts +19 -0
  45. package/dist/bootstrap/adapters.js +61 -0
  46. package/dist/bootstrap/api.d.ts +5 -3
  47. package/dist/bootstrap/api.js +45 -13
  48. package/dist/bootstrap/catchup.d.ts +1 -1
  49. package/dist/bootstrap/catchup.js +11 -11
  50. package/dist/bootstrap/event-pipeline.d.ts +11 -0
  51. package/dist/bootstrap/event-pipeline.js +245 -7
  52. package/dist/bootstrap/observers.js +9 -6
  53. package/dist/bootstrap/schedule-helpers.d.ts +104 -6
  54. package/dist/bootstrap/schedule-helpers.js +172 -19
  55. package/dist/config.js +26 -12
  56. package/dist/core/agent-core.d.ts +33 -1
  57. package/dist/core/agent-core.js +36 -1
  58. package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
  59. package/dist/core/agents/activity-scan-cadence.js +127 -0
  60. package/dist/core/agents/agent-route-override.d.ts +53 -0
  61. package/dist/core/agents/agent-route-override.js +69 -0
  62. package/dist/core/agents/builtin-registry.d.ts +51 -14
  63. package/dist/core/agents/builtin-registry.js +92 -15
  64. package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
  65. package/dist/core/agents/config-gate-reconcile.js +51 -0
  66. package/dist/core/agents/cron-substitute.d.ts +1 -1
  67. package/dist/core/agents/cron-substitute.js +1 -1
  68. package/dist/core/agents/custom-routine-migration.d.ts +60 -0
  69. package/dist/core/agents/custom-routine-migration.js +149 -0
  70. package/dist/core/agents/firing-blocked.d.ts +1 -1
  71. package/dist/core/agents/hourly-cadence.d.ts +102 -0
  72. package/dist/core/agents/hourly-cadence.js +126 -0
  73. package/dist/core/agents/loader-boot.js +23 -0
  74. package/dist/core/agents/loader.d.ts +19 -0
  75. package/dist/core/agents/loader.js +34 -2
  76. package/dist/core/agents/override-merge.d.ts +1 -1
  77. package/dist/core/agents/override-merge.js +9 -1
  78. package/dist/core/agents/recurrence-convert.d.ts +1 -1
  79. package/dist/core/agents/recurrence-convert.js +1 -1
  80. package/dist/core/agents/recurring-schedule-adapter.js +8 -0
  81. package/dist/core/alerts.js +6 -6
  82. package/dist/core/backends/auth-health-monitor.d.ts +2 -2
  83. package/dist/core/backends/auth-health-monitor.js +1 -1
  84. package/dist/core/backends/backend-router.d.ts +27 -1
  85. package/dist/core/backends/backend-router.js +165 -1
  86. package/dist/core/backends/claude-code-core.d.ts +71 -31
  87. package/dist/core/backends/claude-code-core.js +282 -54
  88. package/dist/core/backends/cli-quota-guards.d.ts +29 -1
  89. package/dist/core/backends/cli-quota-guards.js +40 -5
  90. package/dist/core/backends/codex-core.d.ts +6 -0
  91. package/dist/core/backends/codex-core.js +22 -6
  92. package/dist/core/backends/failure-spend.d.ts +58 -0
  93. package/dist/core/backends/failure-spend.js +137 -0
  94. package/dist/core/backends/gemini-cli-core.d.ts +6 -0
  95. package/dist/core/backends/gemini-cli-core.js +25 -6
  96. package/dist/core/backends/model-registry.d.ts +1 -1
  97. package/dist/core/backends/model-registry.js +4 -4
  98. package/dist/core/backends/opencode-core.d.ts +1 -1
  99. package/dist/core/backends/opencode-core.js +5 -5
  100. package/dist/core/backends/plan-presets.js +39 -15
  101. package/dist/core/bang-commands/commands-cost.js +3 -1
  102. package/dist/core/bang-commands/commands-report.js +4 -3
  103. package/dist/core/bang-commands/commands-research.js +4 -1
  104. package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
  105. package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
  106. package/dist/core/bang-commands/commands-stop-start.js +3 -3
  107. package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
  108. package/dist/core/bang-commands/commands-task-control.js +147 -0
  109. package/dist/core/bang-commands/commands-wiki.js +5 -5
  110. package/dist/core/bang-commands/index.d.ts +2 -0
  111. package/dist/core/bang-commands/index.js +12 -0
  112. package/dist/core/bang-commands/registry.d.ts +12 -0
  113. package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
  114. package/dist/core/browser-history/research-cluster-fanout.js +39 -16
  115. package/dist/core/channel-timeline.d.ts +5 -1
  116. package/dist/core/channel-timeline.js +13 -0
  117. package/dist/core/context/index-reconciler.js +5 -2
  118. package/dist/core/context/policy-index-reconciler.d.ts +6 -4
  119. package/dist/core/context/policy-index-runner.js +25 -6
  120. package/dist/core/context-builder-calendar.js +10 -2
  121. package/dist/core/context-builder-conversation.d.ts +8 -1
  122. package/dist/core/context-builder-conversation.js +41 -7
  123. package/dist/core/context-builder-yesterday.js +4 -3
  124. package/dist/core/context-builder.d.ts +7 -2
  125. package/dist/core/context-builder.js +62 -20
  126. package/dist/core/context-file-serializer.d.ts +1 -1
  127. package/dist/core/context-file-serializer.js +1 -1
  128. package/dist/core/context-health.js +2 -2
  129. package/dist/core/context-paths.d.ts +1 -1
  130. package/dist/core/context-paths.js +1 -1
  131. package/dist/core/context-validation/prepare-write.js +1 -1
  132. package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
  133. package/dist/core/context-vault-aliases.d.ts +0 -13
  134. package/dist/core/context-vault-aliases.js +37 -0
  135. package/dist/core/custom-routines.d.ts +99 -0
  136. package/dist/core/custom-routines.js +187 -0
  137. package/dist/core/daemon-api-cli.js +49 -0
  138. package/dist/core/day-boundary.d.ts +46 -0
  139. package/dist/core/day-boundary.js +40 -0
  140. package/dist/core/dispatcher-activity-scan.d.ts +221 -0
  141. package/dist/core/dispatcher-activity-scan.js +775 -0
  142. package/dist/core/dispatcher-error-handling.d.ts +6 -11
  143. package/dist/core/dispatcher-error-handling.js +38 -62
  144. package/dist/core/dispatcher-hourly-check.js +6 -1
  145. package/dist/core/dispatcher-message-handler.d.ts +10 -0
  146. package/dist/core/dispatcher-message-handler.js +17 -0
  147. package/dist/core/dispatcher-morning-routine.d.ts +6 -6
  148. package/dist/core/dispatcher-morning-routine.js +13 -13
  149. package/dist/core/dispatcher-result-processor.d.ts +33 -0
  150. package/dist/core/dispatcher-result-processor.js +167 -11
  151. package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
  152. package/dist/core/dispatcher-scheduled-background-task.js +89 -0
  153. package/dist/core/dispatcher-scheduled-tasks.d.ts +63 -1
  154. package/dist/core/dispatcher-scheduled-tasks.js +213 -6
  155. package/dist/core/dispatcher-task-delivery.d.ts +105 -0
  156. package/dist/core/dispatcher-task-delivery.js +555 -0
  157. package/dist/core/dispatcher-types.d.ts +48 -9
  158. package/dist/core/dispatcher-types.js +3 -3
  159. package/dist/core/dispatcher.d.ts +112 -31
  160. package/dist/core/dispatcher.js +284 -59
  161. package/dist/core/dm-freshness-metrics.d.ts +1 -1
  162. package/dist/core/drift-effects.js +2 -2
  163. package/dist/core/feedback/consolidation-prep.js +17 -5
  164. package/dist/core/feedback/eviction-scorer.js +6 -2
  165. package/dist/core/feedback/lesson-format.js +9 -4
  166. package/dist/core/feedback/lesson-injection.d.ts +1 -1
  167. package/dist/core/feedback/lesson-injection.js +17 -2
  168. package/dist/core/feedback/lesson-store-overview.d.ts +8 -4
  169. package/dist/core/feedback/lesson-store-overview.js +8 -4
  170. package/dist/core/feedback/regeneralization-prep.js +29 -16
  171. package/dist/core/feedback/self-performance-prep.d.ts +186 -0
  172. package/dist/core/feedback/self-performance-prep.js +541 -0
  173. package/dist/core/feedback/tuning-actuator.d.ts +198 -0
  174. package/dist/core/feedback/tuning-actuator.js +432 -0
  175. package/dist/core/feedback/tuning-recommender.d.ts +247 -0
  176. package/dist/core/feedback/tuning-recommender.js +580 -0
  177. package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
  178. package/dist/core/feedback/tuning-revert-monitor.js +213 -0
  179. package/dist/core/health-monitor.d.ts +6 -0
  180. package/dist/core/health-monitor.js +1 -1
  181. package/dist/core/injection-policy.d.ts +4 -4
  182. package/dist/core/injection-policy.js +4 -4
  183. package/dist/core/integration-main-backend.js +4 -0
  184. package/dist/core/management-md.d.ts +2 -2
  185. package/dist/core/management-md.js +51 -13
  186. package/dist/core/morning/orchestrator.d.ts +2 -2
  187. package/dist/core/morning/orchestrator.js +2 -2
  188. package/dist/core/notification-gate.d.ts +64 -0
  189. package/dist/core/notification-gate.js +51 -0
  190. package/dist/core/notification-rate-limit.d.ts +40 -0
  191. package/dist/core/notification-rate-limit.js +50 -0
  192. package/dist/core/policy-files.d.ts +1 -1
  193. package/dist/core/policy-files.js +2 -2
  194. package/dist/core/pre-pass-freshness.d.ts +4 -4
  195. package/dist/core/retention.d.ts +5 -0
  196. package/dist/core/retention.js +20 -4
  197. package/dist/core/review-context.d.ts +1 -1
  198. package/dist/core/review-context.js +10 -5
  199. package/dist/core/roadmap-write-lock.d.ts +2 -1
  200. package/dist/core/roadmap-write-lock.js +15 -10
  201. package/dist/core/routine-acquisition-plan.d.ts +47 -1
  202. package/dist/core/routine-acquisition-plan.js +78 -20
  203. package/dist/core/routine-fetch-window-retry.js +7 -4
  204. package/dist/core/routine-fetch-window-runner.d.ts +39 -3
  205. package/dist/core/routine-fetch-window-runner.js +264 -13
  206. package/dist/core/routine-windows.d.ts +2 -2
  207. package/dist/core/routine-windows.js +8 -5
  208. package/dist/core/scheduler.d.ts +175 -16
  209. package/dist/core/scheduler.js +559 -102
  210. package/dist/core/signal-detector.d.ts +12 -0
  211. package/dist/core/signal-detector.js +53 -9
  212. package/dist/core/skills-compiler-denied-tools.js +2 -2
  213. package/dist/core/skills-compiler-skill-index.d.ts +2 -2
  214. package/dist/core/skills-compiler-skill-index.js +2 -2
  215. package/dist/core/skills-compiler-variants.d.ts +1 -1
  216. package/dist/core/skills-compiler-variants.js +8 -0
  217. package/dist/core/skills-compiler.d.ts +29 -26
  218. package/dist/core/skills-compiler.js +117 -81
  219. package/dist/core/skills-manifest.d.ts +37 -0
  220. package/dist/core/skills-manifest.js +73 -2
  221. package/dist/core/sleep-inhibitor.d.ts +79 -0
  222. package/dist/core/sleep-inhibitor.js +132 -0
  223. package/dist/core/slim-system-prompt-loader.d.ts +77 -0
  224. package/dist/core/slim-system-prompt-loader.js +141 -0
  225. package/dist/core/spawn-gates.d.ts +126 -0
  226. package/dist/core/spawn-gates.js +180 -0
  227. package/dist/core/today-direct-writer.d.ts +2 -2
  228. package/dist/core/today-direct-writer.js +1 -1
  229. package/dist/core/today-write-lock.d.ts +4 -2
  230. package/dist/core/today-write-lock.js +30 -20
  231. package/dist/core/wake-detector.d.ts +55 -0
  232. package/dist/core/wake-detector.js +80 -0
  233. package/dist/core/wiki/compile-lock.d.ts +1 -1
  234. package/dist/core/wiki/compile-lock.js +1 -1
  235. package/dist/core/workdir.js +15 -6
  236. package/dist/db/activity-scan-signals.d.ts +77 -0
  237. package/dist/db/activity-scan-signals.js +378 -0
  238. package/dist/db/agents-store.d.ts +28 -0
  239. package/dist/db/agents-store.js +62 -0
  240. package/dist/db/background-task-clarifications-store.d.ts +81 -0
  241. package/dist/db/background-task-clarifications-store.js +152 -0
  242. package/dist/db/background-task-store.d.ts +207 -0
  243. package/dist/db/background-task-store.js +380 -0
  244. package/dist/db/browser-history-store.d.ts +39 -6
  245. package/dist/db/browser-history-store.js +51 -7
  246. package/dist/db/browser-task-clarifications-store.d.ts +12 -0
  247. package/dist/db/browser-task-clarifications-store.js +35 -5
  248. package/dist/db/browser-task-store.d.ts +3 -0
  249. package/dist/db/browser-task-store.js +29 -4
  250. package/dist/db/deferred-dm.d.ts +86 -0
  251. package/dist/db/deferred-dm.js +199 -0
  252. package/dist/db/migrations.js +330 -0
  253. package/dist/db/observations.d.ts +2 -2
  254. package/dist/db/observations.js +3 -3
  255. package/dist/db/schema.js +217 -16
  256. package/dist/db/voice-transcripts-store.d.ts +1 -1
  257. package/dist/index.js +86 -29
  258. package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
  259. package/dist/messaging/browser-task-mcp-notifier.js +30 -151
  260. package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
  261. package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
  262. package/dist/observers/delegated-sync-worker.d.ts +6 -6
  263. package/dist/observers/delegated-sync-worker.js +10 -10
  264. package/dist/observers/git-delegated-cron.d.ts +1 -1
  265. package/dist/observers/git-delegated-cron.js +2 -2
  266. package/dist/observers/github-poller-classifier.d.ts +3 -3
  267. package/dist/observers/github-poller-classifier.js +3 -3
  268. package/dist/observers/imminent-event-scheduler.d.ts +1 -1
  269. package/dist/observers/imminent-event-scheduler.js +1 -1
  270. package/dist/observers/mail-poller.d.ts +1 -0
  271. package/dist/observers/mail-poller.js +42 -3
  272. package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
  273. package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
  274. package/dist/observers/observation-summarizer/worker.d.ts +2 -2
  275. package/dist/observers/observation-summarizer/worker.js +4 -4
  276. package/dist/observers/obsidian-watcher.d.ts +1 -1
  277. package/dist/observers/obsidian-watcher.js +1 -1
  278. package/dist/safety/agent-write-tracker.d.ts +4 -4
  279. package/dist/safety/agent-write-tracker.js +4 -4
  280. package/dist/safety/audit.d.ts +43 -5
  281. package/dist/safety/audit.js +86 -18
  282. package/dist/safety/risk-classifier.d.ts +6 -0
  283. package/dist/safety/risk-classifier.js +75 -11
  284. package/dist/scheduler/activity-scan-gate.d.ts +86 -0
  285. package/dist/scheduler/activity-scan-gate.js +132 -0
  286. package/dist/services/background-task/background-task-budget.d.ts +80 -0
  287. package/dist/services/background-task/background-task-budget.js +91 -0
  288. package/dist/services/background-task/background-task-driver.d.ts +105 -0
  289. package/dist/services/background-task/background-task-driver.js +416 -0
  290. package/dist/services/background-task/background-task-runner.d.ts +96 -0
  291. package/dist/services/background-task/background-task-runner.js +673 -0
  292. package/dist/services/background-task/background-task-tools.d.ts +84 -0
  293. package/dist/services/background-task/background-task-tools.js +247 -0
  294. package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
  295. package/dist/services/background-task/background-task-transition-events.js +54 -0
  296. package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
  297. package/dist/services/browser-history/automation/egress-denylist.js +16 -6
  298. package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
  299. package/dist/services/browser-task/browser-task-runner.js +53 -8
  300. package/dist/services/observations-batch.d.ts +1 -1
  301. package/dist/services/observations-batch.js +2 -2
  302. package/dist/settings/runtime-settings.d.ts +38 -11
  303. package/dist/settings/runtime-settings.js +203 -40
  304. package/dist/settings/settings-store.js +11 -3
  305. package/package.json +4 -4
@@ -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
+ }
@@ -5,12 +5,12 @@
5
5
  * the daemon — adapter / service interfaces (`IDashboardStream`,
6
6
  * `INotificationManager`, `ISessionManager`, `IMessageRecorder`,
7
7
  * `IContextBuilder`, `IAuditLogger`), the `GetTaskFlow` callback, and the
8
- * value types used at API boundaries (`TriggerHourlyCheckOptions`,
9
- * `TriggerHourlyCheckResult`, `InFlightExecutionInfo`, `SetupMode`,
8
+ * value types used at API boundaries (`TriggerActivityScanOptions`,
9
+ * `TriggerActivityScanResult`, `InFlightExecutionInfo`, `SetupMode`,
10
10
  * `BangCommandDetail`, `ReplyActivityHandle`). It also hosts two pure
11
11
  * helpers that have no dependence on dispatcher instance state:
12
12
  * `buildLogErrorContext` (recovers backend/quota failure metadata from a
13
- * thrown error) and `parseStage2Verdict` (extracts the Stage 2 hourly-check
13
+ * thrown error) and `parseStage2Verdict` (extracts the Stage 2 activity-scan
14
14
  * triage JSON verdict from an LLM response).
15
15
  *
16
16
  * The dispatcher re-exports the public-surface members of this file so that
@@ -18,6 +18,7 @@
18
18
  * without modification — phase D-1 of `docs/design/appendices/file-split-plan.md`.
19
19
  */
20
20
  import type { Event, MessageEvent, AgentResult, BackendId, IntegrationKey, IntegrationState } from "@aitne/shared";
21
+ import type { OutboundAttachmentRef } from "../adapters/types.js";
21
22
  import type { SessionInfoPayload } from "../api/chat-binding-query.js";
22
23
  import type { DeferredSessionEffects } from "./session-manager.js";
23
24
  export interface ReplyActivityHandle {
@@ -114,6 +115,15 @@ export interface INotificationManager {
114
115
  * WIKI_BUILDER_DESIGN.md §3.4-bis (completion notification path).
115
116
  */
116
117
  replyTo?: MessageReplyTarget;
118
+ /**
119
+ * Outbound media attachments, delivered alongside `message` in a
120
+ * single adapter send. Only honoured on the direct `replyTo` path
121
+ * (the manager hands them to `messageHub.sendToPlatform`); ignored
122
+ * on the proactive/batch paths. Used by the background task-delivery
123
+ * idle branch to re-attach browser-task screenshots inline
124
+ * (BACKGROUND_TASK_RUNNER_DESIGN.md Phase 1).
125
+ */
126
+ attachments?: readonly OutboundAttachmentRef[];
117
127
  }): Promise<void>;
118
128
  beginReplyActivity(event: MessageEvent): Promise<ReplyActivityHandle>;
119
129
  }
@@ -268,6 +278,21 @@ export interface IAuditLogger {
268
278
  trigger: "reactive" | "autonomous";
269
279
  backend?: BackendId;
270
280
  costSource?: AgentResult["costSource"];
281
+ /**
282
+ * Terminal result override for the success path. Defaults to
283
+ * `"success"`. The only other legal value here is `"partial"` — a
284
+ * session that ended cleanly (no backend error) but failed a post-run
285
+ * outcome check, e.g. RESEARCH_CLUSTER_COST_FIX_PLAN F5's
286
+ * journal-write verification. Hard errors take the `logError` path
287
+ * (`result='failed'`); this override never expresses failure.
288
+ */
289
+ result?: "success" | "partial";
290
+ /**
291
+ * Outcome-failure marker persisted to `agent_actions.error` when a run
292
+ * is downgraded to `result:"partial"` (F5: `'journal_write_missing'`).
293
+ * Paired with `result:"partial"`; ignored on a plain success.
294
+ */
295
+ error?: string;
271
296
  /**
272
297
  * Whether the agent made at least one PUT/PATCH call to /api/context/*.
273
298
  * Used for observer-event observability — see Phase 6 of
@@ -367,13 +392,27 @@ export interface IAuditLogger {
367
392
  */
368
393
  dailyWrite?: DailyWriteAuditDetail | null;
369
394
  }): void;
370
- logSkip(event: Event, reason: string, trigger: "reactive" | "autonomous"): void;
395
+ logSkip(event: Event, reason: string, trigger: "reactive" | "autonomous",
396
+ /** Optional structured context for the `detail` JSON column (N2/N3). */
397
+ detail?: Record<string, unknown>): void;
371
398
  logError(event: Event, error: Error, trigger: "reactive" | "autonomous", context?: {
372
399
  durationMs?: number;
373
400
  backendId?: BackendId;
374
401
  modelId?: string;
375
402
  failureKind?: string;
376
403
  failureCode?: string;
404
+ /**
405
+ * PREPASS_COST_REDUCTION_PLAN.md N1 — recovered spend for a failed
406
+ * turn the provider already billed. Only real recovered figures;
407
+ * never a guess (see `AuditLogger.logError`).
408
+ */
409
+ costUsd?: number;
410
+ costSource?: string;
411
+ tokensInput?: number;
412
+ tokensOutput?: number;
413
+ tokensCacheCreation?: number;
414
+ tokensCacheRead?: number;
415
+ numTurns?: number;
377
416
  /**
378
417
  * Pre-pass fan-out failure block. Mirrors the `prePass` payload on
379
418
  * `logAction` so `MetricsCollector.collectPrePassMetrics` can see
@@ -474,7 +513,7 @@ export interface BangCommandDetail {
474
513
  status: "ok" | "skipped" | "unknown" | "invalid_args" | "paused_decline" | "paused_blocked";
475
514
  [extra: string]: unknown;
476
515
  }
477
- export type TriggerHourlyCheckSkipReason = "morning_routine_active" | "hourly_check_in_progress" | "below_threshold" | "setup_incomplete" | "setup_in_progress" | "vault_degraded" | "user_paused" | "morning_routine_pending_for_today" | "gate_stage0_silent" | "gate_stage2_log_only";
516
+ export type TriggerActivityScanSkipReason = "morning_routine_active" | "activity_scan_in_progress" | "below_threshold" | "setup_incomplete" | "setup_in_progress" | "vault_degraded" | "user_paused" | "morning_routine_pending_for_today" | "gate_stage0_silent" | "gate_stage2_log_only";
478
517
  export type SetupMode = "initial" | "update";
479
518
  /**
480
519
  * Unwrap the partial-run context the audit logger needs for a failed
@@ -489,16 +528,16 @@ export declare function buildLogErrorContext(err: unknown, durationMs: number):
489
528
  failureKind?: string;
490
529
  failureCode?: string;
491
530
  };
492
- export interface TriggerHourlyCheckOptions {
531
+ export interface TriggerActivityScanOptions {
493
532
  force?: boolean;
494
533
  /** Optional model hint — injected as `requestedModel` on the enqueued
495
- * routine.hourly_check event so the user can force an Opus run from
534
+ * routine.activity_scan event so the user can force an Opus run from
496
535
  * /api/agent/run-now without touching process_backend_config. */
497
536
  requestedModel?: "sonnet" | "opus";
498
537
  }
499
- export interface TriggerHourlyCheckResult {
538
+ export interface TriggerActivityScanResult {
500
539
  status: "queued" | "skipped";
501
- reason?: TriggerHourlyCheckSkipReason;
540
+ reason?: TriggerActivityScanSkipReason;
502
541
  pendingCount?: number;
503
542
  minObservations: number;
504
543
  forced: boolean;