@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,416 @@
1
+ /**
2
+ * Background-task driver — generic Claude Agent SDK glue for the
3
+ * per-task worker loop. BACKGROUND_TASK_RUNNER_DESIGN.md §4.1.
4
+ *
5
+ * The browser-task driver's analogue, with the entire Playwright /
6
+ * managed-Chromium / allowlist / final-confirm plane removed. A worker
7
+ * is a plain `query()` session seeded with a SELF-CONTAINED brief and a
8
+ * three-tool MCP envelope (`read_memory`, `ask_user`, `finish`) plus the
9
+ * SDK's `WebSearch` / `WebFetch` for research-type work. It opts out of
10
+ * the `<user>` / `<management_rules>` injection (`settingSources:
11
+ * ["project"]`, as browser-task does) — the brief carries the context,
12
+ * the output-language directive, persona hints for the `draft`, and the
13
+ * notification policy / criteria.
14
+ *
15
+ * Responsibilities:
16
+ * 1. Resolve the `(model, maxTurns, maxBudgetUsd, executeTimeout)`
17
+ * envelope from `process_backend_config` + the row's tier/budget
18
+ * (`background-task-budget.ts`), pinned for the task's lifetime.
19
+ * 2. Render a per-task workdir (empty dir + CLAUDE.md agent profile).
20
+ * 3. Drive `query()` until terminal, honouring the AbortController
21
+ * (cancel + timeout) — no Playwright resources to release.
22
+ * 4. Hand back a `DriverRunResult` the runner maps to terminal state /
23
+ * park. On the death paths the artifact is NULL — the runner
24
+ * synthesizes the fail-loud artifact (§4.3).
25
+ *
26
+ * Excluded from the 100% coverage gate — SDK stream consumer. The pure
27
+ * sub-pieces (budget envelope) live in the covered set.
28
+ */
29
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
30
+ import { join } from "node:path";
31
+ import { query } from "@anthropic-ai/claude-agent-sdk";
32
+ import { getTaskFlow } from "../../core/prompts.js";
33
+ import { ALWAYS_DISALLOWED_TOOLS } from "../../safety/always-disallowed.js";
34
+ import { getBackgroundTask, setBackendSessionId, } from "../../db/background-task-store.js";
35
+ import { createLogger } from "../../logging.js";
36
+ import { resolveBackgroundTaskEnvelope, } from "./background-task-budget.js";
37
+ import { BACKGROUND_TASK_MCP_SERVER_NAME, BACKGROUND_TASK_TOOL_FQNS, createBackgroundTaskMcpServer, createBackgroundTaskRuntime, } from "./background-task-tools.js";
38
+ import { noopBackgroundTaskTransitionEmitter, } from "./background-task-transition-events.js";
39
+ const logger = createLogger("background-task-driver");
40
+ /** SDK tools the worker may use beyond its MCP envelope. Research-type
41
+ * background tasks (the flagship category) need web access; everything
42
+ * filesystem/shell is denied (the worker must not read arbitrary host
43
+ * files or write shared memory — §10.4). */
44
+ const BACKGROUND_TASK_WEB_TOOLS = ["WebSearch", "WebFetch"];
45
+ /** Defence-in-depth deny list. These are NOT in `allowedTools`, so
46
+ * `dontAsk` already denies them — listing them ensures a future
47
+ * regression that widens `allowedTools` cannot unlock filesystem/shell
48
+ * access. */
49
+ const BACKGROUND_TASK_EXTRA_DISALLOWED = [
50
+ "Bash",
51
+ "Read",
52
+ "Write",
53
+ "Edit",
54
+ "NotebookEdit",
55
+ "Glob",
56
+ "Grep",
57
+ ];
58
+ /** Read the operator-editable envelope row for `background_task`. */
59
+ export function loadBackgroundTaskProcessConfig(db) {
60
+ try {
61
+ const row = db
62
+ .prepare(`SELECT main_backend, main_model, max_turns, max_budget_usd
63
+ FROM process_backend_config
64
+ WHERE process_key = 'background_task'`)
65
+ .get();
66
+ if (!row)
67
+ return null;
68
+ return {
69
+ mainBackend: row.main_backend,
70
+ mainModel: row.main_model,
71
+ maxTurns: row.max_turns,
72
+ maxBudgetUsd: row.max_budget_usd,
73
+ };
74
+ }
75
+ catch (err) {
76
+ logger.warn({ err }, "background-task: process_backend_config read failed; falling back to tier defaults");
77
+ return null;
78
+ }
79
+ }
80
+ /**
81
+ * Acquire a fresh workdir + runtime + binding for `row`. The runner
82
+ * calls this BEFORE `runDriver` so it can stash the handle in its parked
83
+ * map before any turn fires (an ask_user on the first turn must find the
84
+ * handle already there).
85
+ */
86
+ export async function prepareDriverHandle(input) {
87
+ const { deps, row } = input;
88
+ // Pin the envelope BEFORE any work so a misconfigured backend fails
89
+ // fast AND the binding is frozen for the task's lifetime (no surprise
90
+ // model swap between yield and clarify resume).
91
+ const resolved = resolveBackgroundTaskEnvelope({
92
+ tier: row.tier,
93
+ maxBudgetUsd: row.maxBudgetUsd,
94
+ processConfig: loadBackgroundTaskProcessConfig(deps.db),
95
+ });
96
+ if (!resolved.ok) {
97
+ return { ok: false, reason: "backend_misconfigured", detail: resolved.detail };
98
+ }
99
+ const cwd = join(deps.paDataDir, "background-task-sessions", row.id);
100
+ await mkdir(cwd, { recursive: true });
101
+ try {
102
+ const profileBody = await readAgentProfileBody(deps.workspaceDir);
103
+ await writeFile(join(cwd, "CLAUDE.md"), profileBody, "utf-8");
104
+ }
105
+ catch (err) {
106
+ logger.warn({ err, taskId: row.id }, "background-task: agent profile read failed — proceeding with minimal CLAUDE.md");
107
+ await writeFile(join(cwd, "CLAUDE.md"), "# Background Task Worker\n", "utf-8");
108
+ }
109
+ const abortController = new AbortController();
110
+ const runtime = createBackgroundTaskRuntime({
111
+ taskId: row.id,
112
+ db: deps.db,
113
+ contextDir: deps.contextDir,
114
+ clarificationTtlMs: deps.clarificationTtlMs,
115
+ abortSignal: abortController.signal,
116
+ transitionEmitter: deps.transitionEmitter,
117
+ nowFn: deps.nowFn,
118
+ });
119
+ return {
120
+ ok: true,
121
+ handle: {
122
+ abortController,
123
+ cwd,
124
+ runtime,
125
+ sdkSessionId: row.backendSessionId,
126
+ binding: resolved.envelope,
127
+ },
128
+ };
129
+ }
130
+ /** Drive the initial turn — the worker reads the brief and works. */
131
+ export async function runDriver(deps, row, handle) {
132
+ const startMs = (deps.nowFn ?? (() => Date.now()))();
133
+ return runQuery({
134
+ deps,
135
+ handle,
136
+ row,
137
+ prompt: renderTaskPrompt(row),
138
+ resume: null,
139
+ startMs,
140
+ });
141
+ }
142
+ /** Resume a parked task after `/clarify` lands the owner's answer. Uses
143
+ * the persisted SDK session id so the prompt cache stays warm. Works both
144
+ * in-process (warm parked handle) and across a daemon restart (the runner
145
+ * reconstructs the handle from the persisted `backend_session_id`); in the
146
+ * cross-restart case a session the SDK can no longer load surfaces as
147
+ * `resume_unavailable`. */
148
+ export async function resumeDriver(deps, row, handle, userAnswer) {
149
+ if (!handle.sdkSessionId) {
150
+ return {
151
+ outcome: "no_finish",
152
+ sdkSessionId: null,
153
+ detail: "no sdk session id captured — cannot resume",
154
+ costUsd: 0,
155
+ numTurns: 0,
156
+ durationMs: 0,
157
+ };
158
+ }
159
+ const startMs = (deps.nowFn ?? (() => Date.now()))();
160
+ return runQuery({
161
+ deps,
162
+ handle,
163
+ row,
164
+ prompt: `The owner answered your clarification:\n\n${userAnswer}\n\nContinue the task.`,
165
+ resume: handle.sdkSessionId,
166
+ isResume: true,
167
+ startMs,
168
+ });
169
+ }
170
+ /**
171
+ * BACKGROUND_TASK_RUNNER_DESIGN.md §10.2 / Phase 4 — resume a task that was
172
+ * mid-execution when the daemon restarted, using the persisted SDK session
173
+ * id (`backend_session_id`) so the warm transcript + prompt cache survive
174
+ * the restart instead of re-running the brief from scratch. The runner
175
+ * reconstructs the handle (`prepareDriverHandle` recreates the per-task
176
+ * workdir + sets `sdkSessionId` from the row). When the SDK can no longer
177
+ * load the session, this returns `resume_unavailable` and the runner falls
178
+ * back to re-dispatch-from-brief — so resume is a pure optimization with no
179
+ * regression.
180
+ */
181
+ export async function resumeFromBootDriver(deps, row, handle) {
182
+ if (!handle.sdkSessionId) {
183
+ return {
184
+ outcome: "resume_unavailable",
185
+ sdkSessionId: null,
186
+ detail: "no persisted sdk session id — cannot resume across restart",
187
+ costUsd: 0,
188
+ numTurns: 0,
189
+ durationMs: 0,
190
+ };
191
+ }
192
+ const startMs = (deps.nowFn ?? (() => Date.now()))();
193
+ return runQuery({
194
+ deps,
195
+ handle,
196
+ row,
197
+ prompt: "The daemon restarted while you were working on this task. Pick up "
198
+ + "exactly where you left off and finish it. If you had already "
199
+ + "gathered enough, call finish() now.",
200
+ resume: handle.sdkSessionId,
201
+ isResume: true,
202
+ startMs,
203
+ });
204
+ }
205
+ async function runQuery(input) {
206
+ const { deps, handle, row, prompt, resume, isResume = false, startMs } = input;
207
+ const mcpServer = createBackgroundTaskMcpServer(handle.runtime);
208
+ const mcpServers = {
209
+ [BACKGROUND_TASK_MCP_SERVER_NAME]: mcpServer,
210
+ };
211
+ const binding = handle.binding;
212
+ let sessionId = handle.sdkSessionId;
213
+ let costUsd = 0;
214
+ let numTurns = 0;
215
+ let stopReason = null;
216
+ let isError = false;
217
+ let sawInit = false;
218
+ let outcome = "completed";
219
+ let detail = null;
220
+ const timeoutMs = binding.executeTimeoutMinutes * 60 * 1000;
221
+ const timeoutTimer = setTimeout(() => {
222
+ handle.abortController.abort(new Error("background_task_execute_timeout"));
223
+ }, timeoutMs);
224
+ try {
225
+ const stream = query({
226
+ prompt,
227
+ options: {
228
+ ...(resume ? { resume } : {}),
229
+ cwd: handle.cwd,
230
+ model: binding.modelId,
231
+ maxTurns: binding.maxTurns,
232
+ maxBudgetUsd: binding.maxBudgetUsd,
233
+ abortController: handle.abortController,
234
+ permissionMode: "dontAsk",
235
+ allowedTools: [
236
+ ...BACKGROUND_TASK_TOOL_FQNS,
237
+ ...BACKGROUND_TASK_WEB_TOOLS,
238
+ ],
239
+ disallowedTools: [
240
+ ...ALWAYS_DISALLOWED_TOOLS,
241
+ ...BACKGROUND_TASK_EXTRA_DISALLOWED,
242
+ ],
243
+ mcpServers,
244
+ // CLAUDE.md in cwd is the persona; settingSources includes
245
+ // "project" so the SDK auto-loads it but NOT the daemon's
246
+ // <user>/<management_rules> (the brief is self-contained).
247
+ settingSources: ["project"],
248
+ persistSession: true,
249
+ includePartialMessages: false,
250
+ },
251
+ });
252
+ try {
253
+ for await (const message of stream) {
254
+ if (message.type === "system"
255
+ && message.subtype === "init") {
256
+ sawInit = true;
257
+ const sid = message.session_id ?? null;
258
+ if (sid) {
259
+ sessionId = sid;
260
+ handle.sdkSessionId = sid;
261
+ // Persist so a /clarify resume (and the boot path) can find it.
262
+ try {
263
+ setBackendSessionId(deps.db, row.id, sid);
264
+ }
265
+ catch (err) {
266
+ /* c8 ignore next 3 -- defensive */
267
+ logger.warn({ err, taskId: row.id }, "setBackendSessionId failed (continuing)");
268
+ }
269
+ }
270
+ }
271
+ else if (message.type === "result") {
272
+ const result = message;
273
+ stopReason = result.stop_reason ?? null;
274
+ isError = !!result.is_error;
275
+ costUsd += result.total_cost_usd ?? 0;
276
+ numTurns = result.num_turns ?? numTurns;
277
+ }
278
+ }
279
+ }
280
+ finally {
281
+ try {
282
+ await stream.return?.(undefined);
283
+ }
284
+ catch {
285
+ /* ignore */
286
+ }
287
+ }
288
+ const dbStateAfterStream = getBackgroundTask(deps.db, row.id)?.state ?? "running";
289
+ if (handle.abortController.signal.aborted) {
290
+ const reason = handle.abortController.signal.reason;
291
+ if (reason instanceof Error
292
+ && reason.message === "background_task_execute_timeout") {
293
+ outcome = "timeout";
294
+ detail = `executeTimeoutMinutes=${binding.executeTimeoutMinutes}`;
295
+ }
296
+ else {
297
+ outcome = "cancelled";
298
+ detail = reason instanceof Error ? reason.message : String(reason ?? "abort");
299
+ }
300
+ }
301
+ else if (isResume && !sawInit) {
302
+ // A resume attempt whose session never loaded — no `init` message
303
+ // ever arrived (e.g. the SDK can no longer find the persisted
304
+ // session after a restart). The runner falls back to re-dispatch
305
+ // rather than fail-loud. Comes after the abort check so a cancel /
306
+ // timeout during resume is still classified as such.
307
+ outcome = "resume_unavailable";
308
+ detail = "resume target session did not load (no init message)";
309
+ }
310
+ else if (isError && stopReason && /max_turns/i.test(stopReason)) {
311
+ outcome = "max_turns_exceeded";
312
+ detail = stopReason;
313
+ }
314
+ else if (isError && stopReason && /budget|cost/i.test(stopReason)) {
315
+ outcome = "budget_exceeded";
316
+ detail = stopReason;
317
+ }
318
+ else if (handle.runtime.finishFlag.current || dbStateAfterStream === "completed") {
319
+ outcome = "completed";
320
+ }
321
+ else if (dbStateAfterStream === "awaiting_user"
322
+ && handle.runtime.yieldFlag.current) {
323
+ outcome = "yielded_for_clarification";
324
+ }
325
+ else if (isError) {
326
+ outcome = "sdk_error";
327
+ detail = stopReason ?? "sdk_isError";
328
+ }
329
+ else {
330
+ // Clean SDK end but the worker never called finish/ask_user. The
331
+ // runner treats this as a fail-loud terminal.
332
+ outcome = "no_finish";
333
+ detail = "SDK stream ended without finish() or ask_user()";
334
+ }
335
+ }
336
+ catch (err) {
337
+ if (handle.abortController.signal.aborted) {
338
+ outcome = "cancelled";
339
+ detail = err instanceof Error ? err.message : String(err);
340
+ }
341
+ else if (isResume && !sawInit) {
342
+ // The resume query threw before the session loaded — treat as a
343
+ // recoverable "couldn't resume" rather than a hard SDK error so the
344
+ // runner re-dispatches from brief.
345
+ outcome = "resume_unavailable";
346
+ detail = err instanceof Error ? err.message : String(err);
347
+ }
348
+ else {
349
+ outcome = "sdk_error";
350
+ detail = err instanceof Error ? err.message : String(err);
351
+ logger.error({ err, taskId: row.id }, "background-task SDK query threw");
352
+ }
353
+ }
354
+ finally {
355
+ clearTimeout(timeoutTimer);
356
+ }
357
+ const durationMs = (deps.nowFn ?? (() => Date.now()))() - startMs;
358
+ return { outcome, sdkSessionId: sessionId, detail, costUsd, numTurns, durationMs };
359
+ }
360
+ /** Remove the per-task workdir. Idempotent. Parked tasks (awaiting_user)
361
+ * do NOT call this — the runner keeps the handle in its parked map so
362
+ * /clarify can resume the warm SDK session. */
363
+ export async function releaseDriverHandle(deps, handle) {
364
+ try {
365
+ await rm(handle.cwd, { recursive: true, force: true });
366
+ }
367
+ catch (err) {
368
+ /* c8 ignore start -- defensive */
369
+ logger.warn({ err, cwd: handle.cwd }, "background-task workdir cleanup failed");
370
+ /* c8 ignore stop */
371
+ }
372
+ void deps;
373
+ }
374
+ async function readAgentProfileBody(workspaceDir) {
375
+ const candidate = join(workspaceDir, "agent-assets", "agent-profiles", "background-task.md");
376
+ return readFile(candidate, "utf-8");
377
+ }
378
+ /** Render the worker prompt: the `background_task` task-flow (tool
379
+ * policy + notify-disposition rules + output-language reminder) with the
380
+ * `{context}` block, then the self-contained brief. */
381
+ function renderTaskPrompt(row) {
382
+ const flow = getTaskFlow("background_task");
383
+ const body = flow.length > 0 ? flow : "## Background Task\n\n{context}\n";
384
+ return body
385
+ .replace(/\{context\}/g, renderContextBlock(row))
386
+ .concat("\n\n## Your task (the brief)\n\n", row.brief, "\n");
387
+ }
388
+ function renderContextBlock(row) {
389
+ const lines = [
390
+ "<task>",
391
+ `<task_id>${row.id}</task_id>`,
392
+ `<title>${row.title ?? ""}</title>`,
393
+ `<notification_policy>${row.notificationPolicy}</notification_policy>`,
394
+ ];
395
+ // Phase 4 if_significant criteria DSL (§4.3): when the spawn carried
396
+ // structured criteria, render them as a numbered checklist so the
397
+ // worker's notify decision is a deterministic per-criterion evaluation
398
+ // rather than a free judgement. Only meaningful for `if_significant`;
399
+ // injected only when present (else the worker falls back to the brief's
400
+ // prose criteria).
401
+ if (row.notificationPolicy === "if_significant"
402
+ && row.significanceCriteria
403
+ && row.significanceCriteria.length > 0) {
404
+ lines.push("<significance_criteria>");
405
+ lines.push("Set notify=true if AT LEAST ONE of these is met by your result; "
406
+ + "otherwise notify=false. In `significance`, state which criteria "
407
+ + "were met / unmet.");
408
+ row.significanceCriteria.forEach((c, i) => {
409
+ lines.push(`${i + 1}. ${c}`);
410
+ });
411
+ lines.push("</significance_criteria>");
412
+ }
413
+ lines.push("</task>");
414
+ return lines.join("\n");
415
+ }
416
+ void noopBackgroundTaskTransitionEmitter;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Background-task runner — BACKGROUND_TASK_RUNNER_DESIGN.md §4.1.
3
+ *
4
+ * The browser-task runner's lifecycle skeleton, generalized: it owns the
5
+ * per-task lifecycle from POST → terminal state, REUSING the pure slot
6
+ * manager (`browser-task-slots.ts`) with a synthetic per-task slot key so
7
+ * background tasks contend ONLY on the global concurrency cap (never on a
8
+ * per-key queue). The Playwright plane is gone; the worker is a generic
9
+ * SDK session (`background-task-driver.ts`).
10
+ *
11
+ * What this owns beyond browser-task's runner:
12
+ * - DELIVERY ENQUEUE — the runner's `reconcileDriverOutcome` reads the
13
+ * finished artifact and decides delivery in ONE place (the design's
14
+ * "post-transition hook"): completed + notify=true ⇒ enqueue result;
15
+ * parked ⇒ enqueue the open clarification; failed/timeout/no_finish ⇒
16
+ * FAIL-LOUD synthesize a failure artifact (notify=true) + enqueue;
17
+ * cancelled ⇒ no delivery (the owner cancelled). notify=false ⇒ file
18
+ * only. The periodic recovery sweep re-enqueues any lost delivery.
19
+ *
20
+ * I/O-shaped. Excluded from the 100% coverage gate; the pure logic lives
21
+ * in the reused slot manager + the budget envelope.
22
+ */
23
+ import type Database from "better-sqlite3";
24
+ import { type BackgroundTaskRow } from "../../db/background-task-store.js";
25
+ import { type DriverDeps } from "./background-task-driver.js";
26
+ import { type SlotState } from "../browser-task/browser-task-slots.js";
27
+ import { type BackgroundTaskTransitionEmitter } from "./background-task-transition-events.js";
28
+ /** Shared mutable slot-state container — the runner and the route layer
29
+ * hold the same instance (cancel-while-pending vs runner promote race). */
30
+ export interface BackgroundTaskSlotStateRef {
31
+ state: SlotState;
32
+ }
33
+ export declare function createBackgroundTaskSlotStateRef(maxConcurrent: number): BackgroundTaskSlotStateRef;
34
+ /**
35
+ * Delivery enqueuer — the runner hands the artifact to this so a
36
+ * `task.delivery` event lands on the bus. Wired in bootstrap from the
37
+ * `createBackgroundTask*DeliveryEvent` factories + the event bus (keeps
38
+ * the runner free of any core/messaging import).
39
+ */
40
+ export interface BackgroundTaskDeliveryEnqueuer {
41
+ enqueueResult(input: {
42
+ taskId: string;
43
+ originatingChannel: string | null;
44
+ title: string;
45
+ draft: string;
46
+ report: string;
47
+ }): Promise<void>;
48
+ enqueueClarification(input: {
49
+ taskId: string;
50
+ originatingChannel: string | null;
51
+ title: string;
52
+ clarificationId: string;
53
+ question: string;
54
+ contextSummary: string | null;
55
+ }): Promise<void>;
56
+ }
57
+ export interface BackgroundTaskRunnerDeps {
58
+ db: Database.Database;
59
+ slotStateRef: BackgroundTaskSlotStateRef;
60
+ /** Drives the worker SDK session. Absent only in early-boot ordering
61
+ * races / tests — when absent the runner fails the row fast. */
62
+ driver?: DriverDeps;
63
+ /** Enqueues `task.delivery` events. Optional — when absent (tests) the
64
+ * artifact is still written + filed; only the push is skipped. */
65
+ deliveryEnqueuer?: BackgroundTaskDeliveryEnqueuer;
66
+ transitionEmitter?: BackgroundTaskTransitionEmitter;
67
+ /** BACKGROUND_TASK_RUNNER_DESIGN.md §10.2 / Phase 4 — when true, a task
68
+ * with a captured SDK session id is resumed via `query({resume})` across
69
+ * a daemon restart (boot) or a clarify-after-restart, falling back to
70
+ * re-dispatch-from-brief when the session can't be loaded. Wired from
71
+ * `backgroundTaskResumeAcrossRestart`; defaults to false (the v1
72
+ * re-dispatch-only behaviour) when omitted. */
73
+ resumeAcrossRestart?: boolean;
74
+ nowFn?: () => number;
75
+ }
76
+ export interface RunResult {
77
+ ok: boolean;
78
+ reason: "completed" | "failed" | "timeout" | "cancelled" | "parked_awaiting_user" | "no_driver" | "queued" | "task_missing" | "already_terminal";
79
+ state: BackgroundTaskRow["state"] | null;
80
+ }
81
+ export interface BackgroundTaskRunner {
82
+ runFromPost(taskId: string): Promise<RunResult>;
83
+ runFromScheduleRow(taskId: string): Promise<RunResult>;
84
+ cancel(taskId: string, reason: string): Promise<boolean>;
85
+ resumeAfterClarification(input: {
86
+ taskId: string;
87
+ clarificationId: string;
88
+ answer: string;
89
+ }): Promise<RunResult>;
90
+ /** Phase 4 (§10.2) — boot recovery for ONE non-terminal task: resume the
91
+ * warm SDK session when possible, else re-dispatch from brief. */
92
+ resumeFromBoot(taskId: string): Promise<RunResult>;
93
+ expireForDeadline(taskId: string, kind: "clarification_deadline" | "queue_timeout", waitedMs?: number): Promise<RunResult>;
94
+ __peekParkedIds(): readonly string[];
95
+ }
96
+ export declare function createBackgroundTaskRunner(deps: BackgroundTaskRunnerDeps): BackgroundTaskRunner;