@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
@@ -127,7 +127,7 @@ export const EVENT_SKILL_SETS = {
127
127
  "roadmap",
128
128
  "management-policy",
129
129
  ],
130
- "routine.hourly_check": [
130
+ "routine.activity_scan": [
131
131
  "context",
132
132
  "today",
133
133
  "observations",
@@ -260,6 +260,14 @@ export const EVENT_SKILL_SETS = {
260
260
  // (no prior research offer to respond to) — that exclusion does
261
261
  // not generalise.
262
262
  "browser-task",
263
+ // BACKGROUND_TASK_RUNNER_DESIGN.md §5 / Phase 3 — DM-driven entry
264
+ // point to hand a long-running / open-ended task to the detached
265
+ // background runner. Loaded for the first DM too: the user's literal
266
+ // first message ("research X for me", "audit my repos") is exactly
267
+ // the spawn surface. The `background-task-reply` clarify skill is
268
+ // correctly absent here — no prior task can be parked on the very
269
+ // first DM of a session — exactly as `browser-history-respond` is.
270
+ "background-task",
263
271
  ],
264
272
  "message.received.dm": [
265
273
  "context",
@@ -305,6 +313,20 @@ export const EVENT_SKILL_SETS = {
305
313
  // the agent simply does not call its endpoints when the user's
306
314
  // request doesn't ask for a browser task.
307
315
  "browser-task",
316
+ // BACKGROUND_TASK_RUNNER_DESIGN.md §5 / Phase 3 — the generic
317
+ // long-running-task surface. `background-task` teaches the DM agent
318
+ // to compose a self-contained brief, set the notification policy,
319
+ // POST + ack + end the turn, and read `GET /:id` for follow-up
320
+ // detail. `background-task-reply` is the narrow clarify-relay (mirror
321
+ // of `browser-history-respond`) that translates the owner's answer to
322
+ // a parked task's question into `POST /:id/clarify`. Loaded for
323
+ // `message.received.dm` (which `message.dm` + `dashboard.chat`
324
+ // inherit via PROCESS_TO_EVENT_TYPE). The reply skill is NOT on
325
+ // `message.received.dm_first` (no task can be parked on a session's
326
+ // first DM). No conditional gate: both skill bodies no-op when their
327
+ // endpoints have nothing to act on, and the in-context cost is small.
328
+ "background-task",
329
+ "background-task-reply",
308
330
  ],
309
331
  // ── Task events ──
310
332
  "schedule.approaching": [
@@ -564,6 +586,13 @@ export const ALL_SKILLS = [
564
586
  // `browser-history-managed` skill that fronted the frozen workflow
565
587
  // registry was retired alongside the route + routines in Phase 6.
566
588
  "browser-task",
589
+ // BACKGROUND_TASK_RUNNER_DESIGN.md §5 / Phase 3 — generic detached
590
+ // long-task surface. `background-task` (spawn) loads on
591
+ // `message.received.dm` + `message.received.dm_first`;
592
+ // `background-task-reply` (clarify relay) loads on `message.received.dm`
593
+ // only. Both are DM-agent-exclusive, like browser-task.
594
+ "background-task",
595
+ "background-task-reply",
567
596
  // Scheduling split — `/schedule` is one-shot only; recurring tasks are
568
597
  // Agents. This skill teaches the DM agent to author a detailed recurring
569
598
  // Agent (`POST /api/agents`) when the user asks for an ongoing cadence.
@@ -579,7 +608,7 @@ const PROCESS_TO_EVENT_TYPE = {
579
608
  "routine.evening_review": "routine.evening_review",
580
609
  "routine.weekly_review": "routine.weekly_review",
581
610
  "routine.monthly_review": "routine.monthly_review",
582
- "routine.hourly_check": "routine.hourly_check",
611
+ "routine.activity_scan": "routine.activity_scan",
583
612
  "routine.roadmap_refresh": "routine.roadmap_refresh",
584
613
  "routine.today_refresh": "routine.today_refresh",
585
614
  "routine.user_profile_sweep": "routine.user_profile_sweep",
@@ -887,6 +916,48 @@ export function resolveSkillManifest(eventType, opts) {
887
916
  export function resolveSkillManifestForProcess(processKey, opts) {
888
917
  return resolveSkillManifest(PROCESS_TO_EVENT_TYPE[processKey] ?? processKey, opts);
889
918
  }
919
+ /**
920
+ * Event-type / process-key families whose sessions do NOT receive the
921
+ * owner's user-authored skill library (`<contextDir>/policies/skills/`).
922
+ *
923
+ * User skills are general assistant extensions the owner writes for their
924
+ * conversational agent; `syncAllUserSkills` provisions them into a session
925
+ * workdir ON TOP of the process key's built-in manifest. Narrow-persona
926
+ * sessions run under dedicated profiles with tight, purpose-built manifests
927
+ * — dumping the full user-skill library into them is pure cold-start cost +
928
+ * attention dilution and never load-bearing (their work is fully covered by
929
+ * the built-in bundle, and the wiki/research skills are themselves
930
+ * built-ins, not user-authored).
931
+ *
932
+ * Excluded families:
933
+ * - `wiki.*` — the wiki-agent persona (ingest_url / compile /
934
+ * ask / lint / trace / connect).
935
+ * - `routine.research_*` — the browser-history research routines
936
+ * (cluster_update / offer_dm / dispatch /
937
+ * wiki_summary).
938
+ *
939
+ * Everything else (the conversational DM agent, DM-tone scheduled sessions,
940
+ * the daily/weekly/monthly routines, setup, knowledge import, git/github
941
+ * observers, scheduled tasks) keeps the prior "every user skill, every
942
+ * session" behaviour.
943
+ */
944
+ export const USER_SKILL_EXCLUDED_PREFIXES = [
945
+ "wiki.",
946
+ "routine.research_",
947
+ ];
948
+ /**
949
+ * Whether a session for `eventTypeOrProcessKey` should be provisioned with
950
+ * the owner's user-authored skills. Keyed on the SAME string the skill
951
+ * manifest resolves against (`processKey ?? eventType`) so the gate and the
952
+ * built-in manifest agree on what a session is.
953
+ *
954
+ * Gates the `syncAllUserSkills` call at every materialisation chokepoint
955
+ * (`createSessionWorkdir`, `ensureSessionWorkdir`, and the backend-fallback
956
+ * re-materialiser). See `USER_SKILL_EXCLUDED_PREFIXES`.
957
+ */
958
+ export function eventTypeAcceptsUserSkills(eventTypeOrProcessKey) {
959
+ return !USER_SKILL_EXCLUDED_PREFIXES.some((prefix) => eventTypeOrProcessKey.startsWith(prefix));
960
+ }
890
961
  export function getProfileForEvent(eventType) {
891
962
  for (const rule of PROFILE_RULES) {
892
963
  if (rule.exact ? eventType === rule.prefix : eventType.startsWith(rule.prefix)) {
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Keep-awake posture while the daemon runs (`preventSleepMode` setting):
3
+ *
4
+ * - `"off"` — never inhibit sleep.
5
+ * - `"ac"` — inhibit system sleep only while on AC power
6
+ * (macOS `caffeinate -s`; battery drain stays impossible).
7
+ * - `"always"` — additionally inhibit idle sleep on battery
8
+ * (macOS `caffeinate -i -s`).
9
+ *
10
+ * Why this exists: every timer in the daemon (node-cron, WakeDetector,
11
+ * in-flight agent sessions) freezes while the host sleeps. A sleeping
12
+ * laptop turns the 04:00 day-boundary flow into hours of wake-catchup
13
+ * replays riding macOS maintenance DarkWakes — each replay re-fires
14
+ * day-boundary work in a 1–2 minute window before the machine re-sleeps,
15
+ * and cold prompt caches make those runs 10× the normal cost. Keeping
16
+ * the machine awake while plugged in removes that failure mode at the
17
+ * source; server installs (which never sleep) are unaffected.
18
+ *
19
+ * macOS only. `caffeinate` cannot override lid-close (clamshell) sleep —
20
+ * that needs root (`pmset disablesleep 1`), which the daemon must not
21
+ * touch. Windows/Linux hosts running this daemon are assumed to be
22
+ * servers or desktops with OS-level power management; the inhibitor is a
23
+ * no-op there and logs once at debug level.
24
+ */
25
+ export declare const PREVENT_SLEEP_MODES: readonly ["off", "ac", "always"];
26
+ export type PreventSleepMode = (typeof PREVENT_SLEEP_MODES)[number];
27
+ /** Respawns allowed after an unexpected `caffeinate` exit before giving up. */
28
+ export declare const SLEEP_INHIBITOR_MAX_RESPAWNS = 3;
29
+ /** Pause before a respawn so a persistently-dying binary cannot tight-loop. */
30
+ export declare const SLEEP_INHIBITOR_RESPAWN_DELAY_MS = 5000;
31
+ export interface SleepInhibitCommand {
32
+ command: string;
33
+ args: string[];
34
+ }
35
+ /**
36
+ * Pure command resolver — `null` means "do not inhibit" (mode off or
37
+ * unsupported platform). `-w <pid>` ties the power assertion to the
38
+ * daemon process itself, so the assertion is released even if the daemon
39
+ * is SIGKILLed and `stop()` never runs.
40
+ */
41
+ export declare function resolveSleepInhibitCommand(platform: NodeJS.Platform, mode: PreventSleepMode, pid: number): SleepInhibitCommand | null;
42
+ export interface SleepInhibitorChild {
43
+ on(event: "error", listener: (err: Error) => void): unknown;
44
+ on(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): unknown;
45
+ kill(signal?: NodeJS.Signals): boolean;
46
+ unref(): void;
47
+ pid?: number;
48
+ }
49
+ export type SleepInhibitorSpawn = (command: string, args: readonly string[]) => SleepInhibitorChild;
50
+ export interface SleepInhibitorOptions {
51
+ mode: PreventSleepMode;
52
+ /** Injectable for tests; defaults to the host platform. */
53
+ platform?: NodeJS.Platform;
54
+ /** Injectable for tests; defaults to the daemon's own pid. */
55
+ pid?: number;
56
+ spawnFn?: SleepInhibitorSpawn;
57
+ respawnDelayMs?: number;
58
+ }
59
+ /**
60
+ * Holds a `caffeinate` child for the daemon's lifetime. Crash-safe by
61
+ * construction (`-w pid` above); `stop()` exists for symmetric shutdown
62
+ * and to suppress the respawn path during graceful exit.
63
+ */
64
+ export declare class SleepInhibitor {
65
+ private readonly mode;
66
+ private readonly platform;
67
+ private readonly pid;
68
+ private readonly spawnFn;
69
+ private readonly respawnDelayMs;
70
+ private child;
71
+ private respawnTimer;
72
+ private respawns;
73
+ private started;
74
+ private stopped;
75
+ constructor(options: SleepInhibitorOptions);
76
+ start(): void;
77
+ stop(): void;
78
+ private spawnChild;
79
+ }
@@ -0,0 +1,132 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createLogger } from "../logging.js";
3
+ const logger = createLogger("sleep-inhibitor");
4
+ /**
5
+ * Keep-awake posture while the daemon runs (`preventSleepMode` setting):
6
+ *
7
+ * - `"off"` — never inhibit sleep.
8
+ * - `"ac"` — inhibit system sleep only while on AC power
9
+ * (macOS `caffeinate -s`; battery drain stays impossible).
10
+ * - `"always"` — additionally inhibit idle sleep on battery
11
+ * (macOS `caffeinate -i -s`).
12
+ *
13
+ * Why this exists: every timer in the daemon (node-cron, WakeDetector,
14
+ * in-flight agent sessions) freezes while the host sleeps. A sleeping
15
+ * laptop turns the 04:00 day-boundary flow into hours of wake-catchup
16
+ * replays riding macOS maintenance DarkWakes — each replay re-fires
17
+ * day-boundary work in a 1–2 minute window before the machine re-sleeps,
18
+ * and cold prompt caches make those runs 10× the normal cost. Keeping
19
+ * the machine awake while plugged in removes that failure mode at the
20
+ * source; server installs (which never sleep) are unaffected.
21
+ *
22
+ * macOS only. `caffeinate` cannot override lid-close (clamshell) sleep —
23
+ * that needs root (`pmset disablesleep 1`), which the daemon must not
24
+ * touch. Windows/Linux hosts running this daemon are assumed to be
25
+ * servers or desktops with OS-level power management; the inhibitor is a
26
+ * no-op there and logs once at debug level.
27
+ */
28
+ export const PREVENT_SLEEP_MODES = ["off", "ac", "always"];
29
+ /** Respawns allowed after an unexpected `caffeinate` exit before giving up. */
30
+ export const SLEEP_INHIBITOR_MAX_RESPAWNS = 3;
31
+ /** Pause before a respawn so a persistently-dying binary cannot tight-loop. */
32
+ export const SLEEP_INHIBITOR_RESPAWN_DELAY_MS = 5_000;
33
+ /**
34
+ * Pure command resolver — `null` means "do not inhibit" (mode off or
35
+ * unsupported platform). `-w <pid>` ties the power assertion to the
36
+ * daemon process itself, so the assertion is released even if the daemon
37
+ * is SIGKILLed and `stop()` never runs.
38
+ */
39
+ export function resolveSleepInhibitCommand(platform, mode, pid) {
40
+ if (mode === "off")
41
+ return null;
42
+ if (platform !== "darwin")
43
+ return null;
44
+ const flags = mode === "always" ? ["-i", "-s"] : ["-s"];
45
+ return { command: "caffeinate", args: [...flags, "-w", String(pid)] };
46
+ }
47
+ const defaultSpawn = (command, args) => spawn(command, args, { stdio: "ignore" });
48
+ /**
49
+ * Holds a `caffeinate` child for the daemon's lifetime. Crash-safe by
50
+ * construction (`-w pid` above); `stop()` exists for symmetric shutdown
51
+ * and to suppress the respawn path during graceful exit.
52
+ */
53
+ export class SleepInhibitor {
54
+ mode;
55
+ platform;
56
+ pid;
57
+ spawnFn;
58
+ respawnDelayMs;
59
+ child = null;
60
+ respawnTimer = null;
61
+ respawns = 0;
62
+ started = false;
63
+ stopped = false;
64
+ constructor(options) {
65
+ this.mode = options.mode;
66
+ this.platform = options.platform ?? process.platform;
67
+ this.pid = options.pid ?? process.pid;
68
+ this.spawnFn = options.spawnFn ?? defaultSpawn;
69
+ this.respawnDelayMs =
70
+ options.respawnDelayMs ?? SLEEP_INHIBITOR_RESPAWN_DELAY_MS;
71
+ }
72
+ start() {
73
+ if (this.started)
74
+ return;
75
+ this.started = true;
76
+ const cmd = resolveSleepInhibitCommand(this.platform, this.mode, this.pid);
77
+ if (!cmd) {
78
+ logger.debug({ mode: this.mode, platform: this.platform }, "Sleep inhibitor inactive (mode off or unsupported platform)");
79
+ return;
80
+ }
81
+ this.spawnChild(cmd);
82
+ }
83
+ stop() {
84
+ this.stopped = true;
85
+ if (this.respawnTimer) {
86
+ clearTimeout(this.respawnTimer);
87
+ this.respawnTimer = null;
88
+ }
89
+ // Kill but leave `this.child` set — the exit handler clears it and the
90
+ // `stopped` flag suppresses the respawn path.
91
+ this.child?.kill("SIGTERM");
92
+ }
93
+ spawnChild(cmd) {
94
+ let child;
95
+ try {
96
+ child = this.spawnFn(cmd.command, cmd.args);
97
+ }
98
+ catch (err) {
99
+ logger.warn({ err, command: cmd.command }, "Sleep inhibitor spawn failed — system sleep stays OS-managed");
100
+ return;
101
+ }
102
+ this.child = child;
103
+ // The inhibitor must never keep the event loop alive on its own.
104
+ child.unref();
105
+ child.on("error", (err) => {
106
+ // ENOENT and friends — the binary is missing or unrunnable, which a
107
+ // respawn cannot fix. Warn once and fall back to OS-managed sleep.
108
+ this.child = null;
109
+ logger.warn({ err, command: cmd.command }, "Sleep inhibitor process errored — system sleep stays OS-managed");
110
+ });
111
+ child.on("exit", (code, signal) => {
112
+ if (this.child !== child)
113
+ return; // killed by stop() or replaced
114
+ this.child = null;
115
+ if (this.stopped)
116
+ return;
117
+ if (this.respawns >= SLEEP_INHIBITOR_MAX_RESPAWNS) {
118
+ logger.error({ code, signal, respawns: this.respawns }, "Sleep inhibitor exited repeatedly — giving up; system sleep stays OS-managed");
119
+ return;
120
+ }
121
+ this.respawns += 1;
122
+ logger.warn({ code, signal, attempt: this.respawns }, "Sleep inhibitor exited unexpectedly — respawning");
123
+ this.respawnTimer = setTimeout(() => {
124
+ this.respawnTimer = null;
125
+ if (!this.stopped)
126
+ this.spawnChild(cmd);
127
+ }, this.respawnDelayMs);
128
+ this.respawnTimer.unref?.();
129
+ });
130
+ logger.info({ command: cmd.command, args: cmd.args, mode: this.mode }, "Sleep inhibitor active");
131
+ }
132
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Single source of truth for the daemon's per-process **slim** system-prompt
3
+ * templates. A slim prompt replaces the verbose `preset: "claude_code"` system
4
+ * prompt (Claude SDK) / the wide profile + skill-index instruction file
5
+ * (Codex / Gemini CLI) with a tight, self-contained operational contract for
6
+ * one short, mechanical, lite-tier process key — shedding ~30 K tokens of
7
+ * built-in tool descriptions, the skills index, the memory-system docs, and
8
+ * tone/style guidance the key never uses.
9
+ *
10
+ * Two cost-reduction efforts share this registry:
11
+ * - `routine.fetch_window` — docs/design/appendices/fetch-window-cost-reduction.md
12
+ * Phase 1 / 1.5 (the original slim prompt).
13
+ * - `routine.research_cluster_update` — RESEARCH_CLUSTER_COST_FIX_PLAN.md F4
14
+ * (Phase 2): the nightly per-cluster journal-append session.
15
+ *
16
+ * Adding a slim key is a one-line entry in `SLIM_SYSTEM_PROMPT_LOADERS` below
17
+ * (plus the asset file under agent-assets/system-prompts/ and, for CLI parity,
18
+ * the skill set in skills-compiler.ts:`SLIM_CLI_SKILL_SETS`).
19
+ *
20
+ * The same template is consumed by two backend paths so the body stays
21
+ * byte-identical across backends:
22
+ * - Claude SDK — passed as `query()`'s `systemPrompt` string by
23
+ * `claude-code-core.ts:buildSystemPrompt`.
24
+ * - Codex / Gemini CLI — written verbatim as `AGENTS.md` / `GEMINI.md` by
25
+ * `skills-compiler.ts:materializeSlimCliSession`.
26
+ *
27
+ * Hoisting the loaders out of `claude-code-core.ts` lets the skills compiler
28
+ * import them without a cross-backend dependency, and keeps the disk read +
29
+ * cache amortised across both code paths (one read per template per daemon
30
+ * boot regardless of how many sessions of either backend run).
31
+ *
32
+ * Each template is immutable for the daemon's lifetime, so a single
33
+ * per-template module-level cache is sufficient. The test reset helpers let
34
+ * unit tests simulate a fresh boot without reaching into module internals.
35
+ */
36
+ import { type ProcessKey } from "@aitne/shared";
37
+ /**
38
+ * Load the slim `routine.fetch_window` system-prompt template, caching the
39
+ * result for the daemon's lifetime.
40
+ */
41
+ export declare function loadFetchWindowSystemPrompt(): string;
42
+ /**
43
+ * Load the slim `routine.research_cluster_update` system-prompt template
44
+ * (RESEARCH_CLUSTER_COST_FIX_PLAN.md F4), caching for the daemon's lifetime.
45
+ */
46
+ export declare function loadResearchClusterUpdateSystemPrompt(): string;
47
+ /**
48
+ * Registry of process keys whose session uses a slim system prompt instead of
49
+ * the wide preset / profile. Typed as `Partial<Record<ProcessKey, …>>` (not an
50
+ * inferred string-literal map) so a rename in `@aitne/shared/process-key.ts`
51
+ * lights up here rather than silently dead-branching every consumer.
52
+ *
53
+ * Both call sites (Claude SDK `buildSystemPrompt`, CLI `materializeSlimCliSession`)
54
+ * resolve membership through `loadSlimSystemPrompt` / `isSlimSystemPromptKey`,
55
+ * so adding a key here is the single edit that wires both backends.
56
+ */
57
+ export declare const SLIM_SYSTEM_PROMPT_LOADERS: Partial<Record<ProcessKey, () => string>>;
58
+ /** True when `processKey` has a slim system-prompt template registered. */
59
+ export declare function isSlimSystemPromptKey(processKey: ProcessKey | undefined): boolean;
60
+ /**
61
+ * Load the slim system-prompt body for `processKey`, or `null` when the key
62
+ * has no slim template (callers fall through to the wide preset / profile).
63
+ * The disk read is amortised by the per-template cache.
64
+ */
65
+ export declare function loadSlimSystemPrompt(processKey: ProcessKey | undefined): string | null;
66
+ /**
67
+ * Test-only — drop every cached slim prompt so a subsequent load re-reads from
68
+ * disk. Production never needs this; the templates are immutable per boot.
69
+ */
70
+ export declare function resetSlimSystemPromptsForTest(): void;
71
+ /**
72
+ * Back-compat alias retained so the existing fetch_window tests / call sites
73
+ * keep working after the Phase 2 generalization. Resets every slim cache (a
74
+ * superset of the prior fetch_window-only reset — harmless for tests). Prefer
75
+ * `resetSlimSystemPromptsForTest` in new code.
76
+ */
77
+ export declare function resetFetchWindowSystemPromptForTest(): void;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Single source of truth for the daemon's per-process **slim** system-prompt
3
+ * templates. A slim prompt replaces the verbose `preset: "claude_code"` system
4
+ * prompt (Claude SDK) / the wide profile + skill-index instruction file
5
+ * (Codex / Gemini CLI) with a tight, self-contained operational contract for
6
+ * one short, mechanical, lite-tier process key — shedding ~30 K tokens of
7
+ * built-in tool descriptions, the skills index, the memory-system docs, and
8
+ * tone/style guidance the key never uses.
9
+ *
10
+ * Two cost-reduction efforts share this registry:
11
+ * - `routine.fetch_window` — docs/design/appendices/fetch-window-cost-reduction.md
12
+ * Phase 1 / 1.5 (the original slim prompt).
13
+ * - `routine.research_cluster_update` — RESEARCH_CLUSTER_COST_FIX_PLAN.md F4
14
+ * (Phase 2): the nightly per-cluster journal-append session.
15
+ *
16
+ * Adding a slim key is a one-line entry in `SLIM_SYSTEM_PROMPT_LOADERS` below
17
+ * (plus the asset file under agent-assets/system-prompts/ and, for CLI parity,
18
+ * the skill set in skills-compiler.ts:`SLIM_CLI_SKILL_SETS`).
19
+ *
20
+ * The same template is consumed by two backend paths so the body stays
21
+ * byte-identical across backends:
22
+ * - Claude SDK — passed as `query()`'s `systemPrompt` string by
23
+ * `claude-code-core.ts:buildSystemPrompt`.
24
+ * - Codex / Gemini CLI — written verbatim as `AGENTS.md` / `GEMINI.md` by
25
+ * `skills-compiler.ts:materializeSlimCliSession`.
26
+ *
27
+ * Hoisting the loaders out of `claude-code-core.ts` lets the skills compiler
28
+ * import them without a cross-backend dependency, and keeps the disk read +
29
+ * cache amortised across both code paths (one read per template per daemon
30
+ * boot regardless of how many sessions of either backend run).
31
+ *
32
+ * Each template is immutable for the daemon's lifetime, so a single
33
+ * per-template module-level cache is sufficient. The test reset helpers let
34
+ * unit tests simulate a fresh boot without reaching into module internals.
35
+ */
36
+ import { existsSync, readFileSync } from "node:fs";
37
+ import { dirname, join } from "node:path";
38
+ import { fileURLToPath } from "node:url";
39
+ import { substituteBrandTokens } from "@aitne/shared";
40
+ /**
41
+ * Resolve + read a slim-prompt asset by file basename (without the `.md`
42
+ * extension), applying brand-token substitution.
43
+ *
44
+ * Path resolution follows the same shape as `prompts.ts:resolveTaskFlowsDir`:
45
+ * `__dirname` lives at `packages/daemon/src/core/` (or the matching
46
+ * `dist/core/` after build), so the repo's `agent-assets/` directory is four
47
+ * levels up. The cwd fallback only fires in unusual harness layouts.
48
+ *
49
+ * Brand tokens (`{APP_NAME}`) are substituted here so the slim template stays
50
+ * aligned with the wide path's substitution contract: the Claude SDK
51
+ * systemPrompt and the CLI AGENTS.md / GEMINI.md both see the substituted
52
+ * product name instead of a literal `{APP_NAME}` leaking through. APP_NAME is
53
+ * a compile-time constant, so the per-boot cache stays byte-stable.
54
+ */
55
+ function readSlimSystemPromptAsset(basename) {
56
+ const here = dirname(fileURLToPath(import.meta.url));
57
+ const candidates = [
58
+ join(here, "..", "..", "..", "..", "agent-assets", "system-prompts", `${basename}.md`),
59
+ // Defensive fallback — if the module is invoked from an unexpected layout
60
+ // we still find the asset by walking up from cwd. Last-resort only; the
61
+ // relative path above is the canonical resolution.
62
+ join(process.cwd(), "agent-assets", "system-prompts", `${basename}.md`),
63
+ ];
64
+ for (const path of candidates) {
65
+ if (existsSync(path)) {
66
+ return substituteBrandTokens(readFileSync(path, "utf-8"));
67
+ }
68
+ }
69
+ throw new Error(`slim system prompt "${basename}" not found. Looked in: ${candidates.join(", ")}`);
70
+ }
71
+ // Per-template module-level caches. Each template is byte-stable per boot, so
72
+ // a single cached string is sufficient; the reset helper drops them for tests.
73
+ let cachedFetchWindowSystemPrompt = null;
74
+ let cachedResearchClusterUpdateSystemPrompt = null;
75
+ /**
76
+ * Load the slim `routine.fetch_window` system-prompt template, caching the
77
+ * result for the daemon's lifetime.
78
+ */
79
+ export function loadFetchWindowSystemPrompt() {
80
+ if (cachedFetchWindowSystemPrompt !== null)
81
+ return cachedFetchWindowSystemPrompt;
82
+ cachedFetchWindowSystemPrompt = readSlimSystemPromptAsset("routine-fetch-window");
83
+ return cachedFetchWindowSystemPrompt;
84
+ }
85
+ /**
86
+ * Load the slim `routine.research_cluster_update` system-prompt template
87
+ * (RESEARCH_CLUSTER_COST_FIX_PLAN.md F4), caching for the daemon's lifetime.
88
+ */
89
+ export function loadResearchClusterUpdateSystemPrompt() {
90
+ if (cachedResearchClusterUpdateSystemPrompt !== null) {
91
+ return cachedResearchClusterUpdateSystemPrompt;
92
+ }
93
+ cachedResearchClusterUpdateSystemPrompt = readSlimSystemPromptAsset("routine-research-cluster-update");
94
+ return cachedResearchClusterUpdateSystemPrompt;
95
+ }
96
+ /**
97
+ * Registry of process keys whose session uses a slim system prompt instead of
98
+ * the wide preset / profile. Typed as `Partial<Record<ProcessKey, …>>` (not an
99
+ * inferred string-literal map) so a rename in `@aitne/shared/process-key.ts`
100
+ * lights up here rather than silently dead-branching every consumer.
101
+ *
102
+ * Both call sites (Claude SDK `buildSystemPrompt`, CLI `materializeSlimCliSession`)
103
+ * resolve membership through `loadSlimSystemPrompt` / `isSlimSystemPromptKey`,
104
+ * so adding a key here is the single edit that wires both backends.
105
+ */
106
+ export const SLIM_SYSTEM_PROMPT_LOADERS = {
107
+ "routine.fetch_window": loadFetchWindowSystemPrompt,
108
+ "routine.research_cluster_update": loadResearchClusterUpdateSystemPrompt,
109
+ };
110
+ /** True when `processKey` has a slim system-prompt template registered. */
111
+ export function isSlimSystemPromptKey(processKey) {
112
+ return processKey !== undefined && processKey in SLIM_SYSTEM_PROMPT_LOADERS;
113
+ }
114
+ /**
115
+ * Load the slim system-prompt body for `processKey`, or `null` when the key
116
+ * has no slim template (callers fall through to the wide preset / profile).
117
+ * The disk read is amortised by the per-template cache.
118
+ */
119
+ export function loadSlimSystemPrompt(processKey) {
120
+ if (processKey === undefined)
121
+ return null;
122
+ const loader = SLIM_SYSTEM_PROMPT_LOADERS[processKey];
123
+ return loader ? loader() : null;
124
+ }
125
+ /**
126
+ * Test-only — drop every cached slim prompt so a subsequent load re-reads from
127
+ * disk. Production never needs this; the templates are immutable per boot.
128
+ */
129
+ export function resetSlimSystemPromptsForTest() {
130
+ cachedFetchWindowSystemPrompt = null;
131
+ cachedResearchClusterUpdateSystemPrompt = null;
132
+ }
133
+ /**
134
+ * Back-compat alias retained so the existing fetch_window tests / call sites
135
+ * keep working after the Phase 2 generalization. Resets every slim cache (a
136
+ * superset of the prior fetch_window-only reset — harmless for tests). Prefer
137
+ * `resetSlimSystemPromptsForTest` in new code.
138
+ */
139
+ export function resetFetchWindowSystemPromptForTest() {
140
+ resetSlimSystemPromptsForTest();
141
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Pre-spawn gates for autonomous routine sessions —
3
+ * PREPASS_COST_REDUCTION_PLAN.md N2.
4
+ *
5
+ * Two cheap checks run before the daemon spawns an autonomous backend
6
+ * session (routine dispatch + pre-pass fan-out sub-sessions), so a
7
+ * session that would deterministically fail is skipped instead of
8
+ * billed/spawned:
9
+ *
10
+ * 1. **Offline gate** — DNS-resolve the *backend API host* (the daemon
11
+ * never talks to integration APIs in delegated/native mode, so the
12
+ * backend host is the only one that matters). Uses `dns.lookup`
13
+ * (getaddrinfo — the same resolver path that produces the observed
14
+ * ENOTFOUND failures), NOT `dns.resolve` (c-ares bypasses the OS
15
+ * resolver / hosts file and can disagree with what the session would
16
+ * see). Results are cached ~60s per host so a fan-out of N
17
+ * integrations costs one lookup.
18
+ * 2. **Auth gate** — consult the cached auth-health row
19
+ * (`readCachedAuthStatus`) and treat the backend as non-viable only
20
+ * when `shouldSkip` is true (expired/missing with a ≤10-min-fresh
21
+ * cache, or a recovery subprocess owning the row). The hourly
22
+ * `checkAll()` probe and the reactive `recordReactiveAuth*` writers
23
+ * refresh the cache independently of routine sessions, so a
24
+ * recovered backend un-skips within minutes.
25
+ *
26
+ * **A spawn is skipped only when EVERY candidate backend (main +
27
+ * fallback) is non-viable.** The BackendRouter already skips an
28
+ * auth-unhealthy main straight to its fallback, so gating on the main
29
+ * alone would suppress sessions the router could have completed —
30
+ * exactly the accuracy degradation the now-scope forbids.
31
+ *
32
+ * Skips never touch pre-pass freshness state, so the next tick retries.
33
+ * Every decision is fail-open: an unknown backend host, a gate-internal
34
+ * error, or a DB failure lets the spawn proceed — the gate exists to
35
+ * save doomed sessions, never to block live ones.
36
+ */
37
+ import type Database from "better-sqlite3";
38
+ import type { BackendId } from "@aitne/shared";
39
+ /**
40
+ * Backend → API host the SDK/CLI must reach for a session to be viable.
41
+ * Hosts chosen per the default auth path of each backend's runtime:
42
+ * Claude Code → Anthropic API; Codex CLI (ChatGPT-plan auth) →
43
+ * chatgpt.com; Gemini CLI (OAuth code-assist) → cloudcode-pa. The gate
44
+ * only needs a DNS answer for outage detection, so an API-key install
45
+ * resolving a sibling host of the same provider is equally conclusive.
46
+ * Backends without an entry (e.g. opencode, which routes to arbitrary
47
+ * providers) are fail-open: the offline gate passes them.
48
+ */
49
+ export declare const BACKEND_API_HOSTS: Partial<Record<BackendId, string>>;
50
+ export type SpawnGateSkipReason = "offline" | "auth_unhealthy";
51
+ /** Per-candidate diagnostics carried into the skip audit row. */
52
+ export interface SpawnGateBackendVerdict {
53
+ backendId: BackendId;
54
+ /** Host probed by the offline gate; null = no mapping (gate passed). */
55
+ host: string | null;
56
+ /** True when the host failed to resolve (within the cache TTL). */
57
+ offline: boolean;
58
+ /** Cached auth status string (`ok` / `expired` / `missing` / …). */
59
+ authStatus: string;
60
+ /** True when the cached auth row says a spawn is doomed. */
61
+ authShouldSkip: boolean;
62
+ /** Net verdict: this backend could run the session. */
63
+ viable: boolean;
64
+ }
65
+ export interface SpawnGateDecision {
66
+ skip: boolean;
67
+ /**
68
+ * Present iff `skip`. `offline` when every candidate was blocked by
69
+ * DNS; `auth_unhealthy` when at least one candidate resolved but had
70
+ * a confirmed-bad auth cache.
71
+ */
72
+ reason?: SpawnGateSkipReason;
73
+ backends: SpawnGateBackendVerdict[];
74
+ }
75
+ export interface AutonomousSpawnGateOptions {
76
+ /** Injected for tests; defaults to `node:dns/promises.lookup`. */
77
+ lookup?: (host: string) => Promise<unknown>;
78
+ /** Injected clock for tests; defaults to `Date.now`. */
79
+ now?: () => number;
80
+ /** DNS verdict cache TTL; defaults to 60s. */
81
+ dnsCacheTtlMs?: number;
82
+ /** Per-lookup deadline (fail-open past it); defaults to 2.5s. */
83
+ dnsLookupTimeoutMs?: number;
84
+ /**
85
+ * Freshness window forwarded to `readCachedAuthStatus`. Omit to use
86
+ * that module's 10-minute default; callers with a configured
87
+ * `authPreflightFreshnessMs` should thread it through so the gate and
88
+ * the router agree on what "recently confirmed bad" means.
89
+ */
90
+ authFreshnessMs?: number;
91
+ /** Host mapping override for tests. */
92
+ backendApiHosts?: Partial<Record<BackendId, string>>;
93
+ }
94
+ export declare class AutonomousSpawnGate {
95
+ private readonly db;
96
+ private readonly lookup;
97
+ private readonly now;
98
+ private readonly dnsCacheTtlMs;
99
+ private readonly dnsLookupTimeoutMs;
100
+ private readonly authFreshnessMs;
101
+ private readonly hosts;
102
+ private readonly dnsCache;
103
+ constructor(db: Database.Database, options?: AutonomousSpawnGateOptions);
104
+ /**
105
+ * Evaluate the gates for the candidate backends that could run the
106
+ * session (binding main first, then fallback). Returns `skip: false`
107
+ * for an empty candidate list (nothing to assert) and on any internal
108
+ * error (fail-open).
109
+ */
110
+ evaluate(candidates: readonly BackendId[]): Promise<SpawnGateDecision>;
111
+ private evaluateBackend;
112
+ private hostResolves;
113
+ /**
114
+ * One bounded lookup attempt. Three fail-OPEN (`true`) outcomes that
115
+ * deliberately do not count as "offline":
116
+ * - the resolver answered (any address);
117
+ * - `EAI_AGAIN` — the resolver said "try again", which is a transient
118
+ * resolver condition, not an outage verdict;
119
+ * - the deadline elapsed — no answer is not a negative answer.
120
+ * Only a definitive resolution failure (ENOTFOUND et al.) returns
121
+ * `false`. The verdict — including a fail-open one — is cached by the
122
+ * caller for the TTL so a hung resolver costs at most one deadline
123
+ * per host per minute.
124
+ */
125
+ private lookupWithDeadline;
126
+ }