@aitne/daemon 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/dist/adapters/adapter-watchdog.d.ts +70 -0
  2. package/dist/adapters/adapter-watchdog.js +115 -0
  3. package/dist/adapters/discord.d.ts +17 -1
  4. package/dist/adapters/discord.js +33 -0
  5. package/dist/adapters/notification-manager.d.ts +27 -1
  6. package/dist/adapters/notification-manager.js +54 -39
  7. package/dist/adapters/slack-adapter.d.ts +26 -1
  8. package/dist/adapters/slack-adapter.js +41 -0
  9. package/dist/adapters/telegram-adapter.d.ts +18 -1
  10. package/dist/adapters/telegram-adapter.js +41 -2
  11. package/dist/adapters/types.d.ts +20 -0
  12. package/dist/adapters/whatsapp-adapter.d.ts +26 -7
  13. package/dist/adapters/whatsapp-adapter.js +74 -21
  14. package/dist/api/env-writer.d.ts +1 -0
  15. package/dist/api/env-writer.js +17 -7
  16. package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
  17. package/dist/api/helpers/agent-errors-registry.js +5 -5
  18. package/dist/api/routes/agent-schedule.js +5 -1
  19. package/dist/api/routes/agent.js +33 -12
  20. package/dist/api/routes/agents/index.js +75 -16
  21. package/dist/api/routes/agents/views.d.ts +37 -2
  22. package/dist/api/routes/agents/views.js +64 -2
  23. package/dist/api/routes/apple-calendar.js +4 -1
  24. package/dist/api/routes/background-task.d.ts +22 -0
  25. package/dist/api/routes/background-task.js +338 -0
  26. package/dist/api/routes/browser-history.js +9 -1
  27. package/dist/api/routes/calendar.js +12 -2
  28. package/dist/api/routes/context/path-resolve.js +6 -1
  29. package/dist/api/routes/context/permissions.js +12 -2
  30. package/dist/api/routes/context/snapshots.js +0 -3
  31. package/dist/api/routes/context/write.js +3 -17
  32. package/dist/api/routes/dashboard/config.js +58 -12
  33. package/dist/api/routes/dashboard/cost-approvals.js +66 -0
  34. package/dist/api/routes/dashboard/notifications.js +9 -9
  35. package/dist/api/routes/dashboard/oauth-google.js +5 -3
  36. package/dist/api/routes/feedback.d.ts +3 -0
  37. package/dist/api/routes/feedback.js +349 -0
  38. package/dist/api/routes/git.js +10 -3
  39. package/dist/api/routes/github.js +5 -1
  40. package/dist/api/routes/integrations/crud-patch.js +5 -1
  41. package/dist/api/routes/integrations-reconcile.js +2 -2
  42. package/dist/api/routes/mcp.js +65 -13
  43. package/dist/api/routes/notion.d.ts +1 -1
  44. package/dist/api/routes/observations.js +7 -7
  45. package/dist/api/routes/obsidian.d.ts +1 -1
  46. package/dist/api/routes/receipts.js +5 -1
  47. package/dist/api/routes/setup-migrate.js +1 -1
  48. package/dist/api/routes/setup.js +1 -1
  49. package/dist/api/routes/task-flows.d.ts +1 -1
  50. package/dist/api/routes/task-flows.js +1 -1
  51. package/dist/api/routes/tuning.d.ts +29 -0
  52. package/dist/api/routes/tuning.js +304 -0
  53. package/dist/api/server.d.ts +44 -16
  54. package/dist/api/server.js +12 -0
  55. package/dist/bootstrap/adapters.d.ts +19 -0
  56. package/dist/bootstrap/adapters.js +61 -0
  57. package/dist/bootstrap/api.d.ts +5 -3
  58. package/dist/bootstrap/api.js +45 -13
  59. package/dist/bootstrap/catchup.d.ts +1 -1
  60. package/dist/bootstrap/catchup.js +11 -11
  61. package/dist/bootstrap/event-pipeline.d.ts +11 -0
  62. package/dist/bootstrap/event-pipeline.js +246 -8
  63. package/dist/bootstrap/observers.js +9 -6
  64. package/dist/bootstrap/schedule-helpers.d.ts +104 -6
  65. package/dist/bootstrap/schedule-helpers.js +172 -19
  66. package/dist/config.js +32 -12
  67. package/dist/core/agent-core.d.ts +33 -1
  68. package/dist/core/agent-core.js +36 -1
  69. package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
  70. package/dist/core/agents/activity-scan-cadence.js +127 -0
  71. package/dist/core/agents/agent-route-override.d.ts +53 -0
  72. package/dist/core/agents/agent-route-override.js +69 -0
  73. package/dist/core/agents/builtin-registry.d.ts +51 -14
  74. package/dist/core/agents/builtin-registry.js +92 -15
  75. package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
  76. package/dist/core/agents/config-gate-reconcile.js +51 -0
  77. package/dist/core/agents/cron-substitute.d.ts +1 -1
  78. package/dist/core/agents/cron-substitute.js +1 -1
  79. package/dist/core/agents/custom-routine-migration.d.ts +60 -0
  80. package/dist/core/agents/custom-routine-migration.js +149 -0
  81. package/dist/core/agents/firing-blocked.d.ts +1 -1
  82. package/dist/core/agents/hourly-cadence.d.ts +102 -0
  83. package/dist/core/agents/hourly-cadence.js +126 -0
  84. package/dist/core/agents/loader-boot.js +23 -0
  85. package/dist/core/agents/loader.d.ts +19 -0
  86. package/dist/core/agents/loader.js +34 -2
  87. package/dist/core/agents/override-merge.d.ts +1 -1
  88. package/dist/core/agents/override-merge.js +9 -1
  89. package/dist/core/agents/recurrence-convert.d.ts +1 -1
  90. package/dist/core/agents/recurrence-convert.js +1 -1
  91. package/dist/core/agents/recurring-schedule-adapter.js +8 -0
  92. package/dist/core/alerts.js +6 -6
  93. package/dist/core/backends/auth-health-monitor.d.ts +2 -2
  94. package/dist/core/backends/auth-health-monitor.js +1 -1
  95. package/dist/core/backends/backend-router.d.ts +27 -1
  96. package/dist/core/backends/backend-router.js +165 -1
  97. package/dist/core/backends/claude-code-core.d.ts +71 -31
  98. package/dist/core/backends/claude-code-core.js +282 -54
  99. package/dist/core/backends/cli-quota-guards.d.ts +29 -1
  100. package/dist/core/backends/cli-quota-guards.js +40 -5
  101. package/dist/core/backends/codex-core.d.ts +6 -0
  102. package/dist/core/backends/codex-core.js +22 -6
  103. package/dist/core/backends/failure-spend.d.ts +58 -0
  104. package/dist/core/backends/failure-spend.js +137 -0
  105. package/dist/core/backends/gemini-cli-core.d.ts +6 -0
  106. package/dist/core/backends/gemini-cli-core.js +38 -6
  107. package/dist/core/backends/model-registry.d.ts +1 -1
  108. package/dist/core/backends/model-registry.js +4 -4
  109. package/dist/core/backends/opencode-core.d.ts +1 -1
  110. package/dist/core/backends/opencode-core.js +5 -5
  111. package/dist/core/backends/plan-presets.js +47 -18
  112. package/dist/core/bang-commands/commands-cost.js +3 -1
  113. package/dist/core/bang-commands/commands-report.js +4 -3
  114. package/dist/core/bang-commands/commands-research.js +4 -1
  115. package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
  116. package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
  117. package/dist/core/bang-commands/commands-stop-start.js +3 -3
  118. package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
  119. package/dist/core/bang-commands/commands-task-control.js +147 -0
  120. package/dist/core/bang-commands/commands-wiki.js +5 -5
  121. package/dist/core/bang-commands/index.d.ts +2 -0
  122. package/dist/core/bang-commands/index.js +12 -0
  123. package/dist/core/bang-commands/registry.d.ts +12 -0
  124. package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
  125. package/dist/core/browser-history/research-cluster-fanout.js +39 -16
  126. package/dist/core/channel-timeline.d.ts +5 -1
  127. package/dist/core/channel-timeline.js +13 -0
  128. package/dist/core/context/index-reconciler.js +5 -2
  129. package/dist/core/context/policy-index-reconciler.d.ts +6 -4
  130. package/dist/core/context/policy-index-runner.js +25 -6
  131. package/dist/core/context-builder-calendar.js +10 -2
  132. package/dist/core/context-builder-conversation.d.ts +8 -1
  133. package/dist/core/context-builder-conversation.js +41 -7
  134. package/dist/core/context-builder-yesterday.js +4 -3
  135. package/dist/core/context-builder.d.ts +7 -2
  136. package/dist/core/context-builder.js +193 -5
  137. package/dist/core/context-file-serializer.d.ts +1 -1
  138. package/dist/core/context-file-serializer.js +1 -1
  139. package/dist/core/context-health.js +2 -2
  140. package/dist/core/context-paths.d.ts +11 -1
  141. package/dist/core/context-paths.js +17 -1
  142. package/dist/core/context-validation/prepare-write.js +1 -1
  143. package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
  144. package/dist/core/context-vault-aliases.d.ts +0 -13
  145. package/dist/core/context-vault-aliases.js +37 -0
  146. package/dist/core/custom-routines.d.ts +99 -0
  147. package/dist/core/custom-routines.js +187 -0
  148. package/dist/core/daemon-api-cli.js +50 -1
  149. package/dist/core/day-boundary.d.ts +46 -0
  150. package/dist/core/day-boundary.js +40 -0
  151. package/dist/core/dispatcher-activity-scan.d.ts +221 -0
  152. package/dist/core/dispatcher-activity-scan.js +775 -0
  153. package/dist/core/dispatcher-error-handling.d.ts +6 -11
  154. package/dist/core/dispatcher-error-handling.js +38 -62
  155. package/dist/core/dispatcher-hourly-check.js +6 -1
  156. package/dist/core/dispatcher-message-handler.d.ts +10 -0
  157. package/dist/core/dispatcher-message-handler.js +24 -0
  158. package/dist/core/dispatcher-morning-routine.d.ts +6 -6
  159. package/dist/core/dispatcher-morning-routine.js +13 -13
  160. package/dist/core/dispatcher-result-processor.d.ts +33 -0
  161. package/dist/core/dispatcher-result-processor.js +167 -11
  162. package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
  163. package/dist/core/dispatcher-scheduled-background-task.js +89 -0
  164. package/dist/core/dispatcher-scheduled-tasks.d.ts +104 -1
  165. package/dist/core/dispatcher-scheduled-tasks.js +480 -8
  166. package/dist/core/dispatcher-task-delivery.d.ts +105 -0
  167. package/dist/core/dispatcher-task-delivery.js +555 -0
  168. package/dist/core/dispatcher-types.d.ts +48 -9
  169. package/dist/core/dispatcher-types.js +3 -3
  170. package/dist/core/dispatcher.d.ts +112 -31
  171. package/dist/core/dispatcher.js +297 -60
  172. package/dist/core/dm-freshness-metrics.d.ts +1 -1
  173. package/dist/core/drift-effects.js +2 -2
  174. package/dist/core/feedback/consolidation-prep.d.ts +94 -0
  175. package/dist/core/feedback/consolidation-prep.js +254 -0
  176. package/dist/core/feedback/eviction-scorer.d.ts +81 -0
  177. package/dist/core/feedback/eviction-scorer.js +136 -0
  178. package/dist/core/feedback/lesson-format.d.ts +79 -0
  179. package/dist/core/feedback/lesson-format.js +199 -0
  180. package/dist/core/feedback/lesson-injection.d.ts +98 -0
  181. package/dist/core/feedback/lesson-injection.js +174 -0
  182. package/dist/core/feedback/lesson-merge.d.ts +51 -0
  183. package/dist/core/feedback/lesson-merge.js +88 -0
  184. package/dist/core/feedback/lesson-store-overview.d.ts +46 -0
  185. package/dist/core/feedback/lesson-store-overview.js +42 -0
  186. package/dist/core/feedback/promotion-gate.d.ts +69 -0
  187. package/dist/core/feedback/promotion-gate.js +117 -0
  188. package/dist/core/feedback/regeneralization-prep.d.ts +87 -0
  189. package/dist/core/feedback/regeneralization-prep.js +152 -0
  190. package/dist/core/feedback/scope-parser.d.ts +86 -0
  191. package/dist/core/feedback/scope-parser.js +141 -0
  192. package/dist/core/feedback/self-performance-prep.d.ts +186 -0
  193. package/dist/core/feedback/self-performance-prep.js +541 -0
  194. package/dist/core/feedback/tuning-actuator.d.ts +198 -0
  195. package/dist/core/feedback/tuning-actuator.js +432 -0
  196. package/dist/core/feedback/tuning-recommender.d.ts +247 -0
  197. package/dist/core/feedback/tuning-recommender.js +580 -0
  198. package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
  199. package/dist/core/feedback/tuning-revert-monitor.js +213 -0
  200. package/dist/core/health-monitor.d.ts +6 -0
  201. package/dist/core/health-monitor.js +1 -1
  202. package/dist/core/injection-policy.d.ts +83 -1
  203. package/dist/core/injection-policy.js +61 -3
  204. package/dist/core/integration-main-backend.js +4 -0
  205. package/dist/core/management-md.d.ts +2 -2
  206. package/dist/core/management-md.js +51 -13
  207. package/dist/core/morning/orchestrator.d.ts +2 -2
  208. package/dist/core/morning/orchestrator.js +2 -2
  209. package/dist/core/notification-gate.d.ts +64 -0
  210. package/dist/core/notification-gate.js +51 -0
  211. package/dist/core/notification-rate-limit.d.ts +40 -0
  212. package/dist/core/notification-rate-limit.js +50 -0
  213. package/dist/core/policy-files.d.ts +1 -1
  214. package/dist/core/policy-files.js +2 -2
  215. package/dist/core/pre-pass-freshness.d.ts +4 -4
  216. package/dist/core/retention.d.ts +5 -0
  217. package/dist/core/retention.js +20 -4
  218. package/dist/core/review-context.d.ts +1 -1
  219. package/dist/core/review-context.js +10 -5
  220. package/dist/core/roadmap-write-lock.d.ts +2 -1
  221. package/dist/core/roadmap-write-lock.js +15 -10
  222. package/dist/core/routine-acquisition-plan.d.ts +47 -1
  223. package/dist/core/routine-acquisition-plan.js +78 -20
  224. package/dist/core/routine-fetch-window-retry.js +7 -4
  225. package/dist/core/routine-fetch-window-runner.d.ts +39 -3
  226. package/dist/core/routine-fetch-window-runner.js +264 -13
  227. package/dist/core/routine-windows.d.ts +2 -2
  228. package/dist/core/routine-windows.js +8 -5
  229. package/dist/core/scheduler.d.ts +175 -16
  230. package/dist/core/scheduler.js +559 -102
  231. package/dist/core/signal-detector.d.ts +51 -1
  232. package/dist/core/signal-detector.js +321 -24
  233. package/dist/core/skills-compiler-denied-tools.js +2 -2
  234. package/dist/core/skills-compiler-skill-index.d.ts +2 -2
  235. package/dist/core/skills-compiler-skill-index.js +2 -2
  236. package/dist/core/skills-compiler-variants.d.ts +1 -1
  237. package/dist/core/skills-compiler-variants.js +8 -0
  238. package/dist/core/skills-compiler.d.ts +29 -26
  239. package/dist/core/skills-compiler.js +117 -81
  240. package/dist/core/skills-manifest.d.ts +37 -0
  241. package/dist/core/skills-manifest.js +73 -2
  242. package/dist/core/sleep-inhibitor.d.ts +79 -0
  243. package/dist/core/sleep-inhibitor.js +132 -0
  244. package/dist/core/slim-system-prompt-loader.d.ts +77 -0
  245. package/dist/core/slim-system-prompt-loader.js +141 -0
  246. package/dist/core/spawn-gates.d.ts +126 -0
  247. package/dist/core/spawn-gates.js +180 -0
  248. package/dist/core/today-direct-writer.d.ts +60 -14
  249. package/dist/core/today-direct-writer.js +90 -13
  250. package/dist/core/today-write-lock.d.ts +4 -2
  251. package/dist/core/today-write-lock.js +30 -20
  252. package/dist/core/wake-detector.d.ts +55 -0
  253. package/dist/core/wake-detector.js +80 -0
  254. package/dist/core/wiki/compile-lock.d.ts +1 -1
  255. package/dist/core/wiki/compile-lock.js +1 -1
  256. package/dist/core/wiki/wiki-fts.js +13 -6
  257. package/dist/core/workdir.js +15 -6
  258. package/dist/db/activity-scan-signals.d.ts +77 -0
  259. package/dist/db/activity-scan-signals.js +378 -0
  260. package/dist/db/agents-store.d.ts +28 -0
  261. package/dist/db/agents-store.js +62 -0
  262. package/dist/db/background-task-clarifications-store.d.ts +81 -0
  263. package/dist/db/background-task-clarifications-store.js +152 -0
  264. package/dist/db/background-task-store.d.ts +207 -0
  265. package/dist/db/background-task-store.js +380 -0
  266. package/dist/db/browser-history-store.d.ts +39 -6
  267. package/dist/db/browser-history-store.js +51 -7
  268. package/dist/db/browser-task-clarifications-store.d.ts +12 -0
  269. package/dist/db/browser-task-clarifications-store.js +35 -5
  270. package/dist/db/browser-task-store.d.ts +3 -0
  271. package/dist/db/browser-task-store.js +29 -4
  272. package/dist/db/deferred-dm.d.ts +86 -0
  273. package/dist/db/deferred-dm.js +199 -0
  274. package/dist/db/feedback-signals-store.d.ts +77 -0
  275. package/dist/db/feedback-signals-store.js +144 -0
  276. package/dist/db/migrations.js +380 -0
  277. package/dist/db/observations.d.ts +2 -2
  278. package/dist/db/observations.js +3 -3
  279. package/dist/db/schema.js +260 -22
  280. package/dist/db/voice-transcripts-store.d.ts +1 -1
  281. package/dist/index.js +86 -29
  282. package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
  283. package/dist/messaging/browser-task-mcp-notifier.js +30 -151
  284. package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
  285. package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
  286. package/dist/observers/delegated-sync-worker.d.ts +6 -6
  287. package/dist/observers/delegated-sync-worker.js +10 -10
  288. package/dist/observers/git-delegated-cron.d.ts +1 -1
  289. package/dist/observers/git-delegated-cron.js +2 -2
  290. package/dist/observers/github-poller-classifier.d.ts +3 -3
  291. package/dist/observers/github-poller-classifier.js +3 -3
  292. package/dist/observers/imminent-event-scheduler.d.ts +1 -1
  293. package/dist/observers/imminent-event-scheduler.js +1 -1
  294. package/dist/observers/mail-poller.d.ts +1 -0
  295. package/dist/observers/mail-poller.js +42 -3
  296. package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
  297. package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
  298. package/dist/observers/observation-summarizer/worker.d.ts +2 -2
  299. package/dist/observers/observation-summarizer/worker.js +4 -4
  300. package/dist/observers/obsidian-watcher.d.ts +1 -1
  301. package/dist/observers/obsidian-watcher.js +1 -1
  302. package/dist/safety/agent-write-tracker.d.ts +4 -4
  303. package/dist/safety/agent-write-tracker.js +4 -4
  304. package/dist/safety/always-disallowed.d.ts +1 -1
  305. package/dist/safety/always-disallowed.js +39 -0
  306. package/dist/safety/audit.d.ts +43 -5
  307. package/dist/safety/audit.js +86 -18
  308. package/dist/safety/risk-classifier.d.ts +6 -0
  309. package/dist/safety/risk-classifier.js +97 -18
  310. package/dist/scheduler/activity-scan-gate.d.ts +86 -0
  311. package/dist/scheduler/activity-scan-gate.js +132 -0
  312. package/dist/services/background-task/background-task-budget.d.ts +80 -0
  313. package/dist/services/background-task/background-task-budget.js +91 -0
  314. package/dist/services/background-task/background-task-driver.d.ts +105 -0
  315. package/dist/services/background-task/background-task-driver.js +416 -0
  316. package/dist/services/background-task/background-task-runner.d.ts +96 -0
  317. package/dist/services/background-task/background-task-runner.js +673 -0
  318. package/dist/services/background-task/background-task-tools.d.ts +84 -0
  319. package/dist/services/background-task/background-task-tools.js +247 -0
  320. package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
  321. package/dist/services/background-task/background-task-transition-events.js +54 -0
  322. package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
  323. package/dist/services/browser-history/automation/egress-denylist.js +34 -8
  324. package/dist/services/browser-history/lifecycle/platform.js +44 -2
  325. package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
  326. package/dist/services/browser-task/browser-task-runner.js +53 -8
  327. package/dist/services/mcp/probe.js +30 -8
  328. package/dist/services/observations-batch.d.ts +1 -1
  329. package/dist/services/observations-batch.js +2 -2
  330. package/dist/settings/runtime-settings.d.ts +45 -12
  331. package/dist/settings/runtime-settings.js +215 -40
  332. package/dist/settings/settings-store.js +11 -3
  333. package/package.json +4 -4
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Activity-scan cadence resolution (AGENTS_HUB_REDESIGN_PLAN.md §2).
3
+ *
4
+ * The activity-scan Agent's firing window (interval + active hours) and its
5
+ * observation-threshold gate live on the **agent row** —
6
+ * `agents.metadata_json.runtime_window`, written by `PATCH /api/agents/
7
+ * activity-scan` (`schedule_window` body block) and preserved across loader
8
+ * re-runs / `npm i -g` by `loader.ts:nextMetadata`. The `activityScan*`
9
+ * config keys (`hourlyCheck*` before the v0.1.11 rename) are deprecated but
10
+ * still parsed; they act as the per-field fallback so a value an operator
11
+ * persisted pre-redesign keeps working until they touch the agent-level
12
+ * setting.
13
+ *
14
+ * Resolution order, per field: `runtime_window` override → legacy config key
15
+ * (which itself carries the shipped default). Pure module — callers fetch the
16
+ * stored override via `agents-store.ts:getRuntimeWindow` and supply the live
17
+ * config; this keeps the precedence logic in the 100%-coverage set.
18
+ */
19
+ /** Field bounds, shared by the PATCH validator and the sanitizer. */
20
+ export const RUNTIME_WINDOW_BOUNDS = {
21
+ interval_minutes: { min: 5, max: 1440 },
22
+ active_start_hour: { min: 0, max: 23 },
23
+ active_end_hour: { min: 1, max: 24 },
24
+ min_observations: { min: 0, max: 1000 },
25
+ };
26
+ export const RUNTIME_WINDOW_FIELDS = Object.keys(RUNTIME_WINDOW_BOUNDS);
27
+ /** Shipped defaults — mirror `runtime-settings.ts` (`activityScan*` keys). */
28
+ export const ACTIVITY_SCAN_CADENCE_DEFAULTS = {
29
+ intervalMinutes: 120,
30
+ activeStartHour: 4,
31
+ activeEndHour: 24,
32
+ minObservations: 1,
33
+ };
34
+ /** True when `value` is an integer within the field's bounds. */
35
+ export function isValidRuntimeWindowValue(field, value) {
36
+ if (typeof value !== "number" || !Number.isInteger(value))
37
+ return false;
38
+ const { min, max } = RUNTIME_WINDOW_BOUNDS[field];
39
+ return value >= min && value <= max;
40
+ }
41
+ /**
42
+ * Sanitize a raw `metadata_json.runtime_window` blob (untrusted: hand-edited
43
+ * DBs, older daemons). Out-of-bounds / non-integer fields are dropped — the
44
+ * resolver then falls back to config for them rather than failing the boot.
45
+ */
46
+ export function parseRuntimeWindowOverride(value) {
47
+ if (typeof value !== "object" || value === null || Array.isArray(value))
48
+ return {};
49
+ const raw = value;
50
+ const out = {};
51
+ for (const field of RUNTIME_WINDOW_FIELDS) {
52
+ const v = raw[field];
53
+ if (isValidRuntimeWindowValue(field, v))
54
+ out[field] = v;
55
+ }
56
+ return out;
57
+ }
58
+ /**
59
+ * Resolve the effective cadence: per-field `runtime_window` override, else the
60
+ * legacy config key. A start ≥ end pairing (possible when an override touches
61
+ * only one side of an old config window) is repaired by widening the end to
62
+ * `start + 1` so `buildActivityScanCronExpr` always receives a non-empty window.
63
+ */
64
+ /** Pick `v` when it is a finite number, else `fallback`. */
65
+ function num(v, fallback) {
66
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
67
+ }
68
+ export function resolveActivityScanCadence(override, config) {
69
+ const o = override ?? {};
70
+ const activeStartHour = o.active_start_hour
71
+ ?? num(config.activityScanActiveStartHour, ACTIVITY_SCAN_CADENCE_DEFAULTS.activeStartHour);
72
+ let activeEndHour = o.active_end_hour
73
+ ?? num(config.activityScanActiveEndHour, ACTIVITY_SCAN_CADENCE_DEFAULTS.activeEndHour);
74
+ if (activeEndHour <= activeStartHour)
75
+ activeEndHour = Math.min(activeStartHour + 1, 24);
76
+ return {
77
+ intervalMinutes: o.interval_minutes
78
+ ?? num(config.activityScanIntervalMinutes, ACTIVITY_SCAN_CADENCE_DEFAULTS.intervalMinutes),
79
+ activeStartHour,
80
+ activeEndHour,
81
+ minObservations: o.min_observations
82
+ ?? num(config.activityScanMinObservations, ACTIVITY_SCAN_CADENCE_DEFAULTS.minObservations),
83
+ };
84
+ }
85
+ /**
86
+ * Merge a PATCH `schedule_window` block onto the stored override. Per-field
87
+ * type/bounds are validated; the cross-field window check (`end > start`) runs
88
+ * against the post-merge **resolved** values so a partial patch can't sneak an
89
+ * empty window past per-field validation. `null` resets a field back to the
90
+ * config fallback. `cadenceChanged` tells the route whether a cron rebuild
91
+ * (`reloadCrons`) is needed — `min_observations` is a fire-time gate and never
92
+ * requires one.
93
+ */
94
+ export function mergeRuntimeWindow(current, patch, config) {
95
+ const next = { ...current };
96
+ let cadenceChanged = false;
97
+ for (const [key, value] of Object.entries(patch)) {
98
+ if (!RUNTIME_WINDOW_FIELDS.includes(key)) {
99
+ return { ok: false, field: `schedule_window.${key}`, error: "invalid_field_value" };
100
+ }
101
+ const field = key;
102
+ if (value === null) {
103
+ if (next[field] !== undefined) {
104
+ delete next[field];
105
+ if (field !== "min_observations")
106
+ cadenceChanged = true;
107
+ }
108
+ continue;
109
+ }
110
+ if (!isValidRuntimeWindowValue(field, value)) {
111
+ return { ok: false, field: `schedule_window.${field}`, error: "invalid_field_value" };
112
+ }
113
+ if (next[field] !== value) {
114
+ next[field] = value;
115
+ if (field !== "min_observations")
116
+ cadenceChanged = true;
117
+ }
118
+ }
119
+ const requestedEnd = next.active_end_hour
120
+ ?? num(config.activityScanActiveEndHour, ACTIVITY_SCAN_CADENCE_DEFAULTS.activeEndHour);
121
+ const requestedStart = next.active_start_hour
122
+ ?? num(config.activityScanActiveStartHour, ACTIVITY_SCAN_CADENCE_DEFAULTS.activeStartHour);
123
+ if (requestedEnd <= requestedStart) {
124
+ return { ok: false, field: "schedule_window.active_end_hour", error: "invalid_window" };
125
+ }
126
+ return { ok: true, value: next, cadenceChanged };
127
+ }
@@ -0,0 +1,53 @@
1
+ import type { BackendId, ProcessModelTier } from "@aitne/shared";
2
+ /**
3
+ * Built-in Agent runtime route override (AGENT_DEFINITIONS_DESIGN.md §6.4.1,
4
+ * runtime wiring).
5
+ *
6
+ * A built-in Agent's Definition-tab edits land in
7
+ * `agents.metadata_json.override_snapshot`. Until this module existed they
8
+ * were display-only — routing stayed governed entirely by
9
+ * `process_backend_config`. `BackendRouter.resolveBinding` now calls
10
+ * `extractAgentRouteOverride` on the snapshot of the firing's resolved Agent
11
+ * (`event.data.agentId`, stamped by `Dispatcher.beginAgentExecution`) and
12
+ * layers the result UNDER any caller-explicit options:
13
+ *
14
+ * caller-explicit (chat picker, run-now hint, schedule row)
15
+ * > agent override (this module)
16
+ * > process_backend_config / process-key defaults
17
+ *
18
+ * Pure + dependency-light so it stays in the 100%-coverage set; the router
19
+ * supplies the snapshot it read from the `agents` row.
20
+ */
21
+ export interface AgentRouteOverride {
22
+ /** Tier override, applied as a `requestedTier` default. */
23
+ tier: ProcessModelTier | null;
24
+ /** Model pin; when set, `backendId` names the owning backend. */
25
+ modelId: string | null;
26
+ /** Owning backend for `modelId` (stored, or inferred from the registry). */
27
+ backendId: BackendId | null;
28
+ /** Per-execution turn cap, applied onto the resolved binding. */
29
+ maxTurns: number | null;
30
+ /** Per-execution budget cap, applied onto the resolved binding. */
31
+ maxBudgetUsd: number | null;
32
+ }
33
+ /**
34
+ * Find the backend that owns `modelId` in the static model registry. Needed
35
+ * for override snapshots written before `backend.backend_id` existed (the
36
+ * model field was a free-text input then). Returns `null` for an id no
37
+ * registered backend knows — the model pin is dropped in that case rather
38
+ * than guessed, because executing a model id on the wrong backend fails the
39
+ * whole firing.
40
+ */
41
+ export declare function inferBackendForModel(modelId: string): BackendId | null;
42
+ /**
43
+ * Extract the routing-relevant overrides from a built-in's
44
+ * `override_snapshot`. Returns `null` when the snapshot carries nothing that
45
+ * affects routing (so the router can skip the merge entirely). Values are
46
+ * re-guarded with the same contracts `override-merge.ts` enforces —
47
+ * defence-in-depth against a hand-edited snapshot; an out-of-contract value
48
+ * is dropped, never thrown on.
49
+ *
50
+ * A model pin without a resolvable backend (no stored `backend.backend_id`,
51
+ * no registry match) drops the pin but keeps the tier/limit overrides.
52
+ */
53
+ export declare function extractAgentRouteOverride(snapshot: unknown): AgentRouteOverride | null;
@@ -0,0 +1,69 @@
1
+ import { AGENT_TIERS, BACKEND_IDS } from "@aitne/shared";
2
+ import { findRegisteredModel } from "../backends/model-registry.js";
3
+ function isTier(value) {
4
+ return typeof value === "string" && AGENT_TIERS.includes(value);
5
+ }
6
+ function isBackendId(value) {
7
+ return typeof value === "string" && BACKEND_IDS.includes(value);
8
+ }
9
+ function isPositiveInt(value) {
10
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
11
+ }
12
+ function isNonNegativeFinite(value) {
13
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
14
+ }
15
+ /**
16
+ * Find the backend that owns `modelId` in the static model registry. Needed
17
+ * for override snapshots written before `backend.backend_id` existed (the
18
+ * model field was a free-text input then). Returns `null` for an id no
19
+ * registered backend knows — the model pin is dropped in that case rather
20
+ * than guessed, because executing a model id on the wrong backend fails the
21
+ * whole firing.
22
+ */
23
+ export function inferBackendForModel(modelId) {
24
+ for (const backendId of BACKEND_IDS) {
25
+ if (findRegisteredModel(backendId, modelId))
26
+ return backendId;
27
+ }
28
+ return null;
29
+ }
30
+ /**
31
+ * Extract the routing-relevant overrides from a built-in's
32
+ * `override_snapshot`. Returns `null` when the snapshot carries nothing that
33
+ * affects routing (so the router can skip the merge entirely). Values are
34
+ * re-guarded with the same contracts `override-merge.ts` enforces —
35
+ * defence-in-depth against a hand-edited snapshot; an out-of-contract value
36
+ * is dropped, never thrown on.
37
+ *
38
+ * A model pin without a resolvable backend (no stored `backend.backend_id`,
39
+ * no registry match) drops the pin but keeps the tier/limit overrides.
40
+ */
41
+ export function extractAgentRouteOverride(snapshot) {
42
+ if (typeof snapshot !== "object" || snapshot === null || Array.isArray(snapshot)) {
43
+ return null;
44
+ }
45
+ const snap = snapshot;
46
+ const tier = isTier(snap["backend.tier"]) ? snap["backend.tier"] : null;
47
+ const rawModel = snap["backend.model"];
48
+ let modelId = typeof rawModel === "string" && rawModel.length > 0 ? rawModel : null;
49
+ let backendId = null;
50
+ if (modelId !== null) {
51
+ const rawBackend = snap["backend.backend_id"];
52
+ backendId = isBackendId(rawBackend) ? rawBackend : inferBackendForModel(modelId);
53
+ if (backendId === null)
54
+ modelId = null;
55
+ }
56
+ const maxTurns = isPositiveInt(snap["limits.max_turns"])
57
+ ? snap["limits.max_turns"]
58
+ : null;
59
+ const maxBudgetUsd = isNonNegativeFinite(snap["limits.max_budget_usd"])
60
+ ? snap["limits.max_budget_usd"]
61
+ : null;
62
+ if (tier === null
63
+ && modelId === null
64
+ && maxTurns === null
65
+ && maxBudgetUsd === null) {
66
+ return null;
67
+ }
68
+ return { tier, modelId, backendId, maxTurns, maxBudgetUsd };
69
+ }
@@ -29,8 +29,8 @@ import type { ProcessKey, StopWarning } from "@aitne/shared";
29
29
  */
