@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
package/dist/db/schema.js CHANGED
@@ -107,7 +107,7 @@ CREATE TABLE IF NOT EXISTS agent_actions (
107
107
  -- 'trigger' — fired by an automation_triggers row
108
108
  -- 'message' — owner DM / mention / dashboard chat
109
109
  -- 'cron' — built-in routine (morning, hourly, evening, ...)
110
- -- 'observation' — hourly-check consumed pending observations
110
+ -- 'observation' — activity-scan consumed pending observations
111
111
  -- 'manual' — Run-now / dashboard-driven invocation
112
112
  -- NULL — legacy / not yet classified
113
113
  -- source_ref is the id of the upstream entity (e.g. trigger id).
@@ -447,9 +447,9 @@ CREATE TABLE IF NOT EXISTS observations (
447
447
  -- cost-reduction-structural section A -- pre-summarization at insert time.
448
448
  -- The summarizer worker (observers/observation-summarizer.ts) drains
449
449
  -- pending rows asynchronously and populates these columns; the
450
- -- hourly_check skill consumes the summary instead of fetching raw
450
+ -- activity_scan skill consumes the summary instead of fetching raw
451
451
  -- content unless novelty_score >= 2. NULL novelty_score + non-
452
- -- 'done' status means hourly_check falls back to legacy fetch-on-doubt.
452
+ -- 'done' status means activity_scan falls back to legacy fetch-on-doubt.
453
453
  summary_text TEXT,
454
454
  novelty_score INTEGER CHECK (novelty_score IS NULL OR (novelty_score >= 0 AND novelty_score <= 3)),
455
455
  summary_at TEXT,
@@ -1217,7 +1217,16 @@ CREATE TABLE IF NOT EXISTS browser_research_clusters (
1217
1217
  last_wiki_offer_at INTEGER,
1218
1218
  research_offer_accepted_at INTEGER,
1219
1219
  wiki_summary_written_at INTEGER,
1220
- agent_summary_revision INTEGER DEFAULT 0
1220
+ agent_summary_revision INTEGER DEFAULT 0,
1221
+ -- RESEARCH_CLUSTER_COST_FIX_PLAN.md F1 — local agent-day label
1222
+ -- ('YYYY-MM-DD', see getAgentDayDateStr) on which the day-boundary
1223
+ -- fan-out last enqueued routine.research_cluster_update for this
1224
+ -- cluster. Stamped BEFORE the EventBus put so a replayed
1225
+ -- day-boundary callback (wake catch-up fires on every sleep gap
1226
+ -- >= 5 min) cannot re-enqueue the same cluster within one agent
1227
+ -- day. NULL = never enqueued. Pre-existing installs get the column
1228
+ -- via migration 0011.
1229
+ journal_update_enqueued_on TEXT
1221
1230
  );
1222
1231
 
1223
1232
  CREATE TABLE IF NOT EXISTS browser_pending_offers (
@@ -1651,7 +1660,12 @@ CREATE TABLE IF NOT EXISTS browser_task (
1651
1660
  CHECK (extract_chars_total >= 0),
1652
1661
  created_at INTEGER NOT NULL,
1653
1662
  started_at INTEGER,
1654
- finished_at INTEGER
1663
+ finished_at INTEGER,
1664
+ -- BACKGROUND_TASK_RUNNER_DESIGN.md Phase 1 — browser-task reports are
1665
+ -- delivered through the shared task.delivery boundary. This timestamp
1666
+ -- is the recovery key for completed reports whose worker stored the
1667
+ -- report before the DM was sent/recorded.
1668
+ delivered_at INTEGER
1655
1669
  );
1656
1670
  CREATE INDEX IF NOT EXISTS idx_browser_task_created_at
1657
1671
  ON browser_task(created_at DESC);
@@ -1704,6 +1718,10 @@ CREATE TABLE IF NOT EXISTS browser_task_clarifications (
1704
1718
  -- sweeps overdue rows on the same 30 s tick that handles the
1705
1719
  -- pending-queue timeout.
1706
1720
  deadline_at INTEGER NOT NULL,
1721
+ -- Phase 1 task.delivery recovery key for ask_user rows. Null means the
1722
+ -- clarification has not yet been surfaced through the gated delivery
1723
+ -- boundary; the deadline scanner still owns answer expiry separately.
1724
+ delivered_at INTEGER,
1707
1725
  answer TEXT,
1708
1726
  answered_at INTEGER,
1709
1727
  resolved INTEGER NOT NULL DEFAULT 0
@@ -1774,6 +1792,166 @@ CREATE INDEX IF NOT EXISTS idx_final_confirm_tokens_status_expires
1774
1792
  CREATE INDEX IF NOT EXISTS idx_final_confirm_tokens_task
1775
1793
  ON browser_task_final_confirm_tokens(task_id);
1776
1794
 
1795
+ -- ── Background-task runner (BACKGROUND_TASK_RUNNER_DESIGN.md §6) ──────────
1796
+ --
1797
+ -- Generic detached-task surface — the browser-task lifecycle skeleton
1798
+ -- (state machine, clarifications store, slot manager, parked-resume,
1799
+ -- delivery boundary) minus the Playwright driver. A worker is a fresh
1800
+ -- Agent-SDK session seeded with a self-contained 'brief'; on completion
1801
+ -- (or when it needs input) it WRITES AN ARTIFACT to this row via its
1802
+ -- 'finish' / 'ask_user' tools rather than DMing directly. The runner's
1803
+ -- delivery boundary reads the 'notify' disposition and, when true, the
1804
+ -- REAL DM agent delivers (active delivery turn) or a no-model 'draft'
1805
+ -- send (idle) through the shared 'task.delivery' path.
1806
+ --
1807
+ -- Separate table (Decision 7) — 'browser_task' carries a large browser-
1808
+ -- specific column set (site_key, allowlist regex, blocked-request /
1809
+ -- extract counters, final-confirm gate) that does not generalize, and
1810
+ -- browser_task is experimental. The artifact fields ('report', 'draft',
1811
+ -- 'notify', 'significance', 'artifact_path', 'notification_policy',
1812
+ -- 'delivered_at') are the genuinely new shape this design adds.
1813
+ --
1814
+ -- Boot-recovery (§10.2): unlike browser_task (whose in-memory
1815
+ -- BrowserContext is unrecoverable, so rows are force-failed), background
1816
+ -- tasks are RE-DISPATCHED FROM 'brief' on restart — they are precisely
1817
+ -- the long-lived tasks likely to span a restart, and the brief is
1818
+ -- self-contained. The event-pipeline boot hook resets non-terminal rows
1819
+ -- to 'pending' and re-runs them once the runner is wired.
1820
+ CREATE TABLE IF NOT EXISTS background_task (
1821
+ -- uuid v4 (crypto.randomUUID).
1822
+ id TEXT PRIMARY KEY,
1823
+ -- Self-contained worker prompt: objective + scope + the inputs the
1824
+ -- task needs + the output-language directive + persona hints for the
1825
+ -- 'draft' + the notification policy / (if_significant) criteria. A
1826
+ -- 'settingSources:["project"]' worker sees ONLY this, so an
1827
+ -- under-specified brief → clarification ping-pong (§9). Capped
1828
+ -- 1..16384 at the route layer.
1829
+ brief TEXT NOT NULL,
1830
+ -- Short label for status / listing / delivery. Optional; the route
1831
+ -- derives one from the brief when absent.
1832
+ title TEXT,
1833
+ -- State machine — mirrors browser_task minus final_confirm/abandoned.
1834
+ -- 'awaiting_user' parks for a clarification; the slot stays held.
1835
+ state TEXT NOT NULL DEFAULT 'pending'
1836
+ CHECK (state IN (
1837
+ 'pending',
1838
+ 'running',
1839
+ 'awaiting_user',
1840
+ 'completed',
1841
+ 'failed',
1842
+ 'timeout',
1843
+ 'cancelled'
1844
+ )),
1845
+ -- The spawn-time notification policy the worker evaluates at finish
1846
+ -- time (§4.3). 'always' (the common case) — notify even on a "0
1847
+ -- issues" result. 'if_significant' — notify iff the concrete criteria
1848
+ -- written into the brief are met. 'silent' — file only.
1849
+ notification_policy TEXT NOT NULL DEFAULT 'always'
1850
+ CHECK (notification_policy IN ('always', 'if_significant', 'silent')),
1851
+ -- Phase 4 if_significant criteria DSL (§4.3): a JSON array of concrete,
1852
+ -- atomic conditions the worker checks one-by-one against its result
1853
+ -- ("any repo main build is red", "total spend > 100"). Authored by the
1854
+ -- DM agent at spawn for 'if_significant' tasks; the driver injects them
1855
+ -- as a numbered checklist and the worker sets notify=true iff ANY is
1856
+ -- met (and records which in significance). NULL / empty array means the
1857
+ -- worker falls back to the prose criteria in the brief (back-compat).
1858
+ significance_criteria TEXT,
1859
+ -- ── ARTIFACT (written by the worker's finish tool) ───────────────
1860
+ -- Full verbatim result (findings, numbers, URLs, IDs) — the fidelity
1861
+ -- anchor, never paraphrased by a weak model. Null until finished. On
1862
+ -- the fail-loud path (worker died before finish) the runner
1863
+ -- synthesizes a failure note here.
1864
+ report TEXT,
1865
+ -- Worker-authored plain-language summary in the owner's language.
1866
+ -- NOT the final DM — it is the body for the idle direct-send path,
1867
+ -- grounding material for the active delivery turn, and the fallback
1868
+ -- if a delivery turn errors. Null until finished.
1869
+ draft TEXT,
1870
+ -- Worker disposition vs the policy: 1 ⇒ surface, 0 ⇒ file only. NULL
1871
+ -- until finished. The recovery sweep selects 'notify = 1 AND
1872
+ -- delivered_at IS NULL'.
1873
+ notify INTEGER,
1874
+ -- Worker note: why notify is true/false (digest + audit). Free-form.
1875
+ significance TEXT,
1876
+ -- Optional vault MD path for research-type output (reuses the
1877
+ -- obsidian 30_outputs/ pattern). Null for quick tasks whose row
1878
+ -- fields suffice.
1879
+ artifact_path TEXT,
1880
+ -- ── lifecycle ────────────────────────────────────────────────────
1881
+ -- Free-form categorical detail on non-success terminals. Examples:
1882
+ -- 'daemon_restarted', 'budget_exceeded', 'max_turns_exceeded',
1883
+ -- 'execute_timeout', 'clarification_deadline', 'queue_timeout',
1884
+ -- 'sdk_error', 'backend_misconfigured', 'runner_unavailable'.
1885
+ outcome_detail TEXT,
1886
+ -- "<platform>:<channel_id>". NULL when no DM channel is associable
1887
+ -- (synthetic / channel-less runs) — the artifact is still filed but
1888
+ -- there is nowhere to deliver it.
1889
+ originating_channel TEXT,
1890
+ -- Links to the spawning owner-DM turn / correlation chain.
1891
+ correlation_id TEXT,
1892
+ -- FK to agent_schedule when the task was inserted via 'scheduleAt'.
1893
+ -- ON DELETE SET NULL so deleting a schedule row leaves history intact.
1894
+ schedule_row_id INTEGER REFERENCES agent_schedule(id) ON DELETE SET NULL,
1895
+ -- Budget envelope inputs. 'tier' (lite|medium|high) selects the
1896
+ -- default turn/budget/timeout envelope; 'max_budget_usd' is an
1897
+ -- optional per-task override, clamped to the hard cap at run time.
1898
+ tier TEXT
1899
+ CHECK (tier IS NULL OR tier IN ('lite', 'medium', 'high')),
1900
+ max_budget_usd REAL,
1901
+ -- Parked worker SDK session id (resume after /clarify). Captured from
1902
+ -- the init message; null until the first turn streams.
1903
+ backend_session_id TEXT,
1904
+ created_at INTEGER NOT NULL,
1905
+ started_at INTEGER,
1906
+ finished_at INTEGER,
1907
+ -- NULL until delivered; the recovery-sweep key (§10.2).
1908
+ delivered_at INTEGER
1909
+ );
1910
+ CREATE INDEX IF NOT EXISTS idx_background_task_state
1911
+ ON background_task(state);
1912
+ CREATE INDEX IF NOT EXISTS idx_background_task_corr
1913
+ ON background_task(correlation_id);
1914
+ -- Powers the delivery recovery sweep (completed & notify=1 &
1915
+ -- delivered_at IS NULL) without scanning every historical row.
1916
+ CREATE INDEX IF NOT EXISTS idx_background_task_delivery
1917
+ ON background_task(state, notify, delivered_at);
1918
+ CREATE INDEX IF NOT EXISTS idx_background_task_created_at
1919
+ ON background_task(created_at DESC);
1920
+ -- The non-terminal index powers the boot re-dispatch sweep + the
1921
+ -- "needs attention" / status queries.
1922
+ CREATE INDEX IF NOT EXISTS idx_background_task_non_terminal
1923
+ ON background_task(state)
1924
+ WHERE state IN ('pending', 'running', 'awaiting_user');
1925
+
1926
+ -- Mirror of browser_task_clarifications (CAS-resolve, TTL). Clarifications
1927
+ -- are always notify=true (the task cannot proceed without an answer), so
1928
+ -- there is no policy column. The TTL default is longer than browser-task's
1929
+ -- 5 min (no browser resource is held while parked).
1930
+ CREATE TABLE IF NOT EXISTS background_task_clarifications (
1931
+ -- uuid v4 == clarificationId.
1932
+ id TEXT PRIMARY KEY,
1933
+ task_id TEXT NOT NULL REFERENCES background_task(id) ON DELETE CASCADE,
1934
+ -- Worker-authored; the delivery turn weaves it / the idle send DMs it.
1935
+ question TEXT NOT NULL,
1936
+ context_summary TEXT,
1937
+ asked_at INTEGER NOT NULL,
1938
+ -- asked_at + configurable TTL (backgroundTaskClarificationTtlMinutes).
1939
+ deadline_at INTEGER NOT NULL,
1940
+ -- task.delivery recovery key for ask_user rows.
1941
+ delivered_at INTEGER,
1942
+ answer TEXT,
1943
+ answered_at INTEGER,
1944
+ resolved INTEGER NOT NULL DEFAULT 0
1945
+ CHECK (resolved IN (0, 1))
1946
+ );
1947
+ CREATE INDEX IF NOT EXISTS idx_bgtask_clar_task
1948
+ ON background_task_clarifications(task_id);
1949
+ -- Partial index over the unresolved set — the deadline scanner sweeps
1950
+ -- this on the same housekeeping tick that handles delivery recovery.
1951
+ CREATE INDEX IF NOT EXISTS idx_bgtask_clar_unresolved
1952
+ ON background_task_clarifications(deadline_at)
1953
+ WHERE resolved = 0;
1954
+
1777
1955
  -- INTEGRATION-DRIFT-PHASE-7-PLAN.md §3.2 — persistent dedup for the
1778
1956
  -- 15-minute imminent-meeting reminder. Pre-Phase-7 the scheduler kept
1779
1957
  -- an in-memory Set, which was lost on every daemon restart and re-DMed
@@ -2066,7 +2244,7 @@ VALUES
2066
2244
  -- Per-process seeds. Two tiers are wired in at install time:
2067
2245
  -- - Sonnet (DEFAULT_CLAUDE_MEDIUM_MODEL) for "main agent work" surfaces
2068
2246
  -- where output quality drives the operator's daily experience: DMs,
2069
- -- dashboard chat, hourly check, daily/weekly/monthly review, morning
2247
+ -- dashboard chat, activity scan, daily/weekly/monthly review, morning
2070
2248
  -- routine, scheduled tasks, git.project.* one-shots. Standard
2071
2249
  -- 50-turn / $1.00 envelope (git.project.* span 30–100 turns and
2072
2250
  -- $0.50–$2.00 — retemplate is widest because re-template work is
@@ -2164,7 +2342,7 @@ VALUES
2164
2342
  -- above).
2165
2343
  ('routine.morning_routine_today', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 50, 1.50, 'preset'),
2166
2344
  ('routine.morning_routine_journal', 'claude', '${DEFAULT_CLAUDE_LITE_MODEL}', 20, 0.30, 'preset'),
2167
- ('routine.hourly_check', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 50, 1.00, 'preset'),
2345
+ ('routine.activity_scan', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 50, 1.00, 'preset'),
2168
2346
  -- $0.50 budget: a typical drift-triggered refresh on Sonnet runs ~$0.10
2169
2347
  -- in 4 turns, but a busy-calendar drift (many/large pending calendar
2170
2348
  -- observations read via the task-flow GET limit=200) compounded by a
@@ -2265,24 +2443,38 @@ VALUES
2265
2443
  -- the seeded max_turns=1 caps every backend at one assistant turn,
2266
2444
  -- so this envelope is the absolute ceiling — defense-in-depth on
2267
2445
  -- top of the prompt's "no tools" contract.
2268
- ('routine.hourly_check.triage', 'claude', '${DEFAULT_CLAUDE_LITE_MODEL}', 1, 0.05, 'preset'),
2446
+ ('routine.activity_scan.triage', 'claude', '${DEFAULT_CLAUDE_LITE_MODEL}', 1, 0.05, 'preset'),
2269
2447
  -- docs/design/appendices/routine-data-acquisition.md §6.2 / §6.9 — pre-pass window
2270
2448
  -- fetcher dispatched before each routine session. Lite tier
2271
2449
  -- (Haiku-class) per P3 ("Lite for Fetch"). Envelope sized for the
2272
2450
  -- worst-case routine (morning_routine: fan-out across 2 mail
2273
- -- providers × N accounts + 2 calendar providers + notion). 20
2274
- -- turns is enough headroom for ~6 partials × 3 tool calls each;
2451
+ -- providers × N accounts + 2 calendar providers + notion);
2275
2452
  -- $0.50 caps the fan-out so a misconfigured account list cannot
2276
2453
  -- drain budget. The lite-tier nominal ($0.20) under-provisioned
2277
2454
  -- real morning fan-outs and tripped BackendQuotaError(max_budget_usd)
2278
2455
  -- mid-fetch, so this envelope is widened via the per-process
2279
2456
  -- override in plan-presets.ts (ENVELOPE_OVERRIDES_BY_PROCESS_KEY).
2280
- ('routine.fetch_window', 'claude', '${DEFAULT_CLAUDE_LITE_MODEL}', 20, 0.50, 'preset'),
2457
+ -- max_turns 20 → 10 (PREPASS_COST_REDUCTION_PLAN.md N4): with the
2458
+ -- per-integration fan-out each session handles ONE partial; live
2459
+ -- P99 over 502 runs is 8 turns. Lock-step with plan-presets.ts.
2460
+ -- Seed-only change — existing installs keep their row (INSERT is
2461
+ -- ignore-on-conflict) until they re-apply defaults.
2462
+ ('routine.fetch_window', 'claude', '${DEFAULT_CLAUDE_LITE_MODEL}', 10, 0.50, 'preset'),
2281
2463
  -- BROWSER_HISTORY_INTEGRATION_PLAN P3:
2282
2464
  -- research_cluster_update — nightly per-cluster journal append.
2283
- -- Lite tier (Haiku-class) — templated DM dispatcher with a
2284
- -- single PUT to context/research/<slug>.md. 5 turns / $0.05 is
2285
- -- enough headroom; the §10.3 safety floor refuses Codex
2465
+ -- Lite tier (Haiku-class) — a small curl flow against the
2466
+ -- daemon API ending in one append PATCH to research/<slug>.md.
2467
+ -- $0.50 is a STOP-LOSS, not a per-run cost target
2468
+ -- (RESEARCH_CLUSTER_COST_FIX_PLAN.md RC2/F3): the SDK budget
2469
+ -- check only fires between turns, and a cold-prompt-cache run
2470
+ -- writes the full session prefix (~$0.13-0.30 observed) before
2471
+ -- the check can abort — the original $0.05 seed killed every
2472
+ -- cold run after the money was already spent. The F1
2473
+ -- per-agent-day enqueue stamp bounds the key to one run per
2474
+ -- cluster per day, so this ceiling caps daily spend at
2475
+ -- $0.50/cluster. Existing installs are bumped by migration
2476
+ -- 0012; keep in lock-step with ENVELOPE_OVERRIDES_BY_PROCESS_KEY
2477
+ -- in plan-presets.ts. The §10.3 safety floor refuses Codex
2286
2478
  -- outright, so the seeded claude row is the only eligible
2287
2479
  -- binding until the operator widens via /settings/models.
2288
2480
  -- research_dispatch — accept path. Medium tier (Sonnet). Uses
@@ -2293,7 +2485,7 @@ VALUES
2293
2485
  -- tier; smaller envelope (30/$0.50) — the agent composes from
2294
2486
  -- the cluster journal it already wrote, so WebFetch fan-out is
2295
2487
  -- bounded.
2296
- ('routine.research_cluster_update', 'claude', '${DEFAULT_CLAUDE_LITE_MODEL}', 5, 0.05, 'preset'),
2488
+ ('routine.research_cluster_update', 'claude', '${DEFAULT_CLAUDE_LITE_MODEL}', 5, 0.50, 'preset'),
2297
2489
  ('routine.research_dispatch', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 50, 1.00, 'preset'),
2298
2490
  ('routine.research_wiki_summary', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 30, 0.50, 'preset'),
2299
2491
  -- BROWSER_TASK_REDESIGN_PLAN.md §5 — open-ended browser sub-agent.
@@ -2307,7 +2499,16 @@ VALUES
2307
2499
  -- Keep this row's (max_turns, max_budget_usd) in sync with §5's
2308
2500
  -- envelope spec — a future widening to support multi-tab tasks
2309
2501
  -- needs both this seed and the spec to move together.
2310
- ('browser_task', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 30, 1.00, 'preset');
2502
+ ('browser_task', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 30, 1.00, 'preset'),
2503
+ -- BACKGROUND_TASK_RUNNER_DESIGN.md §6 — generic detached-task worker.
2504
+ -- Medium tier (Sonnet) by default; the worker runs arbitrary
2505
+ -- long-running work (deep research, multi-repo audit, monitoring).
2506
+ -- The seed envelope is the FALLBACK + the per-tier base — the row's
2507
+ -- 'tier'/'max_budget_usd' (POST body) scale within the hard caps in
2508
+ -- background-task-budget.ts. 40 turns / $2.00 is the medium-tier base;
2509
+ -- long tasks select tier='high' for the wider envelope. Operators can
2510
+ -- pin model/backend per-row from /settings/models.
2511
+ ('background_task', 'claude', '${DEFAULT_CLAUDE_MEDIUM_MODEL}', 40, 2.00, 'preset');
2311
2512
 
2312
2513
  INSERT OR IGNORE INTO settings (key, value_json, updated_at)
2313
2514
  VALUES (
@@ -2,7 +2,7 @@ import type Database from "better-sqlite3";
2
2
  /**
3
3
  * Persistence helpers for `voice_transcripts` — local-Whisper transcripts
4
4
  * keyed 1:1 to `chat_attachments.id`. The cache lets a re-dispatch of the
5
- * same turn (or a Phase-9 hourly check that revisits an old voice
5
+ * same turn (or a Phase-9 activity scan that revisits an old voice
6
6
  * message) reuse a transcription instead of re-running inference.
7
7
  *
8
8
  * See `docs/design/appendices/voice-transcription.md`.
package/dist/index.js CHANGED
@@ -5,7 +5,6 @@ import { getDegradedMode, getVaultRestructurePendingConsent, isSetupCompleted, i
5
5
  import { initDirectories } from "./init.js";
6
6
  import { EventBus } from "./core/event-bus.js";
7
7
  import { AgentScheduler } from "./core/scheduler.js";
8
- import { CustomRoutineScheduler } from "./core/custom-routine-scheduler.js";
9
8
  import { HealthMonitor } from "./core/health-monitor.js";
10
9
  import { Heartbeat } from "./core/heartbeat.js";
11
10
  import { MessageHub } from "./adapters/message-hub.js";
@@ -22,7 +21,7 @@ import { resolveUserSkillsRoot } from "./core/user-skills-root.js";
22
21
  import { bootstrapManagementMd, startManagementMdWatcher, } from "./core/management-md.js";
23
22
  import { bootstrapManagementRegistry, startManagementRegistryWatcher, } from "./core/management-registry.js";
24
23
  import { startDocsIndexer, } from "./core/docs/indexer.js";
25
- import { APP_NAME, EventPriority, getBackendIds, } from "@aitne/shared";
24
+ import { APP_NAME, EventPriority, getAgentDayDateStr, getBackendIds, } from "@aitne/shared";
26
25
  import { getOwnerChannel, selectFirstPairedPlatform, } from "./messaging/owner-channels.js";
27
26
  import { SUPPORTED_MESSAGING_PLATFORMS, } from "./messaging/constants.js";
28
27
  import { AgentWriteTracker } from "./safety/agent-write-tracker.js";
@@ -30,6 +29,8 @@ import { InMemoryTodayWriteLockManager, getTodayWriteLockTimeoutMs, } from "./co
30
29
  import { InMemoryRoadmapWriteLockManager, getRoadmapWriteLockTimeoutMs, } from "./core/roadmap-write-lock.js";
31
30
  import { runRoadmapMechanicalMaintenance } from "./core/roadmap-maintenance.js";
32
31
  import { fanoutResearchClusterUpdates } from "./core/browser-history/research-cluster-fanout.js";
32
+ import { runDayBoundaryTasks } from "./core/day-boundary.js";
33
+ import { SleepInhibitor } from "./core/sleep-inhibitor.js";
33
34
  import { safeRunPreMorningDigestJob } from "./core/browser-history/pre-morning-digest-job.js";
34
35
  import { shouldStartObserversFor } from "./core/integration-lifecycle.js";
35
36
  import { sweepExpiredMigrationBackups } from "./api/routes/setup-migrate.js";
@@ -47,7 +48,7 @@ import { SecretBroker } from "./secrets/secret-broker.js";
47
48
  import { captureOriginalShellEnv, syncBackendApiKeyToEnv, } from "./secrets/backend-api-key-env.js";
48
49
  import { createLogger, toSafeErrorMessage } from "./logging.js";
49
50
  import { runCatchup, runPostMessagingCatchup, } from "./bootstrap/catchup.js";
50
- import { createAdapterReloaders, } from "./bootstrap/adapters.js";
51
+ import { createAdapterReloaders, createAdapterWatchdog, } from "./bootstrap/adapters.js";
51
52
  import { createInitialSecretState, createServiceReloaders, } from "./bootstrap/services.js";
52
53
  import { initDatabase } from "./bootstrap/db.js";
53
54
  import { bootstrapAgents } from "./core/agents/loader-boot.js";
@@ -93,6 +94,16 @@ async function startup() {
93
94
  const { db, settingsStore, persistedSettings, attachmentStore } = initDatabase({
94
95
  config,
95
96
  });
97
+ // ── Keep-awake (sleep inhibitor) ──
98
+ // Host sleep freezes every daemon timer; on a sleeping macOS laptop the
99
+ // 04:00 day-boundary flow degrades into hours of wake-catchup replays
100
+ // riding maintenance DarkWakes, each at cold-prompt-cache cost. Hold a
101
+ // `caffeinate` assertion tied to our pid for the daemon's lifetime
102
+ // (AC-power-only by default — see `preventSleepMode` in
103
+ // settings/runtime-settings.ts). Started after initDatabase so a
104
+ // dashboard-persisted mode from the settings table is honored.
105
+ const sleepInhibitor = new SleepInhibitor({ mode: config.preventSleepMode });
106
+ sleepInhibitor.start();
96
107
  // ── Integration Delegation Framework (Phase 1) ──
97
108
  // Reconcile `<contextDir>/policies/integrations.md` with the DB integrations map.
98
109
  // Creates the file on first run, parses hand-edits if present, and
@@ -371,6 +382,12 @@ async function startup() {
371
382
  reloadSlackAdapter(false),
372
383
  reloadTelegramAdapter(false),
373
384
  ]);
385
+ // Connection watchdog for the library-managed adapters. Probes the live
386
+ // socket/poll state on an interval and forces a stop→start cycle through
387
+ // the reloaders when a transport stays dead (e.g. after machine sleep
388
+ // kills the TCP sockets and the library's own reconnect gave up).
389
+ // Started after messageHub.startAll() below; stopped in shutdown.
390
+ const adapterWatchdog = createAdapterWatchdog({ messageHub, state: adapterState }, { reloadDiscordAdapter, reloadSlackAdapter, reloadTelegramAdapter });
374
391
  if (config.whatsappEnabled) {
375
392
  if (!config.whatsappOwnerPhone) {
376
393
  throw new Error("PA_WHATSAPP_ENABLED=true but PA_WHATSAPP_OWNER_PHONE is not set");
@@ -819,15 +836,10 @@ async function startup() {
819
836
  const heartbeat = new Heartbeat();
820
837
  // ── 9. Scheduler ──
821
838
  const scheduler = new AgentScheduler(eventBus, db, config);
822
- // ── 9.1 Custom routine scheduler (B-007 §5.8) ──
823
- // Reads `policies/routines/custom/*.md` from the context dir and registers a
824
- // cron job per enabled routine. Reloaded from the context API whenever
825
- // the agent or dashboard edits a file under that directory.
826
- const customRoutineScheduler = new CustomRoutineScheduler({
827
- contextDir: getContextDir(config),
828
- eventBus,
829
- timezone: config.timezone || undefined,
830
- });
839
+ // NB: the legacy CustomRoutineScheduler (B-007 §5.8 — per-file node-cron
840
+ // jobs over `policies/routines/custom/*.md`) was retired at the Agents-hub
841
+ // redesign; `bootstrapAgents` converts those files into user Agents once at
842
+ // boot (AGENTS_HUB_REDESIGN_PLAN.md §3).
831
843
  // ── 10. Event Processing Pipeline ──
832
844
  // §9.5 SignalDetector + §10 event-processing pipeline (agent cores,
833
845
  // BackendRouter, dispatcher + setters, NotificationManager,
@@ -885,7 +897,7 @@ async function startup() {
885
897
  emitRoadmapRefreshSink = cb;
886
898
  },
887
899
  });
888
- const { dispatcher, sessionManager, notificationManager, signalDetector, eventBroadcaster, auditLogger, docsQAAdapter, agentBackends, opencodeServerManager, delegatedBackendInvoker, authHealthMonitor, authRecovery, authTelemetry, readTokenManager, migrationLock, contextWriteGate, buildDelegatedSyncWorker, getDelegatedSyncWorker, rematerializeActiveDmWorkdirs, handleSecretChange, handleGoogleServicesReady, handlePromptContextChanged, keepaliveTimer, browserTaskDeadlineTimer, } = eventPipeline;
900
+ const { dispatcher, sessionManager, notificationManager, signalDetector, eventBroadcaster, auditLogger, docsQAAdapter, agentBackends, opencodeServerManager, delegatedBackendInvoker, authHealthMonitor, authRecovery, authTelemetry, readTokenManager, migrationLock, contextWriteGate, buildDelegatedSyncWorker, getDelegatedSyncWorker, rematerializeActiveDmWorkdirs, handleSecretChange, handleGoogleServicesReady, handlePromptContextChanged, keepaliveTimer, browserTaskDeadlineTimer, backgroundTaskHousekeepingTimer, } = eventPipeline;
889
901
  // ── 10.5 Agent Definitions load (AGENT_DEFINITIONS_DESIGN.md §6.1 / §7.1) ──
890
902
  // Runs AFTER `runMigrations` (inside `initDatabase`) and BEFORE
891
903
  // `scheduler.start()`: scans the built-in + user `agent.md` roots into the
@@ -934,7 +946,6 @@ async function startup() {
934
946
  sessionManager,
935
947
  scheduler,
936
948
  agentEnabledCache,
937
- customRoutineScheduler,
938
949
  healthMonitor,
939
950
  heartbeat,
940
951
  messageHub,
@@ -949,6 +960,10 @@ async function startup() {
949
960
  // same in-memory state as the runner's promote/release cascade.
950
961
  browserTaskRunner: eventPipeline.browserTaskRunner,
951
962
  browserTaskSlotStateRef: eventPipeline.browserTaskSlotStateRef,
963
+ // BACKGROUND_TASK_RUNNER_DESIGN.md §4 — generic runner + shared slot
964
+ // state, threaded through so the API routes share the runner's state.
965
+ backgroundTaskRunner: eventPipeline.backgroundTaskRunner,
966
+ backgroundTaskSlotStateRef: eventPipeline.backgroundTaskSlotStateRef,
952
967
  writeTracker,
953
968
  auditLogger,
954
969
  attachmentStore,
@@ -1007,16 +1022,24 @@ async function startup() {
1007
1022
  // (BROWSER_HISTORY_INTEGRATION_PLAN §10.6 step 3). The fan-out is
1008
1023
  // bounded at 25 clusters / cycle; backlog clusters surface on the
1009
1024
  // next day-boundary tick.
1025
+ //
1026
+ // RESEARCH_CLUSTER_COST_FIX_PLAN.md F2 — `runDayBoundaryTasks` gates
1027
+ // the body behind a per-agent-day runtime_state marker. The scheduler
1028
+ // invokes this callback from three sites (04:00 cron, wake catch-up,
1029
+ // morning self-heal); wrapping the single composition site keeps a
1030
+ // sleep-replay morning from re-running the body. Errors propagate
1031
+ // WITHOUT writing the marker — each scheduler site catches + logs and
1032
+ // the next fire retries the whole body.
1010
1033
  scheduler.setDayBoundaryCallback(async () => {
1011
- await dispatcher.summarizeDmSessions();
1012
- try {
1013
- const result = await fanoutResearchClusterUpdates(db, eventBus);
1014
- if (result.enqueuedSlugs.length > 0) {
1015
- logger.info({ enqueuedSlugs: result.enqueuedSlugs }, "Research cluster updates enqueued at day boundary");
1016
- }
1017
- }
1018
- catch (err) {
1019
- logger.error({ err }, "Research cluster update fan-out failed; will retry next day boundary");
1034
+ const todayAgentDay = getAgentDayDateStr(config.timezone || undefined, config.dayBoundaryHour);
1035
+ const result = await runDayBoundaryTasks({
1036
+ db,
1037
+ todayAgentDay,
1038
+ summarizeDmSessions: () => dispatcher.summarizeDmSessions(),
1039
+ fanoutResearchClusterUpdates: () => fanoutResearchClusterUpdates(db, eventBus, { todayAgentDay }),
1040
+ });
1041
+ if (result.ran && result.enqueuedSlugs.length > 0) {
1042
+ logger.info({ enqueuedSlugs: result.enqueuedSlugs }, "Research cluster updates enqueued at day boundary");
1020
1043
  }
1021
1044
  });
1022
1045
  // Register direct DM callback: sends scheduled messages without running an agent
@@ -1030,7 +1053,7 @@ async function startup() {
1030
1053
  }
1031
1054
  return messageHub.sendToUser(message);
1032
1055
  });
1033
- scheduler.setHourlyCheckCallback((source) => dispatcher.triggerHourlyCheck(source));
1056
+ scheduler.setActivityScanCallback((source) => dispatcher.triggerActivityScan(source));
1034
1057
  // B-004 Phase 2a — nightly context-index reconciler. The design doc
1035
1058
  // (§4.1, §5.3) originally proposed an `agent_schedule` row with
1036
1059
  // `task_type: "internal.reconcile_context_index"`, but the dispatcher's
@@ -1095,11 +1118,42 @@ async function startup() {
1095
1118
  },
1096
1119
  });
1097
1120
  });
1098
- // Phase 4 auth probe — runs BEFORE the hourly check on each cron
1121
+ // Phase 4 auth probe — runs BEFORE the activity scan on each cron
1099
1122
  // tick so auth health detection happens independently of the
1100
1123
  // observation-threshold gate. checkAll() owns its own kill switch
1101
1124
  // (authProbeDisabled), morning-routine skip, and in-flight dedupe.
1102
1125
  scheduler.setAuthProbeCallback(() => authHealthMonitor.checkAll());
1126
+ // SELF_TUNING_REVIEW_CYCLE_DESIGN.md §3.4 Phase 3 — self-tuning
1127
+ // auto-revert monitor. Rides the hourly cron tick (P2 — no new scheduled
1128
+ // session), throttles itself to one pass per day, and is a pure no-op
1129
+ // until the Phase 3 actuator has written `runtime_state.self_tuning:*`
1130
+ // ledger entries. Reverts go through the same `applyConfigUpdates`
1131
+ // chokepoint the actuator used; the owner is DM'd on every auto-revert.
1132
+ // Deliberately NOT gated on `selfTuningEnabled`: a safety rollback must
1133
+ // keep working even if the owner turns the loop off after a change
1134
+ // landed.
1135
+ scheduler.setSelfTuningRevertMonitorCallback(async () => {
1136
+ const { runSelfTuningRevertMonitor } = await import("./core/feedback/tuning-revert-monitor.js");
1137
+ const { applyConfigUpdates } = await import("./api/env-writer.js");
1138
+ const { SELF_TUNING_NOTIFICATION_TYPE } = await import("./core/feedback/tuning-recommender.js");
1139
+ return runSelfTuningRevertMonitor({
1140
+ db,
1141
+ applyUpdates: (updates) => applyConfigUpdates(config, settingsStore, updates, { db }),
1142
+ feedbackLearningEnabled: config.feedbackLearningEnabled,
1143
+ sendDm: async (message) => {
1144
+ await notificationManager.send(message, {
1145
+ // Logged to notification_log under this type; R2 excludes it
1146
+ // from demotion candidates (SELF_TUNING_NOTIFICATION_TYPE).
1147
+ type: SELF_TUNING_NOTIFICATION_TYPE,
1148
+ source: "self-tuning-revert-monitor",
1149
+ priority: EventPriority.NORMAL,
1150
+ timestamp: new Date(),
1151
+ data: {},
1152
+ correlationId: randomBytes(8).toString("hex"),
1153
+ }, { priority: "high", destinationMode: "configured_only" });
1154
+ },
1155
+ });
1156
+ });
1103
1157
  // Wire the autonomous-work gate: when policies/management.md is missing
1104
1158
  // or a setup conversation is active, the scheduler pauses cron routines
1105
1159
  // and ScheduleWatcher claims. This prevents any autonomous turn from
@@ -1108,7 +1162,7 @@ async function startup() {
1108
1162
  scheduler.setAutonomousGate(() => dispatcher.isAutonomousAllowed());
1109
1163
  // Pre-routine morning_routine gate (sleep-skip recovery). When the
1110
1164
  // current agent-day's morning_routine has not completed yet — typical
1111
- // cause: Mac slept through the 04:00 cron tick — hourly_check and the
1165
+ // cause: Mac slept through the 04:00 cron tick — activity_scan and the
1112
1166
  // review routines enqueue a wake row instead of running on stale state.
1113
1167
  // Wired here after both `dispatcher` and `scheduler` exist so the
1114
1168
  // binding is a single, stable function reference for the duration of
@@ -1117,8 +1171,8 @@ async function startup() {
1117
1171
  const startupCatchup = await runCatchup(db, dispatcher, config);
1118
1172
  // ── 13. Start all components ──
1119
1173
  await messageHub.startAll();
1174
+ adapterWatchdog.start();
1120
1175
  scheduler.start();
1121
- customRoutineScheduler.start();
1122
1176
  signalDetector.start();
1123
1177
  const registeredPlatforms = messageHub.getPlatforms();
1124
1178
  // Single-app installs (Telegram-only / Discord-only / etc.) would
@@ -1282,10 +1336,11 @@ async function startup() {
1282
1336
  const shutdown = async (signal) => {
1283
1337
  logger.info({ signal }, "Shutdown signal received");
1284
1338
  dispatcher.stop(); // Signals dispatcher to exit run() loop
1339
+ adapterWatchdog.stop();
1285
1340
  scheduler.stop();
1286
- customRoutineScheduler.stop();
1287
1341
  healthMonitor.stop();
1288
1342
  heartbeat.stop();
1343
+ sleepInhibitor.stop();
1289
1344
  signalDetector.stop();
1290
1345
  notificationManager.stop(); // Clear pending batch-flush timer
1291
1346
  authRecovery.shutdown(); // Kill any active recovery subprocesses
@@ -1299,6 +1354,8 @@ async function startup() {
1299
1354
  clearInterval(keepaliveTimer);
1300
1355
  // BROWSER_TASK_REDESIGN_PLAN.md §5 / §5.1 — 30 s deadline tick.
1301
1356
  clearInterval(browserTaskDeadlineTimer);
1357
+ // BACKGROUND_TASK_RUNNER_DESIGN.md §6 — 30 s housekeeping tick.
1358
+ clearInterval(backgroundTaskHousekeepingTimer);
1302
1359
  clearTimeout(migrationBackupSweepInitial);
1303
1360
  clearInterval(migrationBackupSweepTimer);
1304
1361
  if (managementMdWatcher) {
@@ -1340,7 +1397,7 @@ async function startup() {
1340
1397
  logger.info(`${APP_NAME} Daemon ready`);
1341
1398
  }
1342
1399
  // Catchup (`runCatchup` / `runPostMessagingCatchup`) and the pure schedule
1343
- // predicates (`getDueCatchupRoutines`, `shouldCatchUpHourlyCheck`,
1400
+ // predicates (`getDueCatchupRoutines`, `shouldCatchUpActivityScan`,
1344
1401
  // `getProgressMinutesForHour`, `hasFreshAgentDayTodayMd`,
1345
1402
  // `readSkillCurationCadence`) live in `./bootstrap/` — see
1346
1403
  // `docs/design/appendices/file-split-plan.md` §10. Imports are at the top