@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
@@ -36,7 +36,7 @@ export declare function substituteCron(expr: string, config: {
36
36
  * expression. Returns a non-fatal warning string on mismatch, or `null` when
37
37
  * they agree.
38
38
  *
39
- * `registryExpr` is `null` for the runtime-window builtins (`hourly-check`)
39
+ * `registryExpr` is `null` for the runtime-window builtins (`activity-scan`)
40
40
  * whose cadence is not a fixed expression — drift is meaningless there, so the
41
41
  * check is a no-op (returns `null`, §5.5.1). Whitespace differences are
42
42
  * normalised away before comparing.
@@ -57,7 +57,7 @@ function normalizeCron(expr) {
57
57
  * expression. Returns a non-fatal warning string on mismatch, or `null` when
58
58
  * they agree.
59
59
  *
60
- * `registryExpr` is `null` for the runtime-window builtins (`hourly-check`)
60
+ * `registryExpr` is `null` for the runtime-window builtins (`activity-scan`)
61
61
  * whose cadence is not a fixed expression — drift is meaningless there, so the
62
62
  * check is a no-op (returns `null`, §5.5.1). Whitespace differences are
63
63
  * normalised away before comparing.
@@ -0,0 +1,60 @@
1
+ import type Database from "better-sqlite3";
2
+ /**
3
+ * One-time converter: legacy custom routines → user Agents
4
+ * (AGENTS_HUB_REDESIGN_PLAN.md §3).
5
+ *
6
+ * The retired `CustomRoutineScheduler` fired `policies/routines/custom/
7
+ * <slug>.md` files through its own node-cron path — invisible to `/agents`,
8
+ * no metrics, no execution history, a second enable switch. This converter
9
+ * runs at boot BEFORE the agents loader (so the loader pairs the fresh
10
+ * definitions with `recurring_schedules` rows in the same pass), turning each
11
+ * valid spec into `policies/agents/<slug>/agent.md`:
12
+ *
13
+ * - frontmatter: cron schedule, tier, `max_budget_usd`, the spec's
14
+ * `enabled` (a disabled routine migrates disabled — intent preserved);
15
+ * - body: the routine's body after the legacy frontmatter (the `## Checks`
16
+ * section), which the Agents pipeline uses as `task_prompt`.
17
+ *
18
+ * The source file is never deleted (user vault content): it is rewritten in
19
+ * place with `enabled: false` + a `migrated_to_agent:` marker so it is
20
+ * visibly inert. Invalid specs are left untouched and logged — they never
21
+ * fired under the old scheduler either.
22
+ *
23
+ * Idempotent via the `runtime_state` flag; a fresh install (no custom dir)
24
+ * is a flagged no-op.
25
+ */
26
+ export declare const CUSTOM_ROUTINES_MIGRATED_KEY = "custom_routines.migrated_to_agents";
27
+ export interface CustomRoutineMigrationOptions {
28
+ /** Context-vault root (the directory holding `policies/`). */
29
+ contextDir: string;
30
+ /** User agents root — `<contextDir>/policies/agents`. */
31
+ userDir: string;
32
+ /** IANA timezone the generated cron schedule is pinned to. */
33
+ timezone: string;
34
+ now?: () => number;
35
+ }
36
+ export interface CustomRoutineMigrationResult {
37
+ /** False when the runtime_state flag showed the migration already ran. */
38
+ applied: boolean;
39
+ migrated: Array<{
40
+ fromSlug: string;
41
+ toSlug: string;
42
+ }>;
43
+ skipped: Array<{
44
+ slug: string;
45
+ reason: string;
46
+ }>;
47
+ }
48
+ /**
49
+ * Body after the closing `---` fence, or the whole content if no frontmatter.
50
+ * Exported for direct unit tests — callers only ever pass parse-validated
51
+ * content, so the defensive branches are unreachable through the public path.
52
+ */
53
+ export declare function stripLegacyFrontmatter(content: string): string;
54
+ /**
55
+ * Mark a migrated source file inert: `enabled: false` + a
56
+ * `migrated_to_agent:` marker appended inside the frontmatter block.
57
+ * Exported for direct unit tests (see stripLegacyFrontmatter note).
58
+ */
59
+ export declare function markSourceMigrated(content: string, toSlug: string): string;
60
+ export declare function migrateCustomRoutinesToAgents(db: Database.Database, opts: CustomRoutineMigrationOptions): CustomRoutineMigrationResult;
@@ -0,0 +1,149 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { agentDefinitionSchema } from "@aitne/shared";
4
+ import { enumerateCustomRoutines, } from "../custom-routines.js";
5
+ import { CONTEXT_RELATIVE_PATHS } from "../context-paths.js";
6
+ import { renderAgentMarkdown } from "./agent-frontmatter.js";
7
+ import { definitionToFrontmatter } from "./loader.js";
8
+ import { isBuiltinAgentSlug } from "./builtin-registry.js";
9
+ import { getAgent } from "../../db/agents-store.js";
10
+ import { readRuntimeState, writeRuntimeState } from "../../db/runtime-state.js";
11
+ import { createLogger } from "../../logging.js";
12
+ const logger = createLogger("custom-routine-migration");
13
+ /**
14
+ * One-time converter: legacy custom routines → user Agents
15
+ * (AGENTS_HUB_REDESIGN_PLAN.md §3).
16
+ *
17
+ * The retired `CustomRoutineScheduler` fired `policies/routines/custom/
18
+ * <slug>.md` files through its own node-cron path — invisible to `/agents`,
19
+ * no metrics, no execution history, a second enable switch. This converter
20
+ * runs at boot BEFORE the agents loader (so the loader pairs the fresh
21
+ * definitions with `recurring_schedules` rows in the same pass), turning each
22
+ * valid spec into `policies/agents/<slug>/agent.md`:
23
+ *
24
+ * - frontmatter: cron schedule, tier, `max_budget_usd`, the spec's
25
+ * `enabled` (a disabled routine migrates disabled — intent preserved);
26
+ * - body: the routine's body after the legacy frontmatter (the `## Checks`
27
+ * section), which the Agents pipeline uses as `task_prompt`.
28
+ *
29
+ * The source file is never deleted (user vault content): it is rewritten in
30
+ * place with `enabled: false` + a `migrated_to_agent:` marker so it is
31
+ * visibly inert. Invalid specs are left untouched and logged — they never
32
+ * fired under the old scheduler either.
33
+ *
34
+ * Idempotent via the `runtime_state` flag; a fresh install (no custom dir)
35
+ * is a flagged no-op.
36
+ */
37
+ export const CUSTOM_ROUTINES_MIGRATED_KEY = "custom_routines.migrated_to_agents";
38
+ /**
39
+ * Body after the closing `---` fence, or the whole content if no frontmatter.
40
+ * Exported for direct unit tests — callers only ever pass parse-validated
41
+ * content, so the defensive branches are unreachable through the public path.
42
+ */
43
+ export function stripLegacyFrontmatter(content) {
44
+ if (!content.startsWith("---\n") && !content.startsWith("---\r\n"))
45
+ return content;
46
+ const afterOpen = content.startsWith("---\r\n") ? 5 : 4;
47
+ const endIdx = content.indexOf("\n---", afterOpen - 1);
48
+ if (endIdx < 0)
49
+ return content;
50
+ const afterFence = content.indexOf("\n", endIdx + 1);
51
+ return afterFence < 0 ? "" : content.slice(afterFence + 1);
52
+ }
53
+ /**
54
+ * Mark a migrated source file inert: `enabled: false` + a
55
+ * `migrated_to_agent:` marker appended inside the frontmatter block.
56
+ * Exported for direct unit tests (see stripLegacyFrontmatter note).
57
+ */
58
+ export function markSourceMigrated(content, toSlug) {
59
+ let out = content.replace(/^enabled\s*:.*$/m, "enabled: false");
60
+ if (!/^migrated_to_agent\s*:/m.test(out)) {
61
+ // Insert before the closing fence of the first frontmatter block.
62
+ const afterOpen = out.startsWith("---\r\n") ? 5 : 4;
63
+ const endIdx = out.indexOf("\n---", afterOpen - 1);
64
+ if (endIdx >= 0) {
65
+ out = `${out.slice(0, endIdx)}\nmigrated_to_agent: ${toSlug}${out.slice(endIdx)}`;
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+ function titleize(slug) {
71
+ return slug
72
+ .split("-")
73
+ .map((part) => (part.length > 0 ? part[0].toUpperCase() + part.slice(1) : part))
74
+ .join(" ");
75
+ }
76
+ function resolveTargetSlug(db, userDir, slug) {
77
+ const candidates = [slug, `custom-${slug}`];
78
+ for (const candidate of candidates) {
79
+ if (isBuiltinAgentSlug(candidate))
80
+ continue;
81
+ if (getAgent(db, candidate) !== null)
82
+ continue;
83
+ if (existsSync(join(userDir, candidate, "agent.md")))
84
+ continue;
85
+ return candidate;
86
+ }
87
+ return null;
88
+ }
89
+ export function migrateCustomRoutinesToAgents(db, opts) {
90
+ if (readRuntimeState(db, CUSTOM_ROUTINES_MIGRATED_KEY) !== null) {
91
+ return { applied: false, migrated: [], skipped: [] };
92
+ }
93
+ const now = opts.now ?? Date.now;
94
+ const { specs, errors } = enumerateCustomRoutines(opts.contextDir);
95
+ const migrated = [];
96
+ const skipped = [];
97
+ for (const { slug, error } of errors) {
98
+ skipped.push({ slug, reason: `parse_error:${error.kind}` });
99
+ logger.warn({ slug, error }, "Custom routine not migrated (parse error) — left as-is");
100
+ }
101
+ for (const spec of specs) {
102
+ try {
103
+ const result = migrateOne(db, opts, spec);
104
+ if (result.ok) {
105
+ migrated.push({ fromSlug: spec.slug, toSlug: result.toSlug });
106
+ }
107
+ else {
108
+ skipped.push({ slug: spec.slug, reason: result.reason });
109
+ }
110
+ }
111
+ catch (err) {
112
+ skipped.push({ slug: spec.slug, reason: "write_failed" });
113
+ logger.error({ err, slug: spec.slug }, "Custom routine migration failed for slug");
114
+ }
115
+ }
116
+ writeRuntimeState(db, CUSTOM_ROUTINES_MIGRATED_KEY, new Date(now()).toISOString());
117
+ if (migrated.length > 0 || skipped.length > 0) {
118
+ logger.info({ migrated, skipped }, "Custom routines migrated to user Agents");
119
+ }
120
+ return { applied: true, migrated, skipped };
121
+ }
122
+ function migrateOne(db, opts, spec) {
123
+ const toSlug = resolveTargetSlug(db, opts.userDir, spec.slug);
124
+ if (toSlug === null) {
125
+ logger.warn({ slug: spec.slug }, "Custom routine not migrated (slug collision)");
126
+ return { ok: false, reason: "slug_collision" };
127
+ }
128
+ const sourcePath = join(opts.contextDir, CONTEXT_RELATIVE_PATHS.routines.customDir, `${spec.slug}.md`);
129
+ const sourceContent = readFileSync(sourcePath, "utf-8");
130
+ const body = stripLegacyFrontmatter(sourceContent).trim();
131
+ const def = agentDefinitionSchema.parse({
132
+ slug: toSlug,
133
+ name: titleize(spec.slug),
134
+ description: `Migrated from custom routine "${spec.slug}".`,
135
+ kind: "user",
136
+ enabled: spec.enabled,
137
+ schedule: { kind: "cron", expression: spec.cron, timezone: opts.timezone },
138
+ backend: { process_key: "agent.task", tier: spec.backendTier },
139
+ limits: { max_budget_usd: spec.maxBudgetUsd },
140
+ });
141
+ const markdown = renderAgentMarkdown(definitionToFrontmatter(def), body.length > 0 ? body : def.description);
142
+ const dir = join(opts.userDir, toSlug);
143
+ mkdirSync(dir, { recursive: true });
144
+ writeFileSync(join(dir, "agent.md"), markdown, "utf-8");
145
+ // Mark the source inert only AFTER the agent.md write succeeded.
146
+ writeFileSync(sourcePath, markSourceMigrated(sourceContent, toSlug), "utf-8");
147
+ logger.info({ from: spec.slug, to: toSlug }, "Custom routine migrated to user Agent");
148
+ return { ok: true, toSlug };
149
+ }
@@ -3,7 +3,7 @@ import type Database from "better-sqlite3";
3
3
  * `agent.firing_blocked` audit throttle (AGENT_DEFINITIONS_DESIGN.md §12.3).
4
4
  *
5
5
  * When a disabled built-in Agent's cron would have fired, the scheduler records
6
- * the suppressed firing — but a routine on a tight cadence (hourly_check) could
6
+ * the suppressed firing — but a routine on a tight cadence (activity_scan) could
7
7
  * otherwise write dozens of identical audit rows a day. The throttle keeps **at
8
8
  * most one** `agent_actions(action_type='agent.firing_blocked')` row per
9
9
  * `(agent_id, agent-day)`; every subsequent suppression in the same agent-day
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Hourly-check cadence resolution (AGENTS_HUB_REDESIGN_PLAN.md §2).
3
+ *
4
+ * The hourly-check 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
+ * hourly-check` (`schedule_window` body block) and preserved across loader
8
+ * re-runs / `npm i -g` by `loader.ts:nextMetadata`. The legacy `hourlyCheck*`
9
+ * config keys are deprecated but still parsed; they act as the per-field
10
+ * fallback so a value an operator persisted pre-redesign keeps working until
11
+ * they touch the agent-level setting.
12
+ *
13
+ * Resolution order, per field: `runtime_window` override → legacy config key
14
+ * (which itself carries the shipped default). Pure module — callers fetch the
15
+ * stored override via `agents-store.ts:getRuntimeWindow` and supply the live
16
+ * config; this keeps the precedence logic in the 100%-coverage set.
17
+ */
18
+ /** Field bounds, shared by the PATCH validator and the sanitizer. */
19
+ export declare const RUNTIME_WINDOW_BOUNDS: {
20
+ readonly interval_minutes: {
21
+ readonly min: 5;
22
+ readonly max: 1440;
23
+ };
24
+ readonly active_start_hour: {
25
+ readonly min: 0;
26
+ readonly max: 23;
27
+ };
28
+ readonly active_end_hour: {
29
+ readonly min: 1;
30
+ readonly max: 24;
31
+ };
32
+ readonly min_observations: {
33
+ readonly min: 0;
34
+ readonly max: 1000;
35
+ };
36
+ };
37
+ export type RuntimeWindowField = keyof typeof RUNTIME_WINDOW_BOUNDS;
38
+ export declare const RUNTIME_WINDOW_FIELDS: readonly RuntimeWindowField[];
39
+ /**
40
+ * The persisted shape under `metadata_json.runtime_window`. Every field is
41
+ * optional — only operator-touched fields are stored, so an untouched field
42
+ * keeps tracking the config fallback.
43
+ */
44
+ export interface RuntimeWindowOverride {
45
+ interval_minutes?: number;
46
+ active_start_hour?: number;
47
+ active_end_hour?: number;
48
+ min_observations?: number;
49
+ }
50
+ /**
51
+ * The legacy config keys the resolver falls back to. Fields are optional at
52
+ * the type level so partially-stubbed test configs (and any pre-schema boot
53
+ * edge) resolve to the shipped defaults instead of producing a `NaN` cron.
54
+ */
55
+ export interface HourlyCadenceConfig {
56
+ hourlyCheckIntervalMinutes?: number;
57
+ hourlyCheckActiveStartHour?: number;
58
+ hourlyCheckActiveEndHour?: number;
59
+ hourlyCheckMinObservations?: number;
60
+ }
61
+ /** Shipped defaults — mirror `runtime-settings.ts` (`hourlyCheck*` keys). */
62
+ export declare const HOURLY_CADENCE_DEFAULTS: {
63
+ readonly intervalMinutes: 60;
64
+ readonly activeStartHour: 4;
65
+ readonly activeEndHour: 24;
66
+ readonly minObservations: 1;
67
+ };
68
+ /** Fully-resolved cadence every consumer (scheduler, gate, API) reads. */
69
+ export interface ResolvedHourlyCadence {
70
+ intervalMinutes: number;
71
+ activeStartHour: number;
72
+ activeEndHour: number;
73
+ minObservations: number;
74
+ }
75
+ /** True when `value` is an integer within the field's bounds. */
76
+ export declare function isValidRuntimeWindowValue(field: RuntimeWindowField, value: unknown): value is number;
77
+ /**
78
+ * Sanitize a raw `metadata_json.runtime_window` blob (untrusted: hand-edited
79
+ * DBs, older daemons). Out-of-bounds / non-integer fields are dropped — the
80
+ * resolver then falls back to config for them rather than failing the boot.
81
+ */
82
+ export declare function parseRuntimeWindowOverride(value: unknown): RuntimeWindowOverride;
83
+ export declare function resolveHourlyCadence(override: RuntimeWindowOverride | undefined, config: HourlyCadenceConfig): ResolvedHourlyCadence;
84
+ export type RuntimeWindowMergeResult = {
85
+ ok: true;
86
+ value: RuntimeWindowOverride;
87
+ cadenceChanged: boolean;
88
+ } | {
89
+ ok: false;
90
+ field: string;
91
+ error: "invalid_field_value" | "invalid_window";
92
+ };
93
+ /**
94
+ * Merge a PATCH `schedule_window` block onto the stored override. Per-field
95
+ * type/bounds are validated; the cross-field window check (`end > start`) runs
96
+ * against the post-merge **resolved** values so a partial patch can't sneak an
97
+ * empty window past per-field validation. `null` resets a field back to the
98
+ * config fallback. `cadenceChanged` tells the route whether a cron rebuild
99
+ * (`reloadCrons`) is needed — `min_observations` is a fire-time gate and never
100
+ * requires one.
101
+ */
102
+ export declare function mergeRuntimeWindow(current: RuntimeWindowOverride, patch: Record<string, unknown>, config: HourlyCadenceConfig): RuntimeWindowMergeResult;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Hourly-check cadence resolution (AGENTS_HUB_REDESIGN_PLAN.md §2).
3
+ *
4
+ * The hourly-check 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
+ * hourly-check` (`schedule_window` body block) and preserved across loader
8
+ * re-runs / `npm i -g` by `loader.ts:nextMetadata`. The legacy `hourlyCheck*`
9
+ * config keys are deprecated but still parsed; they act as the per-field
10
+ * fallback so a value an operator persisted pre-redesign keeps working until
11
+ * they touch the agent-level setting.
12
+ *
13
+ * Resolution order, per field: `runtime_window` override → legacy config key
14
+ * (which itself carries the shipped default). Pure module — callers fetch the
15
+ * stored override via `agents-store.ts:getRuntimeWindow` and supply the live
16
+ * config; this keeps the precedence logic in the 100%-coverage set.
17
+ */
18
+ /** Field bounds, shared by the PATCH validator and the sanitizer. */
19
+ export const RUNTIME_WINDOW_BOUNDS = {
20
+ interval_minutes: { min: 5, max: 1440 },
21
+ active_start_hour: { min: 0, max: 23 },
22
+ active_end_hour: { min: 1, max: 24 },
23
+ min_observations: { min: 0, max: 1000 },
24
+ };
25
+ export const RUNTIME_WINDOW_FIELDS = Object.keys(RUNTIME_WINDOW_BOUNDS);
26
+ /** Shipped defaults — mirror `runtime-settings.ts` (`hourlyCheck*` keys). */
27
+ export const HOURLY_CADENCE_DEFAULTS = {
28
+ intervalMinutes: 60,
29
+ activeStartHour: 4,
30
+ activeEndHour: 24,
31
+ minObservations: 1,
32
+ };
33
+ /** True when `value` is an integer within the field's bounds. */
34
+ export function isValidRuntimeWindowValue(field, value) {
35
+ if (typeof value !== "number" || !Number.isInteger(value))
36
+ return false;
37
+ const { min, max } = RUNTIME_WINDOW_BOUNDS[field];
38
+ return value >= min && value <= max;
39
+ }
40
+ /**
41
+ * Sanitize a raw `metadata_json.runtime_window` blob (untrusted: hand-edited
42
+ * DBs, older daemons). Out-of-bounds / non-integer fields are dropped — the
43
+ * resolver then falls back to config for them rather than failing the boot.
44
+ */
45
+ export function parseRuntimeWindowOverride(value) {
46
+ if (typeof value !== "object" || value === null || Array.isArray(value))
47
+ return {};
48
+ const raw = value;
49
+ const out = {};
50
+ for (const field of RUNTIME_WINDOW_FIELDS) {
51
+ const v = raw[field];
52
+ if (isValidRuntimeWindowValue(field, v))
53
+ out[field] = v;
54
+ }
55
+ return out;
56
+ }
57
+ /**
58
+ * Resolve the effective cadence: per-field `runtime_window` override, else the
59
+ * legacy config key. A start ≥ end pairing (possible when an override touches
60
+ * only one side of an old config window) is repaired by widening the end to
61
+ * `start + 1` so `buildHourlyCronExpr` always receives a non-empty window.
62
+ */
63
+ /** Pick `v` when it is a finite number, else `fallback`. */
64
+ function num(v, fallback) {
65
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
66
+ }
67
+ export function resolveHourlyCadence(override, config) {
68
+ const o = override ?? {};
69
+ const activeStartHour = o.active_start_hour
70
+ ?? num(config.hourlyCheckActiveStartHour, HOURLY_CADENCE_DEFAULTS.activeStartHour);
71
+ let activeEndHour = o.active_end_hour
72
+ ?? num(config.hourlyCheckActiveEndHour, HOURLY_CADENCE_DEFAULTS.activeEndHour);
73
+ if (activeEndHour <= activeStartHour)
74
+ activeEndHour = Math.min(activeStartHour + 1, 24);
75
+ return {
76
+ intervalMinutes: o.interval_minutes
77
+ ?? num(config.hourlyCheckIntervalMinutes, HOURLY_CADENCE_DEFAULTS.intervalMinutes),
78
+ activeStartHour,
79
+ activeEndHour,
80
+ minObservations: o.min_observations
81
+ ?? num(config.hourlyCheckMinObservations, HOURLY_CADENCE_DEFAULTS.minObservations),
82
+ };
83
+ }
84
+ /**
85
+ * Merge a PATCH `schedule_window` block onto the stored override. Per-field
86
+ * type/bounds are validated; the cross-field window check (`end > start`) runs
87
+ * against the post-merge **resolved** values so a partial patch can't sneak an
88
+ * empty window past per-field validation. `null` resets a field back to the
89
+ * config fallback. `cadenceChanged` tells the route whether a cron rebuild
90
+ * (`reloadCrons`) is needed — `min_observations` is a fire-time gate and never
91
+ * requires one.
92
+ */
93
+ export function mergeRuntimeWindow(current, patch, config) {
94
+ const next = { ...current };
95
+ let cadenceChanged = false;
96
+ for (const [key, value] of Object.entries(patch)) {
97
+ if (!RUNTIME_WINDOW_FIELDS.includes(key)) {
98
+ return { ok: false, field: `schedule_window.${key}`, error: "invalid_field_value" };
99
+ }
100
+ const field = key;
101
+ if (value === null) {
102
+ if (next[field] !== undefined) {
103
+ delete next[field];
104
+ if (field !== "min_observations")
105
+ cadenceChanged = true;
106
+ }
107
+ continue;
108
+ }
109
+ if (!isValidRuntimeWindowValue(field, value)) {
110
+ return { ok: false, field: `schedule_window.${field}`, error: "invalid_field_value" };
111
+ }
112
+ if (next[field] !== value) {
113
+ next[field] = value;
114
+ if (field !== "min_observations")
115
+ cadenceChanged = true;
116
+ }
117
+ }
118
+ const requestedEnd = next.active_end_hour
119
+ ?? num(config.hourlyCheckActiveEndHour, HOURLY_CADENCE_DEFAULTS.activeEndHour);
120
+ const requestedStart = next.active_start_hour
121
+ ?? num(config.hourlyCheckActiveStartHour, HOURLY_CADENCE_DEFAULTS.activeStartHour);
122
+ if (requestedEnd <= requestedStart) {
123
+ return { ok: false, field: "schedule_window.active_end_hour", error: "invalid_window" };
124
+ }
125
+ return { ok: true, value: next, cadenceChanged };
126
+ }
@@ -4,6 +4,8 @@ import { createLogger } from "../../logging.js";
4
4
  import { AgentEnabledCache, loadAgents, resolveTimezone, } from "./loader.js";
5
5
  import { startAgentsWatcher, } from "./loader-watcher.js";
6
6
  import { createRecurringSchedulePort } from "./recurring-schedule-adapter.js";
7
+ import { reconcileConfigGates } from "./config-gate-reconcile.js";
8
+ import { migrateCustomRoutinesToAgents } from "./custom-routine-migration.js";
7
9
  import { listAllSkillSlugs } from "../release-assets.js";
8
10
  import { resolveUserSkillsRoot } from "../user-skills-root.js";
9
11
  /**
@@ -75,7 +77,28 @@ export function buildAgentLoadOptions(deps) {
75
77
  */
76
78
  export function bootstrapAgents(deps) {
77
79
  const opts = buildAgentLoadOptions(deps);
80
+ // One-time legacy custom-routine conversion (AGENTS_HUB_REDESIGN_PLAN §3).
81
+ // Runs BEFORE loadAgents so the loader pairs the generated user agent.md
82
+ // files with recurring_schedules rows in this same boot pass. Never throws
83
+ // out of boot.
84
+ try {
85
+ migrateCustomRoutinesToAgents(deps.db, {
86
+ contextDir: getContextDir(deps.config, deps.db),
87
+ userDir: opts.userDir,
88
+ timezone: resolveTimezone(undefined, deps.config.timezone),
89
+ });
90
+ }
91
+ catch (err) {
92
+ logger.error({ err }, "Custom-routine migration failed (continuing boot)");
93
+ }
78
94
  const result = loadAgents(deps.db, opts);
95
+ // One-time legacy gate carry-over (activityScanEnabled / monthlyReviewEnabled
96
+ // → agents.enabled). Must run AFTER the loader has seeded the rows;
97
+ // runtime_state-flagged so it never re-applies (AGENTS_HUB_REDESIGN_PLAN §2).
98
+ reconcileConfigGates(deps.db, {
99
+ activityScanEnabled: deps.config.activityScanEnabled,
100
+ monthlyReviewEnabled: deps.config.monthlyReviewEnabled,
101
+ });
79
102
  if (result.invalid.length > 0) {
80
103
  logger.warn({ count: result.invalid.length, slugs: result.invalid.map((d) => d.slug) }, "Some Agent definitions failed to load (surfaced under /api/agents include_invalid)");
81
104
  }
@@ -51,6 +51,14 @@ export interface RecurringCreateInput {
51
51
  tier: string | null;
52
52
  backendId: string | null;
53
53
  recurrence: AgentRecurrenceSpec;
54
+ /**
55
+ * Row-local flags copied into every materialised `agent_schedule` row by
56
+ * `generateNextScheduleRow` (the `pin_to_quiet_hours_end` precedent). Today
57
+ * carries `defer_in_quiet_hours: true` for opted-in Agents
58
+ * (QUIET_HOURS_HARDENING_PLAN.md §6); omitted/empty otherwise (the store
59
+ * defaults the column to `{}`).
60
+ */
61
+ taskContext?: Record<string, unknown>;
54
62
  }
55
63
  /** Divergent-field patch applied when reconciling a paired recurring row (YAML wins). */
56
64
  export interface RecurringUpdateInput {
@@ -61,6 +69,9 @@ export interface RecurringUpdateInput {
61
69
  tier?: string | null;
62
70
  backendId?: string | null;
63
71
  recurrence?: AgentRecurrenceSpec;
72
+ /** Full replacement `task_context` (the loader merges off the row's current
73
+ * value, so unrelated keys survive a flag flip). */
74
+ taskContext?: Record<string, unknown>;
64
75
  }
65
76
  /**
66
77
  * Recurring-schedule pairing + auto-import port (§6.1 step 5 / §6.5). The
@@ -162,6 +173,14 @@ export declare function validateDefinition(def: AgentDefinition, source: AgentSo
162
173
  * row with no override) lets the YAML win.
163
174
  */
164
175
  export declare function resolveEnabled(baseEnabled: boolean, existing: AgentDTO | null, fileMtimeMs: number): boolean;
176
+ /**
177
+ * Render the subset of an `AgentDefinition` worth persisting to an auto-imported
178
+ * `agent.md` frontmatter. Schema defaults (limits/tools/on_error) are omitted
179
+ * to keep the generated file readable; they re-populate on the next parse.
180
+ * Exported for the custom-routine migration, which materialises user Agents
181
+ * through the same shape (AGENTS_HUB_REDESIGN_PLAN.md §3).
182
+ */
183
+ export declare function definitionToFrontmatter(def: AgentDefinition): Record<string, unknown>;
165
184
  /**
166
185
  * Load every Agent definition into the `agents` table. Never throws; per-file
167
186
  * failures land in {@link LoadAgentsResult.invalid}. Order: auto-import (writes
@@ -32,7 +32,7 @@ import { cronToRecurrenceSpec, recurrenceSpecToCron, } from "./recurrence-conver
32
32
  * the real daemon modules.
33
33
  */
34
34
  const baseLogger = createLogger("agents-loader");
35
- /** A built-in whose registry `cronExpression` is `null` (hourly-check) still
35
+ /** A built-in whose registry `cronExpression` is `null` (activity-scan) still
36
36
  * needs a schema-valid `schedule.expression` when synthesised from the
37
37
  * registry. The loader never schedules from it (the runtime window owns the
38
38
  * cadence, §5.5.1); this literal is self-documenting only and drift-free
@@ -236,6 +236,12 @@ function nextMetadata(existing, hashChanged) {
236
236
  if (prior.override_snapshot !== undefined) {
237
237
  meta.override_snapshot = prior.override_snapshot;
238
238
  }
239
+ // Runtime-window cadence overrides (activity-scan interval / active hours /
240
+ // observation gate) are operator state exactly like override_snapshot — they
241
+ // must survive every loader re-run and `npm i -g`.
242
+ if (prior.runtime_window !== undefined) {
243
+ meta.runtime_window = prior.runtime_window;
244
+ }
239
245
  const priorVersion = typeof prior.version_counter === "number" ? prior.version_counter : 0;
240
246
  meta.version_counter = hashChanged ? priorVersion + 1 : Math.max(priorVersion, 1);
241
247
  // last_error is intentionally dropped: this path runs only for a definition
@@ -372,6 +378,14 @@ function pairRecurring(def, expression, body, existing, port, resolvedTz, resolv
372
378
  tier: def.backend.tier,
373
379
  backendId: def.backend.backend_id,
374
380
  recurrence: spec,
381
+ // QUIET_HOURS_HARDENING_PLAN.md §6 — `generateNextScheduleRow` spreads the
382
+ // row's `task_context` into every materialised `agent_schedule` row, so the
383
+ // scheduler can read the flag row-locally at claim time (no `agents` join).
384
+ // Opt-in only: an absent key keeps default-false context clean (the
385
+ // `pin_to_quiet_hours_end` convention on `dm_session` rows).
386
+ taskContext: def.schedule.defer_in_quiet_hours
387
+ ? { defer_in_quiet_hours: true }
388
+ : {},
375
389
  };
376
390
  const existingId = existing?.recurringScheduleId ?? null;
377
391
  if (existingId !== null) {
@@ -403,6 +417,22 @@ function pairRecurring(def, expression, body, existing, port, resolvedTz, resolv
403
417
  // Structural compare so a re-parsed-but-identical spec is not a "change".
404
418
  if (stableStringify(row.recurrence) !== stableStringify(spec))
405
419
  patch.recurrence = spec;
420
+ // Reconcile the quiet-hours opt-in (YAML wins), merging off the row's
421
+ // current context so unrelated keys survive the flip. A `taskContext`
422
+ // update does NOT cancel + re-materialise the pending row (only
423
+ // recurrence/enabled do), so a pending materialisation keeps the prior
424
+ // flag until its next generation — the same one-cycle staleness the
425
+ // model/tier pins already have.
426
+ const rowOptedIn = row.taskContext.defer_in_quiet_hours === true;
427
+ const wantOptIn = def.schedule.defer_in_quiet_hours;
428
+ if (rowOptedIn !== wantOptIn) {
429
+ const nextContext = { ...row.taskContext };
430
+ if (wantOptIn)
431
+ nextContext.defer_in_quiet_hours = true;
432
+ else
433
+ delete nextContext.defer_in_quiet_hours;
434
+ patch.taskContext = nextContext;
435
+ }
406
436
  if (Object.keys(patch).length > 0)
407
437
  port.update(existingId, patch);
408
438
  return existingId;
@@ -504,8 +534,10 @@ function autoImportOrphans(db, opts, port, result, importedSlugs, logger, now) {
504
534
  * Render the subset of an `AgentDefinition` worth persisting to an auto-imported
505
535
  * `agent.md` frontmatter. Schema defaults (limits/tools/on_error) are omitted
506
536
  * to keep the generated file readable; they re-populate on the next parse.
537
+ * Exported for the custom-routine migration, which materialises user Agents
538
+ * through the same shape (AGENTS_HUB_REDESIGN_PLAN.md §3).
507
539
  */
508
- function definitionToFrontmatter(def) {
540
+ export function definitionToFrontmatter(def) {
509
541
  // Auto-import always builds a cron schedule with a resolved expression +
510
542
  // timezone, so both are emitted unconditionally (no dead-branch guards).
511
543
  const backend = { process_key: def.backend.process_key };
@@ -36,7 +36,7 @@ import type { AgentDefinition } from "@aitne/shared";
36
36
  * (no target). The loader resolves the real enabled-state from the
37
37
  * `agents.enabled_overridden_at` column via the §6.4 timestamp comparison.
38
38
  */
39
- export declare const MERGEABLE_OVERRIDE_PATHS: readonly ["enabled", "enabled_overridden_at", "backend.tier", "backend.model", "limits.max_turns", "limits.max_budget_usd", "limits.timeout_minutes", "on_error.notify_owner"];
39
+ export declare const MERGEABLE_OVERRIDE_PATHS: readonly ["enabled", "enabled_overridden_at", "backend.tier", "backend.model", "backend.backend_id", "limits.max_turns", "limits.max_budget_usd", "limits.timeout_minutes", "on_error.notify_owner"];
40
40
  export type MergeableOverridePath = (typeof MERGEABLE_OVERRIDE_PATHS)[number];
41
41
  /** True when `path` is one of the allow-listed override keys. */
42
42
  export declare function isMergeableOverridePath(path: string): path is MergeableOverridePath;
@@ -1,4 +1,4 @@
1
- import { AGENT_TIERS, OVERRIDE_EDIT_PATHS } from "@aitne/shared";
1
+ import { AGENT_TIERS, BACKEND_IDS, OVERRIDE_EDIT_PATHS } from "@aitne/shared";
2
2
  /**
3
3
  * Built-in override merge (AGENT_DEFINITIONS_DESIGN.md §6.4.1).
4
4
  *
@@ -53,6 +53,9 @@ export function isMergeableOverridePath(path) {
53
53
  function isAgentTier(value) {
54
54
  return typeof value === "string" && AGENT_TIERS.includes(value);
55
55
  }
56
+ function isBackendIdValue(value) {
57
+ return typeof value === "string" && BACKEND_IDS.includes(value);
58
+ }
56
59
  function isPositiveInt(value) {
57
60
  return typeof value === "number" && Number.isInteger(value) && value > 0;
58
61
  }
@@ -86,6 +89,11 @@ function applyOverridePath(def, path, value) {
86
89
  def.backend.model = value;
87
90
  }
88
91
  break;
92
+ case "backend.backend_id":
93
+ if (value === null || isBackendIdValue(value)) {
94
+ def.backend.backend_id = value;
95
+ }
96
+ break;
89
97
  case "limits.max_turns":
90
98
  if (isPositiveInt(value))
91
99
  def.limits.max_turns = value;
@@ -34,7 +34,7 @@
34
34
  * is the monthly `onMissingDay` policy, which cron cannot carry — it is dropped
35
35
  * on conversion (a `daysOfMonth` cron uses node-cron's own missing-day
36
36
  * behaviour). Sub-hourly intervals (every N minutes) are NOT representable —
37
- * only the built-in hourly-check supports those, via config.
37
+ * only the built-in activity-scan supports those, via config.
38
38
  */
39
39
  /** Loader-local mirror of the daemon `recurrence_rule` JSON (§2.2). Covers the
40
40
  * four cron-representable frequencies (`hourly` via the step-form cron). The