30
30
  /**
31
31
  * Resolves a built-in's cron expression from the live `dayBoundaryHour`
32
- * config. `null` for the interval-gated `hourly-check`, whose cadence is a
33
- * runtime window (`buildHourlyCronExpr(intervalMinutes, activeStart,
32
+ * config. `null` for the interval-gated `activity-scan`, whose cadence is a
33
+ * runtime window (`buildActivityScanCronExpr(intervalMinutes, activeStart,
34
34
  * activeEnd)`), not a fixed expression — the loader's drift check is a no-op
35
35
  * for a `null` resolver (§5.5.1).
36
36
  */
@@ -39,8 +39,8 @@ export type CronExpressionResolver = (config: {
39
39
  }) => string;
40
40
  /**
41
41
  * How `setupRecurringJobs` fires a built-in. NB: a non-null `processKey` does
42
- * NOT imply `emit_routine`/`queue_wake` — `hourly-check` carries
43
- * `routine.hourly_check` yet fires via `in_process_callback`.
42
+ * NOT imply `emit_routine`/`queue_wake` — `activity-scan` carries
43
+ * `routine.activity_scan` yet fires via `in_process_callback`.
44
44
  *
45
45
  * - `emit_routine` — `emitRoutine(routine, data?)` puts a `routine.<name>`
46
46
  * event on the bus. `data` is the fixed payload the scheduler passes; the
@@ -50,7 +50,7 @@ export type CronExpressionResolver = (config: {
50
50
  * baked literal would be wrong).
51
51
  * - `queue_wake` — `queueMorningRoutineWake(...)` enqueues a durable wake
52
52
  * row instead of a transient event (morning-routine only).
53
- * - `in_process_callback` — a typed daemon callback (`onHourlyCheck`,
53
+ * - `in_process_callback` — a typed daemon callback (`onActivityScan`,
54
54
  * `onRoadmapMaintenance`, `onContextIndexReconcile`); the last two are
55
55
  * no-LLM passes with `processKey: null`.
56
56
  */
