@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
@@ -285,6 +285,30 @@ const API_RISK = {
285
285
  "GET /api/browser-task/{*}/screenshots/{*}": RiskTier.ReadSensitive,
286
286
  "POST /api/browser-task/{*}/clarify": RiskTier.Autonomous,
287
287
  "POST /api/browser-task/{*}/cancel": RiskTier.Autonomous,
288
+ // ── Background Task (BACKGROUND_TASK_RUNNER_DESIGN.md §7) ──
289
+ // Generic detached long-task surface, cloned from browser-task and
290
+ // classified to the same posture. The only production callers are the
291
+ // DM-agent `background-task` / `background-task-reply` skills and the
292
+ // morning-briefing session, whose curl shim carries `x-read-token`
293
+ // (sufficient for ReadSensitive, never enough for Approve). The same
294
+ // loopback / sec-fetch / channel-attestation defenses as browser-task
295
+ // apply, and every dispatched task surfaces to the user via DM — no
296
+ // covert dispatch path.
297
+ // - POST (spawn) + clarify + cancel are Autonomous, matching the
298
+ // sibling agent-driven write paths (design §7: "same posture as
299
+ // /api/browser-task").
300
+ // - The list + detail reads stay ReadSensitive (NOT Autonomous):
301
+ // the artifact (`report` / `brief` / `draft` / `significance`)
302
+ // carries personal research / audit content, exactly the reason
303
+ // browser-task's reads are ReadSensitive. Reachable from the agent
304
+ // sessions that need them because their curl carries the read
305
+ // token (cf. GET /api/observations, also ReadSensitive, which the
306
+ // briefing already calls).
307
+ "POST /api/background-task": RiskTier.Autonomous,
308
+ "GET /api/background-task": RiskTier.ReadSensitive,
309
+ "GET /api/background-task/{*}": RiskTier.ReadSensitive,
310
+ "POST /api/background-task/{*}/clarify": RiskTier.Autonomous,
311
+ "POST /api/background-task/{*}/cancel": RiskTier.Autonomous,
288
312
  "/api/setup": RiskTier.Approve,
289
313
  "POST /api/setup/redetect-browsers": RiskTier.Approve,
290
314
  // Management Mode Phase 2 — migration endpoint. Redundant with the
@@ -451,6 +475,22 @@ const API_RISK = {
451
475
  "PUT /api/observations/{*}/consume": RiskTier.Autonomous,
452
476
  "PATCH /api/observations/{*}/consume": RiskTier.Autonomous,
453
477
  "DELETE /api/observations/{*}/consume": RiskTier.Autonomous,
478
+ "POST /api/feedback": RiskTier.Autonomous,
479
+ "POST /api/feedback/consume": RiskTier.Autonomous,
480
+ // Read-only lesson-store overview for the dashboard Lessons settings page
481
+ // (FEEDBACK_LEARNING_LOOP_DESIGN.md §9 Phase 5). Summarises cap utilisation
482
+ // only — lesson prose was redaction-scrubbed at capture — so Autonomous.
483
+ "GET /api/feedback/lessons": RiskTier.Autonomous,
484
+ // Self-tuning verdict endpoint (SELF_TUNING_REVIEW_CYCLE_DESIGN.md §3.4).
485
+ // Autonomous + (Phase 3) mandatory owner DM on apply — the exact pattern
486
+ // that replaced the abolished Notify tier; requiring the Approve bearer
487
+ // would put a human back in every loop iteration and defeat the design.
488
+ // Safety is carried by code, not tier: verdicts may only reference
489
+ // daemon-generated single-use recommendation ids from the current cycle,
490
+ // the handler is idempotent per id, and Phase 2 never actuates (shadow).
491
+ "POST /api/tuning/verdicts": RiskTier.Autonomous,
492
+ // Pending-cycle read — knob names + telemetry counts only, no user prose.
493
+ "GET /api/tuning/pending": RiskTier.Autonomous,
454
494
  // ── Notification ──
455
495
  "/api/notify": RiskTier.Autonomous,
456
496
  // ── External Service Proxy ──
@@ -662,7 +702,7 @@ const API_RISK = {
662
702
  "PATCH /api/delegated-sync/active-hours": RiskTier.Approve,
663
703
  "POST /api/delegated-sync/cadences/": RiskTier.Approve,
664
704
  // INTEGRATION-DRIFT-DETECTION-PLAN.md §6.0 — drift-detection chokepoint.
665
- // Autonomous: the agent's hourly_check delegated variant POSTs the
705
+ // Autonomous: the agent's activity_scan delegated variant POSTs the
666
706
  // result of its connector fetch to compute a structural diff. Defense
667
707
  // layers (window-key allowlist + per-call audit row) live inside the
668
708
  // handler. Daemon-internal callers (CalendarPoller, DelegatedSyncWorker)
@@ -797,13 +837,22 @@ const API_RISK = {
797
837
  "GET /api/skill-curation/signals": RiskTier.Autonomous,
798
838
  "GET /api/skill-curation/knowledge-map": RiskTier.Autonomous,
799
839
  "GET /api/skill-curation/proposals/": RiskTier.Autonomous,
800
- // Mutation endpoints are Autonomous the optimizer agent's runToken
801
- // (HMAC, one-time, scoped to a single run) is the auth surface, enforced
802
- // inside the route. Per design §2.1 the chokepoint applies every passing
803
- // proposal atomically; there are no per-proposal approve/reject/revert
804
- // routes. The only roll-back path is the system-driven auto-revert
805
- // (`auto-revert.ts`), which never crosses the HTTP boundary.
806
- "POST /api/skill-curation/runs": RiskTier.Autonomous,
840
+ // Run minting (`POST /runs`, exact) is Approve. In production this surface
841
+ // is unused the dispatcher's `materializeOptimizerWorkdir` mints the
842
+ // runId/runToken directly and never crosses the HTTP boundary (see the
843
+ // route handler comment). Leaving it Autonomous let any Bearer-less local
844
+ // caller (incl. a prompt-injected DM agent) mint a valid optimizer token
845
+ // once curation is opted in, then drive `/proposals` to self-author skill
846
+ // overlays — a least-privilege violation. Approve confines minting to the
847
+ // dashboard/operator; the legitimate optimizer reads its token from the
848
+ // workdir preamble, not this route.
849
+ "POST /api/skill-curation/runs": RiskTier.Approve,
850
+ // The proposal chokepoint and the per-run finalize stay Autonomous — both
851
+ // are gated by the optimizer's runToken (HMAC, scoped to a single run)
852
+ // enforced inside the route, and the legitimate optimizer agent reaches
853
+ // them via curl from its session workdir (no Bearer). Per design §2.1 the
854
+ // chokepoint applies every passing proposal atomically; the only roll-back
855
+ // path is the system-driven auto-revert (`auto-revert.ts`).
807
856
  "POST /api/skill-curation/proposals": RiskTier.Autonomous,
808
857
  "POST /api/skill-curation/runs/": RiskTier.Autonomous,
809
858
  // P22 §6.1 — settings + listing surfaces consumed by the dashboard.
@@ -878,9 +927,14 @@ export function findExplicitRiskClassification(method, path) {
878
927
  /* c8 ignore stop */
879
928
  if (keyMethod && keyMethod !== method)
880
929
  return false;
881
- return path.startsWith(keyPath);
930
+ return pathPrefixMatches(keyPath, path);
882
931
  })
883
- .sort((a, b) => b[0].length - a[0].length);
932
+ // Rank by matched path-prefix length so the most-specific prefix wins.
933
+ // Must compare the path segment, NOT the raw key: the `"METHOD "` token
934
+ // would otherwise lift a shorter, less-specific method-keyed prefix
935
+ // (e.g. `DELETE /api/git`) above a longer path-only one (`/api/github`),
936
+ // silently downgrading the tier. Mirrors the step-3 pattern tiebreaker.
937
+ .sort((a, b) => keyPathOf(b[0]).length - keyPathOf(a[0]).length);
884
938
  if (candidates.length > 0)
885
939
  return candidates[0][1];
886
940
  return null;
@@ -960,16 +1014,41 @@ function matchesPattern(keyPath, actualPath) {
960
1014
  }
961
1015
  return true;
962
1016
  }
963
- /** Count characters before the first `{*}` (or whole length if none) —
964
- * used to rank pattern candidates by specificity. Defensive against future
965
- * overlapping `{*}` keys: today no two `{*}` entries in `API_RISK` match
966
- * the same `(method, path)` pair, so the sort comparator never invokes
967
- * this helper. The function exists so the first overlapping pair added
968
- * picks the more specific entry instead of relying on iteration order. */
1017
+ /** Extract the path portion of an `API_RISK` key: `"METHOD /path"` `/path`,
1018
+ * or a bare `"/path"` path-only key unchanged. Ranking must compare the path
1019
+ * segment alone the leading `"METHOD "` token (3–6 chars) would otherwise
1020
+ * inflate a shorter, less-specific method-keyed prefix above a longer
1021
+ * path-only one. */
1022
+ export function keyPathOf(key) {
1023
+ return key.includes(" ") ? key.split(" ")[1] : key;
1024
+ }
1025
+ /** Segment-aware prefix test for step-4 (non-`{*}`) keys. A raw
1026
+ * `path.startsWith(keyPath)` matches a *string* prefix, so `/api/git`
1027
+ * would spuriously match the unrelated sibling `/api/git-accounts` (and,
1028
+ * worse, an unclassified future `/api/git-webhook` — silently inheriting
1029
+ * the sibling's tier instead of failing closed to Approve). A key that
1030
+ * ends in `/` is an explicit subtree catch-all, so a raw `startsWith` is
1031
+ * already the boundary; otherwise the match must be a strict
1032
+ * `/`-delimited descendant. Exact `path === keyPath` is pre-empted by the
1033
+ * step-1/step-2 exact lookups, so step 4 only ever matches descendants.
1034
+ * Mirrors the segment discipline of `matchesPattern` (step 3). */
1035
+ function pathPrefixMatches(keyPath, path) {
1036
+ if (keyPath.endsWith("/"))
1037
+ return path.startsWith(keyPath);
1038
+ return path.startsWith(keyPath + "/");
1039
+ }
1040
+ /** Count characters of the path prefix before the first `{*}` (or the whole
1041
+ * path length if none) — used to rank pattern candidates by specificity.
1042
+ * Defensive against future overlapping `{*}` keys: today no two `{*}` entries
1043
+ * in `API_RISK` match the same `(method, path)` pair, so the sort comparator
1044
+ * never invokes this helper. The function exists so the first overlapping
1045
+ * pair added picks the more specific entry instead of relying on iteration
1046
+ * order. */
969
1047
  /* c8 ignore start */
970
1048
  function literalPrefixLength(key) {
971
- const idx = key.indexOf("{*}");
972
- return idx < 0 ? key.length : idx;
1049
+ const keyPath = keyPathOf(key);
1050
+ const idx = keyPath.indexOf("{*}");
1051
+ return idx < 0 ? keyPath.length : idx;
973
1052
  }
974
1053
  /* c8 ignore stop */
975
1054
  /** Strip a trailing `{*}` placeholder from `path` so callers can
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Stage-1 deterministic gate for the three-stage activity_scan funnel
3
+ * (cost-reduction-structural §B). Pure function over a `ActivityScanSignals`
4
+ * snapshot — no DB handle, no clock, no I/O. The dispatcher computes the
5
+ * snapshot via `computeActivityScanSignals`, then asks this module which
6
+ * stage to enter.
7
+ *
8
+ * The four possible decisions:
9
+ * - `stage0_silent`: consume observations, append a single Agent Log
10
+ * line via the daemon-direct writer, return. No LLM call.
11
+ * - `stage2`: lite-tier triage (only when `stage2Enabled`). Strict
12
+ * JSON-only output decides log_only vs escalate.
13
+ * - `stage3`: existing full activity_scan session.
14
+ *
15
+ * HOURLY_CHECK_GATE_REDESIGN_PLAN.md Phase 4 collapsed the gateMode
16
+ * enum (`off`/`shadow`/`live`) into a single execution path. The gate's
17
+ * verdict is always honoured.
18
+ */
19
+ import type { ActivityScanSignals } from "../db/activity-scan-signals.js";
20
+ export type ActivityScanGateStage = "stage0_silent" | "stage2" | "stage3";
21
+ export interface ActivityScanGateConfig {
22
+ /**
23
+ * Hours since the last Stage 3 run after which the gate force-runs at
24
+ * least Stage 2 (or Stage 3 if novelty is high). Bounds the worst case
25
+ * where Stage 0/1 keeps short-circuiting on a quiet day.
26
+ */
27
+ heartbeatHours: number;
28
+ /**
29
+ * When `false`, low-signal cases bypass Stage 2 entirely and route to
30
+ * Stage 3. The cautious default — Stage 2 only takes effect after
31
+ * shadow telemetry validates the decision boundary.
32
+ */
33
+ stage2Enabled: boolean;
34
+ /**
35
+ * Below this threshold, `pendingObsCount` alone does not imply
36
+ * Stage 3. Default 0 — any pending observation hits the low-signal
37
+ * branch which routes to Stage 2 or Stage 3 depending on the flag.
38
+ */
39
+ pendingObsLowSignalCeiling?: number;
40
+ }
41
+ export interface ActivityScanGateDecision {
42
+ stage: ActivityScanGateStage;
43
+ /** Short, telemetry-friendly explanation. */
44
+ reason: string;
45
+ /** Snapshot at decision time (echoed for the audit row). */
46
+ signals: ActivityScanSignals;
47
+ }
48
+ export declare function decideStage(signals: ActivityScanSignals, config: ActivityScanGateConfig): ActivityScanGateDecision;
49
+ /**
50
+ * The dispatcher logs every cron tick to `agent_actions` regardless of
51
+ * which stage runs. Helper that builds the JSON payload from a decision
52
+ * so the schema stays consistent across stages and call-sites.
53
+ */
54
+ export declare function buildGateAuditDetail(decision: ActivityScanGateDecision, extra: {
55
+ appliedDecision: ActivityScanGateStage;
56
+ /** Stage-2 LLM verdict, when Stage 2 ran. */
57
+ stage2Verdict?: "log_only" | "escalate" | "failed";
58
+ /** Forced-run flag from `/api/agent/run-now` etc. */
59
+ forced?: boolean;
60
+ /**
61
+ * HOURLY_CHECK_GATE_REDESIGN_PLAN.md §3.5 — true when pre-pass for
62
+ * any non-direct integration failed in `harvestForGate` and the
63
+ * gate force-escalated to `stage3` regardless of the signal
64
+ * verdict. Surfaced in the audit row so dashboards can flag the
65
+ * cautious-escalate path.
66
+ */
67
+ cautiousEscalate?: boolean;
68
+ /**
69
+ * Original gate verdict captured BEFORE cautious-escalate
70
+ * overwrote it. Persisted as `pre_escalate_gate_*` so dashboards
71
+ * can answer "what would the gate have said if pre-pass had
72
+ * succeeded?" — distinguishes a tick that was structurally
73
+ * stage3 anyway from one that was forced up from stage0_silent
74
+ * by a transient fetch outage.
75
+ */
76
+ preEscalateGateStage?: ActivityScanGateStage;
77
+ preEscalateGateReason?: string;
78
+ }): Record<string, unknown>;
79
+ /**
80
+ * Build the `<gate_decision>` block injected into Stage 3's prompt so
81
+ * the routine knows *why* it was escalated and can prioritize.
82
+ */
83
+ export declare function renderGateDecisionBlock(decision: ActivityScanGateDecision, extra: {
84
+ forced?: boolean;
85
+ cautiousEscalate?: boolean;
86
+ }): string;
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Stage-1 deterministic gate for the three-stage activity_scan funnel
3
+ * (cost-reduction-structural §B). Pure function over a `ActivityScanSignals`
4
+ * snapshot — no DB handle, no clock, no I/O. The dispatcher computes the
5
+ * snapshot via `computeActivityScanSignals`, then asks this module which
6
+ * stage to enter.
7
+ *
8
+ * The four possible decisions:
9
+ * - `stage0_silent`: consume observations, append a single Agent Log
10
+ * line via the daemon-direct writer, return. No LLM call.
11
+ * - `stage2`: lite-tier triage (only when `stage2Enabled`). Strict
12
+ * JSON-only output decides log_only vs escalate.
13
+ * - `stage3`: existing full activity_scan session.
14
+ *
15
+ * HOURLY_CHECK_GATE_REDESIGN_PLAN.md Phase 4 collapsed the gateMode
16
+ * enum (`off`/`shadow`/`live`) into a single execution path. The gate's
17
+ * verdict is always honoured.
18
+ */
19
+ const HIGH_NOVELTY_FLOOR = 3;
20
+ const ESCALATE_NOVELTY_FLOOR = 2;
21
+ export function decideStage(signals, config) {
22
+ // Heartbeat: even on quiet days, exercise Stage 2/3 every N hours so
23
+ // the gate's signal compute is exercised end-to-end. We pick Stage 3
24
+ // when there is *some* novelty pending, Stage 2 otherwise — Stage 2
25
+ // is the cheaper lite-tier shape and Stage 3 wastes context if there
26
+ // is nothing to act on. If Stage 2 is disabled, the cautious fallback
27
+ // is Stage 3.
28
+ if (signals.hoursSinceLastStage3Run >= config.heartbeatHours) {
29
+ if (numericNovelty(signals.maxNoveltyScore) >= ESCALATE_NOVELTY_FLOOR) {
30
+ return decision("stage3", "heartbeat_due_with_novelty", signals);
31
+ }
32
+ return decision(config.stage2Enabled ? "stage2" : "stage3", "heartbeat_due", signals);
33
+ }
34
+ // Hard escalate: any high-priority signal goes straight to Stage 3.
35
+ if (numericNovelty(signals.maxNoveltyScore) >= HIGH_NOVELTY_FLOOR) {
36
+ return decision("stage3", "high_novelty", signals);
37
+ }
38
+ if (signals.calendarHasConflict) {
39
+ return decision("stage3", "calendar_conflict", signals);
40
+ }
41
+ if (signals.vipMailUnreadCount > 0) {
42
+ return decision("stage3", "vip_mail_unread", signals);
43
+ }
44
+ if (signals.agentPlanOverdueCount > 0) {
45
+ return decision("stage3", "agent_plan_overdue", signals);
46
+ }
47
+ if (signals.scheduleApproachingCount > 0) {
48
+ return decision("stage3", "schedule_approaching", signals);
49
+ }
50
+ // No signals at all → Stage 0 silent.
51
+ if (signals.pendingObsCount === 0 && !signals.calendarHas24hChange) {
52
+ return decision("stage0_silent", "no_signals", signals);
53
+ }
54
+ // Low signals only → Stage 2 if enabled, else Stage 3 (cautious default).
55
+ // The pendingObsLowSignalCeiling lets an operator widen the silent-skip
56
+ // band ("at most N noise observations is still nothing to act on"); the
57
+ // default 0 leaves the design's conservative posture intact.
58
+ const ceiling = Math.max(0, config.pendingObsLowSignalCeiling ?? 0);
59
+ if (signals.pendingObsCount <= ceiling
60
+ && !signals.calendarHas24hChange) {
61
+ return decision("stage0_silent", "low_signal_under_ceiling", signals);
62
+ }
63
+ return decision(config.stage2Enabled ? "stage2" : "stage3", "low_signal_default", signals);
64
+ }
65
+ /**
66
+ * The dispatcher logs every cron tick to `agent_actions` regardless of
67
+ * which stage runs. Helper that builds the JSON payload from a decision
68
+ * so the schema stays consistent across stages and call-sites.
69
+ */
70
+ export function buildGateAuditDetail(decision, extra) {
71
+ return {
72
+ stage_reached: extra.appliedDecision,
73
+ gate_stage: decision.stage,
74
+ gate_reason: decision.reason,
75
+ forced: extra.forced ?? false,
76
+ ...(extra.stage2Verdict ? { stage2_verdict: extra.stage2Verdict } : {}),
77
+ ...(extra.cautiousEscalate ? { cautious_escalate: true } : {}),
78
+ ...(extra.preEscalateGateStage
79
+ ? { pre_escalate_gate_stage: extra.preEscalateGateStage }
80
+ : {}),
81
+ ...(extra.preEscalateGateReason
82
+ ? { pre_escalate_gate_reason: extra.preEscalateGateReason }
83
+ : {}),
84
+ signal_snapshot: {
85
+ pendingObsCount: decision.signals.pendingObsCount,
86
+ maxNoveltyScore: decision.signals.maxNoveltyScore,
87
+ noveltyDistribution: decision.signals.noveltyDistribution,
88
+ vipMailUnreadCount: decision.signals.vipMailUnreadCount,
89
+ calendarHas24hChange: decision.signals.calendarHas24hChange,
90
+ calendarHasConflict: decision.signals.calendarHasConflict,
91
+ agentPlanOverdueCount: decision.signals.agentPlanOverdueCount,
92
+ scheduleApproachingCount: decision.signals.scheduleApproachingCount,
93
+ hoursSinceLastStage3Run: serializeHours(decision.signals.hoursSinceLastStage3Run),
94
+ },
95
+ };
96
+ }
97
+ /**
98
+ * Build the `<gate_decision>` block injected into Stage 3's prompt so
99
+ * the routine knows *why* it was escalated and can prioritize.
100
+ */
101
+ export function renderGateDecisionBlock(decision, extra) {
102
+ return [
103
+ "<gate_decision>",
104
+ ` triggered_by: ${decision.stage === "stage3" ? "stage1" : "stage2_escalation"}`,
105
+ ` reason: ${decision.reason}`,
106
+ ` forced: ${extra.forced ? "true" : "false"}`,
107
+ ...(extra.cautiousEscalate ? [" cautious_escalate: true"] : []),
108
+ ` signals_snapshot: ${JSON.stringify({
109
+ maxNovelty: decision.signals.maxNoveltyScore,
110
+ pendingObs: decision.signals.pendingObsCount,
111
+ vipMail: decision.signals.vipMailUnreadCount,
112
+ calConflict: decision.signals.calendarHasConflict,
113
+ agentPlanOverdue: decision.signals.agentPlanOverdueCount,
114
+ scheduleApproaching: decision.signals.scheduleApproachingCount,
115
+ })}`,
116
+ "</gate_decision>",
117
+ ].join("\n");
118
+ }
119
+ function numericNovelty(score) {
120
+ // Cautious default — null (no summary done yet) is treated as 2 so a
121
+ // backlog of unsummarized observations does NOT silently skip the
122
+ // routine. The design doc calls this out explicitly under "edge cases".
123
+ return score ?? ESCALATE_NOVELTY_FLOOR;
124
+ }
125
+ function decision(stage, reason, signals) {
126
+ return { stage, reason, signals };
127
+ }
128
+ function serializeHours(value) {
129
+ if (!Number.isFinite(value))
130
+ return null;
131
+ return Number(value.toFixed(2));
132
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Background-task budget envelope — BACKGROUND_TASK_RUNNER_DESIGN.md §6 / §10.1.
3
+ *
4
+ * Pure resolution of the `(modelId, maxTurns, maxBudgetUsd,
5
+ * executeTimeoutMinutes)` envelope for a background-task worker from
6
+ * three inputs:
7
+ *
8
+ * 1. The per-task `tier` (`lite` | `medium` | `high`) — selects the
9
+ * base turn/budget/timeout envelope.
10
+ * 2. The optional per-task `maxBudgetUsd` override (POST body).
11
+ * 3. The operator-editable `process_backend_config` row for
12
+ * `process_key='background_task'` (model + caps) — same chokepoint
13
+ * as browser-task's `loadBrowserTaskBackendBinding`.
14
+ *
15
+ * The worker is Claude-only (it drives the Claude Agent SDK `query()`
16
+ * loop directly, like browser_task). A `process_backend_config` row that
17
+ * pins a non-Claude backend is refused with `backend_misconfigured` so a
18
+ * mis-set `/settings/models` row fails fast rather than silently doing
19
+ * nothing.
20
+ *
21
+ * 100% coverage gate — the I/O (DB read) lives in
22
+ * `loadBackgroundTaskBinding`; this module is the pure arithmetic.
23
+ */
24
+ import type { BackgroundTaskTier } from "../../db/background-task-store.js";
25
+ /** Fallback model when the `process_backend_config` row is missing or
26
+ * carries an empty `main_model`. Mirrors the seed default. */
27
+ export declare const BACKGROUND_TASK_FALLBACK_CLAUDE_MODEL = "claude-sonnet-4-6";
28
+ export declare const BACKGROUND_TASK_DEFAULT_TIER: BackgroundTaskTier;
29
+ /** Hard upper bounds. The seed sits well below; the caps give the
30
+ * operator room to relax via `/settings/models` while pinning a ceiling
31
+ * no per-task override can blow past. Background tasks are the
32
+ * long-running surface, so the turn / timeout ceilings are far higher
33
+ * than browser-task's (60 turns / 5 min) — a deep research or
34
+ * multi-repo audit legitimately runs for many turns over many minutes. */
35
+ export declare const BACKGROUND_TASK_MAX_TURNS_CAP = 120;
36
+ export declare const BACKGROUND_TASK_MAX_BUDGET_USD_CAP = 15;
37
+ export declare const BACKGROUND_TASK_MAX_EXECUTE_TIMEOUT_MINUTES = 120;
38
+ export interface BackgroundTaskEnvelope {
39
+ modelId: string;
40
+ maxTurns: number;
41
+ maxBudgetUsd: number;
42
+ executeTimeoutMinutes: number;
43
+ }
44
+ interface TierEnvelope {
45
+ maxTurns: number;
46
+ maxBudgetUsd: number;
47
+ executeTimeoutMinutes: number;
48
+ }
49
+ export declare function tierEnvelope(tier: BackgroundTaskTier): TierEnvelope;
50
+ /** Shape of the operator-editable `process_backend_config` row, already
51
+ * read from the DB (or null when absent). */
52
+ export interface BackgroundTaskProcessConfig {
53
+ mainBackend: string;
54
+ mainModel: string | null;
55
+ maxTurns: number | null;
56
+ maxBudgetUsd: number | null;
57
+ }
58
+ export interface ResolveEnvelopeInput {
59
+ tier: BackgroundTaskTier | null;
60
+ /** Per-task budget override (POST body). Clamped to the hard cap. */
61
+ maxBudgetUsd: number | null;
62
+ processConfig: BackgroundTaskProcessConfig | null;
63
+ }
64
+ export type ResolveEnvelopeResult = {
65
+ ok: true;
66
+ envelope: BackgroundTaskEnvelope;
67
+ } | {
68
+ ok: false;
69
+ reason: "backend_misconfigured";
70
+ detail: string;
71
+ };
72
+ /**
73
+ * Pure envelope resolution. Branches:
74
+ * - processConfig present + `mainBackend !== 'claude'` → REFUSE.
75
+ * - otherwise: model from the config (or fallback); turns from the
76
+ * config (or tier base), clamped; budget = per-task override ?? config
77
+ * budget ?? tier base, clamped; timeout from the tier base, clamped.
78
+ */
79
+ export declare function resolveBackgroundTaskEnvelope(input: ResolveEnvelopeInput): ResolveEnvelopeResult;
80
+ export {};
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Background-task budget envelope — BACKGROUND_TASK_RUNNER_DESIGN.md §6 / §10.1.
3
+ *
4
+ * Pure resolution of the `(modelId, maxTurns, maxBudgetUsd,
5
+ * executeTimeoutMinutes)` envelope for a background-task worker from
6
+ * three inputs:
7
+ *
8
+ * 1. The per-task `tier` (`lite` | `medium` | `high`) — selects the
9
+ * base turn/budget/timeout envelope.
10
+ * 2. The optional per-task `maxBudgetUsd` override (POST body).
11
+ * 3. The operator-editable `process_backend_config` row for
12
+ * `process_key='background_task'` (model + caps) — same chokepoint
13
+ * as browser-task's `loadBrowserTaskBackendBinding`.
14
+ *
15
+ * The worker is Claude-only (it drives the Claude Agent SDK `query()`
16
+ * loop directly, like browser_task). A `process_backend_config` row that
17
+ * pins a non-Claude backend is refused with `backend_misconfigured` so a
18
+ * mis-set `/settings/models` row fails fast rather than silently doing
19
+ * nothing.
20
+ *
21
+ * 100% coverage gate — the I/O (DB read) lives in
22
+ * `loadBackgroundTaskBinding`; this module is the pure arithmetic.
23
+ */
24
+ /** Fallback model when the `process_backend_config` row is missing or
25
+ * carries an empty `main_model`. Mirrors the seed default. */
26
+ export const BACKGROUND_TASK_FALLBACK_CLAUDE_MODEL = "claude-sonnet-4-6";
27
+ export const BACKGROUND_TASK_DEFAULT_TIER = "medium";
28
+ /** Hard upper bounds. The seed sits well below; the caps give the
29
+ * operator room to relax via `/settings/models` while pinning a ceiling
30
+ * no per-task override can blow past. Background tasks are the
31
+ * long-running surface, so the turn / timeout ceilings are far higher
32
+ * than browser-task's (60 turns / 5 min) — a deep research or
33
+ * multi-repo audit legitimately runs for many turns over many minutes. */
34
+ export const BACKGROUND_TASK_MAX_TURNS_CAP = 120;
35
+ export const BACKGROUND_TASK_MAX_BUDGET_USD_CAP = 15.0;
36
+ export const BACKGROUND_TASK_MAX_EXECUTE_TIMEOUT_MINUTES = 120;
37
+ /** Per-tier base envelope. `process_backend_config` overrides
38
+ * model/turns/budget; the tier is the source of the execute-timeout
39
+ * (which has no `process_backend_config` column) and the fallback when
40
+ * the config row is absent. */
41
+ const TIER_ENVELOPES = {
42
+ lite: { maxTurns: 15, maxBudgetUsd: 0.5, executeTimeoutMinutes: 10 },
43
+ medium: { maxTurns: 40, maxBudgetUsd: 2.0, executeTimeoutMinutes: 30 },
44
+ high: { maxTurns: 80, maxBudgetUsd: 8.0, executeTimeoutMinutes: 60 },
45
+ };
46
+ export function tierEnvelope(tier) {
47
+ return TIER_ENVELOPES[tier];
48
+ }
49
+ function clampPositive(value, cap, fallback) {
50
+ if (!Number.isFinite(value) || value <= 0)
51
+ return fallback;
52
+ return Math.min(value, cap);
53
+ }
54
+ /**
55
+ * Pure envelope resolution. Branches:
56
+ * - processConfig present + `mainBackend !== 'claude'` → REFUSE.
57
+ * - otherwise: model from the config (or fallback); turns from the
58
+ * config (or tier base), clamped; budget = per-task override ?? config
59
+ * budget ?? tier base, clamped; timeout from the tier base, clamped.
60
+ */
61
+ export function resolveBackgroundTaskEnvelope(input) {
62
+ const tier = input.tier ?? BACKGROUND_TASK_DEFAULT_TIER;
63
+ const base = TIER_ENVELOPES[tier];
64
+ const cfg = input.processConfig;
65
+ if (cfg && cfg.mainBackend !== "claude") {
66
+ return {
67
+ ok: false,
68
+ reason: "backend_misconfigured",
69
+ detail: `process_backend_config.main_backend='${cfg.mainBackend}' — background_task drives the Claude Agent SDK directly; refusing to dispatch. Set background_task back to claude in /settings/models.`,
70
+ };
71
+ }
72
+ const modelId = cfg && typeof cfg.mainModel === "string" && cfg.mainModel.length > 0
73
+ ? cfg.mainModel
74
+ : BACKGROUND_TASK_FALLBACK_CLAUDE_MODEL;
75
+ const rawTurns = cfg && typeof cfg.maxTurns === "number" && Number.isFinite(cfg.maxTurns)
76
+ ? Math.max(1, Math.floor(cfg.maxTurns))
77
+ : base.maxTurns;
78
+ const maxTurns = Math.min(rawTurns, BACKGROUND_TASK_MAX_TURNS_CAP);
79
+ // Per-task override wins, then the operator config, then the tier base.
80
+ const budgetSource = input.maxBudgetUsd != null
81
+ ? input.maxBudgetUsd
82
+ : cfg && cfg.maxBudgetUsd != null
83
+ ? cfg.maxBudgetUsd
84
+ : base.maxBudgetUsd;
85
+ const maxBudgetUsd = clampPositive(budgetSource, BACKGROUND_TASK_MAX_BUDGET_USD_CAP, base.maxBudgetUsd);
86
+ const executeTimeoutMinutes = Math.min(base.executeTimeoutMinutes, BACKGROUND_TASK_MAX_EXECUTE_TIMEOUT_MINUTES);
87
+ return {
88
+ ok: true,
89
+ envelope: { modelId, maxTurns, maxBudgetUsd, executeTimeoutMinutes },
90
+ };
91
+ }
@@ -0,0 +1,105 @@
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 type Database from "better-sqlite3";
30
+ import { type BackgroundTaskRow } from "../../db/background-task-store.js";
31
+ import { type BackgroundTaskEnvelope, type BackgroundTaskProcessConfig } from "./background-task-budget.js";
32
+ import { type BackgroundTaskRuntime } from "./background-task-tools.js";
33
+ import { type BackgroundTaskTransitionEmitter } from "./background-task-transition-events.js";
34
+ /** Read the operator-editable envelope row for `background_task`. */
35
+ export declare function loadBackgroundTaskProcessConfig(db: Database.Database): BackgroundTaskProcessConfig | null;
36
+ export interface DriverDeps {
37
+ db: Database.Database;
38
+ paDataDir: string;
39
+ /** Workspace dir root — resolves the agent-profile MD. */
40
+ workspaceDir: string;
41
+ transitionEmitter?: BackgroundTaskTransitionEmitter;
42
+ /** Vault root for the worker's `read_memory` tool. */
43
+ contextDir: string;
44
+ /** Clarification TTL in ms (`backgroundTaskClarificationTtlMinutes`). */
45
+ clarificationTtlMs: number;
46
+ /** Override for tests; production wires `() => Date.now()`. */
47
+ nowFn?: () => number;
48
+ }
49
+ export interface DriverHandle {
50
+ abortController: AbortController;
51
+ cwd: string;
52
+ runtime: BackgroundTaskRuntime;
53
+ sdkSessionId: string | null;
54
+ binding: BackgroundTaskEnvelope;
55
+ }
56
+ export interface DriverRunResult {
57
+ outcome: "completed" | "yielded_for_clarification" | "no_finish" | "max_turns_exceeded" | "budget_exceeded" | "timeout" | "cancelled" | "sdk_error" | "resume_unavailable" | "backend_misconfigured";
58
+ sdkSessionId: string | null;
59
+ detail?: string | null;
60
+ costUsd: number;
61
+ numTurns: number;
62
+ durationMs: number;
63
+ }
64
+ /**
65
+ * Acquire a fresh workdir + runtime + binding for `row`. The runner
66
+ * calls this BEFORE `runDriver` so it can stash the handle in its parked
67
+ * map before any turn fires (an ask_user on the first turn must find the
68
+ * handle already there).
69
+ */
70
+ export declare function prepareDriverHandle(input: {
71
+ deps: DriverDeps;
72
+ row: BackgroundTaskRow;
73
+ }): Promise<{
74
+ ok: true;
75
+ handle: DriverHandle;
76
+ } | {
77
+ ok: false;
78
+ reason: DriverRunResult["outcome"];
79
+ detail?: string;
80
+ }>;
81
+ /** Drive the initial turn — the worker reads the brief and works. */
82
+ export declare function runDriver(deps: DriverDeps, row: BackgroundTaskRow, handle: DriverHandle): Promise<DriverRunResult>;
83
+ /** Resume a parked task after `/clarify` lands the owner's answer. Uses
84
+ * the persisted SDK session id so the prompt cache stays warm. Works both
85
+ * in-process (warm parked handle) and across a daemon restart (the runner
86
+ * reconstructs the handle from the persisted `backend_session_id`); in the
87
+ * cross-restart case a session the SDK can no longer load surfaces as
88
+ * `resume_unavailable`. */
89
+ export declare function resumeDriver(deps: DriverDeps, row: BackgroundTaskRow, handle: DriverHandle, userAnswer: string): Promise<DriverRunResult>;
90
+ /**
91
+ * BACKGROUND_TASK_RUNNER_DESIGN.md §10.2 / Phase 4 — resume a task that was
92
+ * mid-execution when the daemon restarted, using the persisted SDK session
93
+ * id (`backend_session_id`) so the warm transcript + prompt cache survive
94
+ * the restart instead of re-running the brief from scratch. The runner
95
+ * reconstructs the handle (`prepareDriverHandle` recreates the per-task
96
+ * workdir + sets `sdkSessionId` from the row). When the SDK can no longer
97
+ * load the session, this returns `resume_unavailable` and the runner falls
98
+ * back to re-dispatch-from-brief — so resume is a pure optimization with no
99
+ * regression.
100
+ */
101
+ export declare function resumeFromBootDriver(deps: DriverDeps, row: BackgroundTaskRow, handle: DriverHandle): Promise<DriverRunResult>;
102
+ /** Remove the per-task workdir. Idempotent. Parked tasks (awaiting_user)
103
+ * do NOT call this — the runner keeps the handle in its parked map so
104
+ * /clarify can resume the warm SDK session. */
105
+ export declare function releaseDriverHandle(deps: DriverDeps, handle: DriverHandle): Promise<void>;