@@ -65,6 +65,35 @@ export type BuiltinSchedulerFn = {
65
65
  kind: "in_process_callback";
66
66
  callbackName: string;
67
67
  };
68
+ /**
69
+ * Hub grouping for the `/agents` dashboard (AGENTS_HUB_REDESIGN_PLAN.md §1).
70
+ * Built-ins declare one of the three operational categories; user Agents are
71
+ * implicitly `"user"` at the API layer (not a registry value).
72
+ *
73
+ * - `synthesis` — routines that write the user-facing synthesis surfaces
74
+ * (today.md, journals, reviews).
75
+ * - `monitoring` — interval watchers that triage observations and surface
76
+ * activity proactively.
77
+ * - `maintenance` — mechanical / background upkeep passes.
78
+ */
79
+ export declare const AGENT_CATEGORIES: readonly ["synthesis", "monitoring", "maintenance"];
80
+ export type AgentCategory = (typeof AGENT_CATEGORIES)[number];
81
+ /**
82
+ * A context-vault policy file a built-in reads at prompt-assembly time —
83
+ * its editable "rulebook" surface (AGENTS_HUB_REDESIGN_PLAN.md §1 / §4.2).
84
+ * The dashboard's per-agent Rulebook tab renders one editor per entry,
85
+ * loading/saving through the context API chokepoint (`/api/context/<path>`);
86
+ * the vault stays the storage, this is declaration only. Paths are the
87
+ * canonical class-prefixed form (`context-paths.ts`).
88
+ */
89
+ export interface AgentPolicyFile {
90
+ /** Canonical vault-relative path, e.g. `policies/routines/morning.md`. */
91
+ path: string;
92
+ /** Short editor-card title, e.g. "Morning rulebook". */
93
+ label: string;
94
+ /** One-liner explaining what the file controls. */
95
+ description: string;
96
+ }
68
97
  export interface BuiltinAgentRegistryEntry {
69
98
  /** Kebab-case slug; also the `agents.id` and the `agent.md` directory name. */
70
99
  slug: string;
@@ -80,7 +109,7 @@ export interface BuiltinAgentRegistryEntry {
80
109
  description: string;
81
110
  /**
82
111
  * Cron with `{dayBoundaryHour}` / `{dayBoundaryHour-1}` resolved from live
83
- * config, or `null` for the runtime-window `hourly-check` (§5.5.1).
112
+ * config, or `null` for the runtime-window `activity-scan` (§5.5.1).
84
113
  */
85
114
  cronExpression: CronExpressionResolver | null;
86
115
  /**
@@ -103,6 +132,14 @@ export interface BuiltinAgentRegistryEntry {
103
132
  stopWarning: StopWarning;
104
133
  /** The mechanism `setupRecurringJobs` uses to fire this slug. */
105
134
  schedulerFn: BuiltinSchedulerFn;
135
+ /** Hub grouping for the `/agents` dashboard (see `AGENT_CATEGORIES`). */
136
+ category: AgentCategory;
137
+ /**
138
+ * The vault policy files this built-in reads at prompt-assembly time —
139
+ * surfaced as its Rulebook tab. Empty for built-ins with no editable
140
+ * rulebook (no-LLM passes, sweeps).
141
+ */
142
+ policyFiles: readonly AgentPolicyFile[];
106
143
  }
107
144
  export declare const BUILTIN_AGENT_REGISTRY: readonly BuiltinAgentRegistryEntry[];
108
145
  /** Slug → entry, for O(1) lookup from the loader and the scheduler gate. */
@@ -115,15 +152,15 @@ export declare function getBuiltinRegistryEntry(slug: string): BuiltinAgentRegis
115
152
  export declare function isBuiltinAgentSlug(slug: string): boolean;
116
153
  /**
117
154
  * The live config a runtime-window built-in's cadence is resolved from. Today
118
- * the only runtime-window built-in is `hourly-check`, whose firing window is
119
- * `buildHourlyCronExpr(intervalMinutes, activeStart, activeEnd)` — so the three
120
- * `hourlyCheck*` fields are all that's needed. A narrow shape (not the whole
155
+ * the only runtime-window built-in is `activity-scan`, whose firing window is
156
+ * `buildActivityScanCronExpr(intervalMinutes, activeStart, activeEnd)` — so the three
157
+ * `activityScan*` fields are all that's needed. A narrow shape (not the whole
121
158
  * `AgentConfig`) keeps this module pure / coverage-friendly.
122
159
  */
123
160
  export interface RuntimeWindowCadenceConfig {
124
- hourlyCheckIntervalMinutes: number;
125
- hourlyCheckActiveStartHour: number;
126
- hourlyCheckActiveEndHour: number;
161
+ activityScanIntervalMinutes: number;
162
+ activityScanActiveStartHour: number;
163
+ activityScanActiveEndHour: number;
127
164
  }
128
165
  /**
129
166
  * The interval cadence of a runtime-window built-in: how often it fires
@@ -146,8 +183,8 @@ export interface IntervalCadence {
146
183
  *
147
184
  * A built-in is "runtime-window" iff its registry `cronExpression` resolver is
148
185
  * `null` — the documented marker (§5.5.1) that its cadence is owned by
149
- * `buildHourlyCronExpr` from live config, not a baked cron. Today `hourly-check`
150
- * is the sole such entry, so the mapping reads the `hourlyCheck*` config. A
186
+ * `buildActivityScanCronExpr` from live config, not a baked cron. Today `activity-scan`
187
+ * is the sole such entry, so the mapping reads the `activityScan*` config. A
151
188
  * second runtime-window built-in would need its own branch here — the
152
189
  * `cronExpression === null` gate plus this comment keep that requirement
153
190
  * visible. Pure: the caller supplies the live config (read at request time so a
@@ -1,3 +1,15 @@
1
+ /**
2
+ * Hub grouping for the `/agents` dashboard (AGENTS_HUB_REDESIGN_PLAN.md §1).
3
+ * Built-ins declare one of the three operational categories; user Agents are
4
+ * implicitly `"user"` at the API layer (not a registry value).
5
+ *
6
+ * - `synthesis` — routines that write the user-facing synthesis surfaces
7
+ * (today.md, journals, reviews).
8
+ * - `monitoring` — interval watchers that triage observations and surface
9
+ * activity proactively.
10
+ * - `maintenance` — mechanical / background upkeep passes.
11
+ */
12
+ export const AGENT_CATEGORIES = ["synthesis", "monitoring", "maintenance"];
1
13
  /**
2
14
  * Backward-wrapping hour arithmetic shared by the `{dayBoundaryHour-1}`
3
15
  * builtins. `(dayBoundaryHour + 23) % 24` in the scheduler is the same value;
@@ -25,6 +37,28 @@ export const BUILTIN_AGENT_REGISTRY = [
25
37
  reactivation_hint: "Re-enable from /agents/morning-routine. The next firing catches up with a broader observation window.",
26
38
  },
27
39
  schedulerFn: { kind: "queue_wake", routine: "morning_routine" },
40
+ category: "synthesis",
41
+ // journal-format / journal-export hang off morning-routine because the
42
+ // morning pipeline is their only consumer (task-flows
43
+ // `routine.morning_routine_{today,journal}` + the morning composers —
44
+ // verified 2026-06-10, AGENTS_HUB_REDESIGN_PLAN.md §1).
45
+ policyFiles: [
46
+ {
47
+ path: "policies/routines/morning.md",
48
+ label: "Morning rulebook",
49
+ description: "Checks and rules injected into the morning routine prompt.",
50
+ },
51
+ {
52
+ path: "policies/journal-format.md",
53
+ label: "Journal format",
54
+ description: "Template and voice rules for the daily journal synthesis.",
55
+ },
56
+ {
57
+ path: "policies/journal-export.md",
58
+ label: "Journal export rules",
59
+ description: "Redaction and inclusion rules applied when journal content is exported.",
60
+ },
61
+ ],
28
62
  },
29
63
  {
30
64
  slug: "evening-review",
@@ -43,6 +77,14 @@ export const BUILTIN_AGENT_REGISTRY = [
43
77
  reactivation_hint: "Re-enable from /agents/evening-review. Resumes on the next 18:00 firing.",
44
78
  },
45
79
  schedulerFn: { kind: "emit_routine", routine: "evening_review" },
80
+ category: "synthesis",
81
+ policyFiles: [
82
+ {
83
+ path: "policies/routines/evening.md",
84
+ label: "Evening rulebook",
85
+ description: "Checks and rules injected into the evening review prompt.",
86
+ },
87
+ ],
46
88
  },
47
89
  {
48
90
  slug: "weekly-review",
@@ -61,6 +103,14 @@ export const BUILTIN_AGENT_REGISTRY = [
61
103
  reactivation_hint: "Re-enable from /agents/weekly-review. Resumes on the next Friday 19:00 firing.",
62
104
  },
63
105
  schedulerFn: { kind: "emit_routine", routine: "weekly_review" },
106
+ category: "synthesis",
107
+ policyFiles: [
108
+ {
109
+ path: "policies/routines/weekly.md",
110
+ label: "Weekly rulebook",
111
+ description: "Checks and rules injected into the weekly review prompt.",
112
+ },
113
+ ],
64
114
  },
65
115
  {
66
116
  slug: "monthly-review",
@@ -78,33 +128,50 @@ export const BUILTIN_AGENT_REGISTRY = [
78
128
  "Month-end retrospective",
79
129
  ],
80
130
  dependent_agents: [],
81
- reactivation_hint: "Monthly review is opt-in (monthlyReviewEnabled, default off). Re-enable from /agents/monthly-review.",
131
+ reactivation_hint: "Monthly review is opt-in (off by default). Enable from /agents/monthly-review.",
82
132
  },
83
133
  schedulerFn: { kind: "emit_routine", routine: "monthly_review" },
134
+ category: "synthesis",
135
+ policyFiles: [
136
+ {
137
+ path: "policies/routines/monthly.md",
138
+ label: "Monthly rulebook",
139
+ description: "Checks and rules injected into the monthly review prompt.",
140
+ },
141
+ ],
84
142
  },
85
143
  {
86
- slug: "hourly-check",
87
- name: "Hourly Check",
144
+ slug: "activity-scan",
145
+ name: "Activity Scan",
88
146
  description: "Triages pending observations and proactively surfaces new mail / calendar / git / notion activity each interval within active hours.",
89
- // Runtime-window cadence — `buildHourlyCronExpr(...)` owns the expression,
147
+ // Runtime-window cadence — `buildActivityScanCronExpr(...)` owns the expression,
90
148
  // so the registry resolver is `null` and the loader's drift check skips
91
149
  // this slug (§5.5.1). The Phase 4 YAML still carries a self-documenting
92
150
  // literal to satisfy the schema's `cron → expression` refinement.
93
151
  cronExpression: null,
94
- processKey: "routine.hourly_check",
95
- // Agent-enabled defaults true; the feature's real opt-in is the scheduler's
96
- // `hourlyCheckEnabled` config gate, which is ANDed on top at fire time.
152
+ processKey: "routine.activity_scan",
153
+ // `agents.enabled` is the single on/off switch (AGENTS_HUB_REDESIGN_PLAN.md
154
+ // §2 — the legacy `activityScanEnabled` config gate was unified into it; a
155
+ // one-time boot reconcile carries an operator's old `false` forward).
97
156
  defaultEnabled: true,
98
157
  stopWarning: {
99
158
  level: "high",
100
159
  services_lost: [
101
- "Hourly observation triage",
160
+ "Periodic observation triage",
102
161
  "Proactive surfacing of new mail / calendar / git / notion activity",
103
162
  ],
104
163
  dependent_agents: [],
105
- reactivation_hint: "Re-enable from /agents/hourly-check. Resumes on the next interval tick within active hours.",
164
+ reactivation_hint: "Re-enable from /agents/activity-scan. Resumes on the next interval tick within active hours.",
106
165
  },
107
- schedulerFn: { kind: "in_process_callback", callbackName: "onHourlyCheck" },
166
+ schedulerFn: { kind: "in_process_callback", callbackName: "onActivityScan" },
167
+ category: "monitoring",
168
+ policyFiles: [
169
+ {
170
+ path: "policies/routines/activity-scan.md",
171
+ label: "Activity scan rulebook",
172
+ description: "Checks and rules injected into the activity scan prompt.",
173
+ },
174
+ ],
108
175
  },
109
176
  {
110
177
  slug: "user-profile-sweep-morning",
@@ -128,6 +195,8 @@ export const BUILTIN_AGENT_REGISTRY = [
128
195
  routine: "user_profile_sweep",
129
196
  data: { phase: "morning" },
130
197
  },
198
+ category: "maintenance",
199
+ policyFiles: [],
131
200
  },
132
201
  {
133
202
  slug: "user-profile-sweep-evening",
@@ -151,6 +220,8 @@ export const BUILTIN_AGENT_REGISTRY = [
151
220
  routine: "user_profile_sweep",
152
221
  data: { phase: "evening" },
153
222
  },
223
+ category: "maintenance",
224
+ policyFiles: [],
154
225
  },
155
226
  {
156
227
  slug: "roadmap-maintenance",
@@ -174,6 +245,8 @@ export const BUILTIN_AGENT_REGISTRY = [
174
245
  kind: "in_process_callback",
175
246
  callbackName: "onRoadmapMaintenance",
176
247
  },
248
+ category: "maintenance",
249
+ policyFiles: [],
177
250
  },
178
251
  {
179
252
  slug: "context-index-reconcile",
@@ -197,6 +270,8 @@ export const BUILTIN_AGENT_REGISTRY = [
197
270
  kind: "in_process_callback",
198
271
  callbackName: "onContextIndexReconcile",
199
272
  },
273
+ category: "maintenance",
274
+ policyFiles: [],
200
275
  },
201
276
  {
202
277
  slug: "skill-curation",
@@ -218,6 +293,8 @@ export const BUILTIN_AGENT_REGISTRY = [
218
293
  // `cadence` (daily/weekly/monthly) is read from the DB at fire time, so it
219
294
  // is intentionally absent from `data` here — see BuiltinSchedulerFn.
220
295
  schedulerFn: { kind: "emit_routine", routine: "skill_curation" },
296
+ category: "maintenance",
297
+ policyFiles: [],
221
298
  },
222
299
  ];
223
300
  /** Slug → entry, for O(1) lookup from the loader and the scheduler gate. */
@@ -238,8 +315,8 @@ export function isBuiltinAgentSlug(slug) {
238
315
  *
239
316
  * A built-in is "runtime-window" iff its registry `cronExpression` resolver is
240
317
  * `null` — the documented marker (§5.5.1) that its cadence is owned by
241
- * `buildHourlyCronExpr` from live config, not a baked cron. Today `hourly-check`
242
- * is the sole such entry, so the mapping reads the `hourlyCheck*` config. A
318
+ * `buildActivityScanCronExpr` from live config, not a baked cron. Today `activity-scan`
319
+ * is the sole such entry, so the mapping reads the `activityScan*` config. A
243
320
  * second runtime-window built-in would need its own branch here — the
244
321
  * `cronExpression === null` gate plus this comment keep that requirement
245
322
  * visible. Pure: the caller supplies the live config (read at request time so a
@@ -250,8 +327,8 @@ export function resolveRuntimeWindowCadence(slug, config) {
250
327
  if (!entry || entry.cronExpression !== null)
251
328
  return null;
252
329
  return {
253
- interval_minutes: config.hourlyCheckIntervalMinutes,
254
- active_start_hour: config.hourlyCheckActiveStartHour,
255
- active_end_hour: config.hourlyCheckActiveEndHour,
330
+ interval_minutes: config.activityScanIntervalMinutes,
331
+ active_start_hour: config.activityScanActiveStartHour,
332
+ active_end_hour: config.activityScanActiveEndHour,
256
333
  };
257
334
  }
@@ -0,0 +1,38 @@
1
+ import type Database from "better-sqlite3";
2
+ /**
3
+ * One-time enable-switch unification (AGENTS_HUB_REDESIGN_PLAN.md §2).
4
+ *
5
+ * Pre-redesign, two built-ins carried a SECOND on/off switch in runtime
6
+ * config, ANDed on top of `agents.enabled` at fire time:
7
+ *
8
+ * - `activityScanEnabled` (persisted as `hourlyCheckEnabled` before the
9
+ * v0.1.11 rename; default true) → activity-scan
10
+ * - `monthlyReviewEnabled` (default false) → monthly-review
11
+ *
12
+ * The scheduler now consults only `agents.enabled`, so an operator's
13
+ * non-default config value must be carried onto the agent row exactly once —
14
+ * otherwise an old `hourlyCheckEnabled=false` would silently re-enable the
15
+ * activity scan on upgrade (and an opted-in monthly review would stop firing).
16
+ * The legacy settings row reaches `settings.activityScanEnabled` here via
17
+ * `LEGACY_RUNTIME_SETTING_KEY_ALIASES` (settings-store read aliasing), so a
18
+ * pre-agents-hub DB upgrading straight past the rename still carries its
19
+ * disable forward.
20
+ *
21
+ * Boot-time, not a DB `Migration`: it must run AFTER the agents loader has
22
+ * seeded the rows (migrations run before the loader), and it uses the same
23
+ * `setEnabled` dashboard-toggle semantics (stamps `enabled_overridden_at`).
24
+ * Idempotent via the `runtime_state` flag; defaults produce zero changes, so
25
+ * a fresh install is a flagged no-op.
26
+ */
27
+ export declare const CONFIG_GATES_RECONCILED_KEY = "agents.config_gates_reconciled";
28
+ export interface ConfigGateSettings {
29
+ activityScanEnabled: boolean;
30
+ monthlyReviewEnabled: boolean;
31
+ }
32
+ export interface ConfigGateReconcileResult {
33
+ /** False when the runtime_state flag showed the reconcile already ran. */
34
+ applied: boolean;
35
+ /** Slugs whose `agents.enabled` was changed (empty on default configs). */
36
+ changes: string[];
37
+ }
38
+ export declare function reconcileConfigGates(db: Database.Database, settings: ConfigGateSettings, now?: number): ConfigGateReconcileResult;
@@ -0,0 +1,51 @@
1
+ import { getAgent, setEnabled } from "../../db/agents-store.js";
2
+ import { readRuntimeState, writeRuntimeState } from "../../db/runtime-state.js";
3
+ import { createLogger } from "../../logging.js";
4
+ const logger = createLogger("agents-config-gate-reconcile");
5
+ /**
6
+ * One-time enable-switch unification (AGENTS_HUB_REDESIGN_PLAN.md §2).
7
+ *
8
+ * Pre-redesign, two built-ins carried a SECOND on/off switch in runtime
9
+ * config, ANDed on top of `agents.enabled` at fire time:
10
+ *
11
+ * - `activityScanEnabled` (persisted as `hourlyCheckEnabled` before the
12
+ * v0.1.11 rename; default true) → activity-scan
13
+ * - `monthlyReviewEnabled` (default false) → monthly-review
14
+ *
15
+ * The scheduler now consults only `agents.enabled`, so an operator's
16
+ * non-default config value must be carried onto the agent row exactly once —
17
+ * otherwise an old `hourlyCheckEnabled=false` would silently re-enable the
18
+ * activity scan on upgrade (and an opted-in monthly review would stop firing).
19
+ * The legacy settings row reaches `settings.activityScanEnabled` here via
20
+ * `LEGACY_RUNTIME_SETTING_KEY_ALIASES` (settings-store read aliasing), so a
21
+ * pre-agents-hub DB upgrading straight past the rename still carries its
22
+ * disable forward.
23
+ *
24
+ * Boot-time, not a DB `Migration`: it must run AFTER the agents loader has
25
+ * seeded the rows (migrations run before the loader), and it uses the same
26
+ * `setEnabled` dashboard-toggle semantics (stamps `enabled_overridden_at`).
27
+ * Idempotent via the `runtime_state` flag; defaults produce zero changes, so
28
+ * a fresh install is a flagged no-op.
29
+ */
30
+ export const CONFIG_GATES_RECONCILED_KEY = "agents.config_gates_reconciled";
31
+ export function reconcileConfigGates(db, settings, now = Date.now()) {
32
+ if (readRuntimeState(db, CONFIG_GATES_RECONCILED_KEY) !== null) {
33
+ return { applied: false, changes: [] };
34
+ }
35
+ const changes = [];
36
+ // Non-default legacy value → carry onto the agent row. Default values are
37
+ // left alone so the YAML/registry-shipped enabled state stays authoritative.
38
+ if (!settings.activityScanEnabled && getAgent(db, "activity-scan")?.enabled === true) {
39
+ setEnabled(db, "activity-scan", false, now, now);
40
+ changes.push("activity-scan");
41
+ }
42
+ if (settings.monthlyReviewEnabled && getAgent(db, "monthly-review")?.enabled === false) {
43
+ setEnabled(db, "monthly-review", true, now, now);
44
+ changes.push("monthly-review");
45
+ }
46
+ writeRuntimeState(db, CONFIG_GATES_RECONCILED_KEY, new Date(now).toISOString());
47
+ if (changes.length > 0) {
48
+ logger.info({ changes }, "Carried legacy config enable gates onto agent rows");
49
+ }
50
+ return { applied: true, changes };
51
+ }