@aitne/daemon 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. package/dist/adapters/adapter-watchdog.d.ts +70 -0
  2. package/dist/adapters/adapter-watchdog.js +115 -0
  3. package/dist/adapters/discord.d.ts +17 -1
  4. package/dist/adapters/discord.js +33 -0
  5. package/dist/adapters/notification-manager.d.ts +27 -1
  6. package/dist/adapters/notification-manager.js +54 -39
  7. package/dist/adapters/slack-adapter.d.ts +26 -1
  8. package/dist/adapters/slack-adapter.js +41 -0
  9. package/dist/adapters/telegram-adapter.d.ts +18 -1
  10. package/dist/adapters/telegram-adapter.js +41 -2
  11. package/dist/adapters/types.d.ts +20 -0
  12. package/dist/adapters/whatsapp-adapter.d.ts +26 -7
  13. package/dist/adapters/whatsapp-adapter.js +74 -21
  14. package/dist/api/env-writer.d.ts +1 -0
  15. package/dist/api/env-writer.js +17 -7
  16. package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
  17. package/dist/api/helpers/agent-errors-registry.js +5 -5
  18. package/dist/api/routes/agent-schedule.js +5 -1
  19. package/dist/api/routes/agent.js +33 -12
  20. package/dist/api/routes/agents/index.js +75 -16
  21. package/dist/api/routes/agents/views.d.ts +37 -2
  22. package/dist/api/routes/agents/views.js +64 -2
  23. package/dist/api/routes/apple-calendar.js +4 -1
  24. package/dist/api/routes/background-task.d.ts +22 -0
  25. package/dist/api/routes/background-task.js +338 -0
  26. package/dist/api/routes/browser-history.js +9 -1
  27. package/dist/api/routes/calendar.js +12 -2
  28. package/dist/api/routes/context/path-resolve.js +6 -1
  29. package/dist/api/routes/context/permissions.js +12 -2
  30. package/dist/api/routes/context/snapshots.js +0 -3
  31. package/dist/api/routes/context/write.js +3 -17
  32. package/dist/api/routes/dashboard/config.js +58 -12
  33. package/dist/api/routes/dashboard/cost-approvals.js +66 -0
  34. package/dist/api/routes/dashboard/notifications.js +9 -9
  35. package/dist/api/routes/dashboard/oauth-google.js +5 -3
  36. package/dist/api/routes/feedback.d.ts +3 -0
  37. package/dist/api/routes/feedback.js +349 -0
  38. package/dist/api/routes/git.js +10 -3
  39. package/dist/api/routes/github.js +5 -1
  40. package/dist/api/routes/integrations/crud-patch.js +5 -1
  41. package/dist/api/routes/integrations-reconcile.js +2 -2
  42. package/dist/api/routes/mcp.js +65 -13
  43. package/dist/api/routes/notion.d.ts +1 -1
  44. package/dist/api/routes/observations.js +7 -7
  45. package/dist/api/routes/obsidian.d.ts +1 -1
  46. package/dist/api/routes/receipts.js +5 -1
  47. package/dist/api/routes/setup-migrate.js +1 -1
  48. package/dist/api/routes/setup.js +1 -1
  49. package/dist/api/routes/task-flows.d.ts +1 -1
  50. package/dist/api/routes/task-flows.js +1 -1
  51. package/dist/api/routes/tuning.d.ts +29 -0
  52. package/dist/api/routes/tuning.js +304 -0
  53. package/dist/api/server.d.ts +44 -16
  54. package/dist/api/server.js +12 -0
  55. package/dist/bootstrap/adapters.d.ts +19 -0
  56. package/dist/bootstrap/adapters.js +61 -0
  57. package/dist/bootstrap/api.d.ts +5 -3
  58. package/dist/bootstrap/api.js +45 -13
  59. package/dist/bootstrap/catchup.d.ts +1 -1
  60. package/dist/bootstrap/catchup.js +11 -11
  61. package/dist/bootstrap/event-pipeline.d.ts +11 -0
  62. package/dist/bootstrap/event-pipeline.js +246 -8
  63. package/dist/bootstrap/observers.js +9 -6
  64. package/dist/bootstrap/schedule-helpers.d.ts +104 -6
  65. package/dist/bootstrap/schedule-helpers.js +172 -19
  66. package/dist/config.js +32 -12
  67. package/dist/core/agent-core.d.ts +33 -1
  68. package/dist/core/agent-core.js +36 -1
  69. package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
  70. package/dist/core/agents/activity-scan-cadence.js +127 -0
  71. package/dist/core/agents/agent-route-override.d.ts +53 -0
  72. package/dist/core/agents/agent-route-override.js +69 -0
  73. package/dist/core/agents/builtin-registry.d.ts +51 -14
  74. package/dist/core/agents/builtin-registry.js +92 -15
  75. package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
  76. package/dist/core/agents/config-gate-reconcile.js +51 -0
  77. package/dist/core/agents/cron-substitute.d.ts +1 -1
  78. package/dist/core/agents/cron-substitute.js +1 -1
  79. package/dist/core/agents/custom-routine-migration.d.ts +60 -0
  80. package/dist/core/agents/custom-routine-migration.js +149 -0
  81. package/dist/core/agents/firing-blocked.d.ts +1 -1
  82. package/dist/core/agents/hourly-cadence.d.ts +102 -0
  83. package/dist/core/agents/hourly-cadence.js +126 -0
  84. package/dist/core/agents/loader-boot.js +23 -0
  85. package/dist/core/agents/loader.d.ts +19 -0
  86. package/dist/core/agents/loader.js +34 -2
  87. package/dist/core/agents/override-merge.d.ts +1 -1
  88. package/dist/core/agents/override-merge.js +9 -1
  89. package/dist/core/agents/recurrence-convert.d.ts +1 -1
  90. package/dist/core/agents/recurrence-convert.js +1 -1
  91. package/dist/core/agents/recurring-schedule-adapter.js +8 -0
  92. package/dist/core/alerts.js +6 -6
  93. package/dist/core/backends/auth-health-monitor.d.ts +2 -2
  94. package/dist/core/backends/auth-health-monitor.js +1 -1
  95. package/dist/core/backends/backend-router.d.ts +27 -1
  96. package/dist/core/backends/backend-router.js +165 -1
  97. package/dist/core/backends/claude-code-core.d.ts +71 -31
  98. package/dist/core/backends/claude-code-core.js +282 -54
  99. package/dist/core/backends/cli-quota-guards.d.ts +29 -1
  100. package/dist/core/backends/cli-quota-guards.js +40 -5
  101. package/dist/core/backends/codex-core.d.ts +6 -0
  102. package/dist/core/backends/codex-core.js +22 -6
  103. package/dist/core/backends/failure-spend.d.ts +58 -0
  104. package/dist/core/backends/failure-spend.js +137 -0
  105. package/dist/core/backends/gemini-cli-core.d.ts +6 -0
  106. package/dist/core/backends/gemini-cli-core.js +38 -6
  107. package/dist/core/backends/model-registry.d.ts +1 -1
  108. package/dist/core/backends/model-registry.js +4 -4
  109. package/dist/core/backends/opencode-core.d.ts +1 -1
  110. package/dist/core/backends/opencode-core.js +5 -5
  111. package/dist/core/backends/plan-presets.js +47 -18
  112. package/dist/core/bang-commands/commands-cost.js +3 -1
  113. package/dist/core/bang-commands/commands-report.js +4 -3
  114. package/dist/core/bang-commands/commands-research.js +4 -1
  115. package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
  116. package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
  117. package/dist/core/bang-commands/commands-stop-start.js +3 -3
  118. package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
  119. package/dist/core/bang-commands/commands-task-control.js +147 -0
  120. package/dist/core/bang-commands/commands-wiki.js +5 -5
  121. package/dist/core/bang-commands/index.d.ts +2 -0
  122. package/dist/core/bang-commands/index.js +12 -0
  123. package/dist/core/bang-commands/registry.d.ts +12 -0
  124. package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
  125. package/dist/core/browser-history/research-cluster-fanout.js +39 -16
  126. package/dist/core/channel-timeline.d.ts +5 -1
  127. package/dist/core/channel-timeline.js +13 -0
  128. package/dist/core/context/index-reconciler.js +5 -2
  129. package/dist/core/context/policy-index-reconciler.d.ts +6 -4
  130. package/dist/core/context/policy-index-runner.js +25 -6
  131. package/dist/core/context-builder-calendar.js +10 -2
  132. package/dist/core/context-builder-conversation.d.ts +8 -1
  133. package/dist/core/context-builder-conversation.js +41 -7
  134. package/dist/core/context-builder-yesterday.js +4 -3
  135. package/dist/core/context-builder.d.ts +7 -2
  136. package/dist/core/context-builder.js +193 -5
  137. package/dist/core/context-file-serializer.d.ts +1 -1
  138. package/dist/core/context-file-serializer.js +1 -1
  139. package/dist/core/context-health.js +2 -2
  140. package/dist/core/context-paths.d.ts +11 -1
  141. package/dist/core/context-paths.js +17 -1
  142. package/dist/core/context-validation/prepare-write.js +1 -1
  143. package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
  144. package/dist/core/context-vault-aliases.d.ts +0 -13
  145. package/dist/core/context-vault-aliases.js +37 -0
  146. package/dist/core/custom-routines.d.ts +99 -0
  147. package/dist/core/custom-routines.js +187 -0
  148. package/dist/core/daemon-api-cli.js +50 -1
  149. package/dist/core/day-boundary.d.ts +46 -0
  150. package/dist/core/day-boundary.js +40 -0
  151. package/dist/core/dispatcher-activity-scan.d.ts +221 -0
  152. package/dist/core/dispatcher-activity-scan.js +775 -0
  153. package/dist/core/dispatcher-error-handling.d.ts +6 -11
  154. package/dist/core/dispatcher-error-handling.js +38 -62
  155. package/dist/core/dispatcher-hourly-check.js +6 -1
  156. package/dist/core/dispatcher-message-handler.d.ts +10 -0
  157. package/dist/core/dispatcher-message-handler.js +24 -0
  158. package/dist/core/dispatcher-morning-routine.d.ts +6 -6
  159. package/dist/core/dispatcher-morning-routine.js +13 -13
  160. package/dist/core/dispatcher-result-processor.d.ts +33 -0
  161. package/dist/core/dispatcher-result-processor.js +167 -11
  162. package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
  163. package/dist/core/dispatcher-scheduled-background-task.js +89 -0
  164. package/dist/core/dispatcher-scheduled-tasks.d.ts +104 -1
  165. package/dist/core/dispatcher-scheduled-tasks.js +480 -8
  166. package/dist/core/dispatcher-task-delivery.d.ts +105 -0
  167. package/dist/core/dispatcher-task-delivery.js +555 -0
  168. package/dist/core/dispatcher-types.d.ts +48 -9
  169. package/dist/core/dispatcher-types.js +3 -3
  170. package/dist/core/dispatcher.d.ts +112 -31
  171. package/dist/core/dispatcher.js +297 -60
  172. package/dist/core/dm-freshness-metrics.d.ts +1 -1
  173. package/dist/core/drift-effects.js +2 -2
  174. package/dist/core/feedback/consolidation-prep.d.ts +94 -0
  175. package/dist/core/feedback/consolidation-prep.js +254 -0
  176. package/dist/core/feedback/eviction-scorer.d.ts +81 -0
  177. package/dist/core/feedback/eviction-scorer.js +136 -0
  178. package/dist/core/feedback/lesson-format.d.ts +79 -0
  179. package/dist/core/feedback/lesson-format.js +199 -0
  180. package/dist/core/feedback/lesson-injection.d.ts +98 -0
  181. package/dist/core/feedback/lesson-injection.js +174 -0
  182. package/dist/core/feedback/lesson-merge.d.ts +51 -0
  183. package/dist/core/feedback/lesson-merge.js +88 -0
  184. package/dist/core/feedback/lesson-store-overview.d.ts +46 -0
  185. package/dist/core/feedback/lesson-store-overview.js +42 -0
  186. package/dist/core/feedback/promotion-gate.d.ts +69 -0
  187. package/dist/core/feedback/promotion-gate.js +117 -0
  188. package/dist/core/feedback/regeneralization-prep.d.ts +87 -0
  189. package/dist/core/feedback/regeneralization-prep.js +152 -0
  190. package/dist/core/feedback/scope-parser.d.ts +86 -0
  191. package/dist/core/feedback/scope-parser.js +141 -0
  192. package/dist/core/feedback/self-performance-prep.d.ts +186 -0
  193. package/dist/core/feedback/self-performance-prep.js +541 -0
  194. package/dist/core/feedback/tuning-actuator.d.ts +198 -0
  195. package/dist/core/feedback/tuning-actuator.js +432 -0
  196. package/dist/core/feedback/tuning-recommender.d.ts +247 -0
  197. package/dist/core/feedback/tuning-recommender.js +580 -0
  198. package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
  199. package/dist/core/feedback/tuning-revert-monitor.js +213 -0
  200. package/dist/core/health-monitor.d.ts +6 -0
  201. package/dist/core/health-monitor.js +1 -1
  202. package/dist/core/injection-policy.d.ts +83 -1
  203. package/dist/core/injection-policy.js +61 -3
  204. package/dist/core/integration-main-backend.js +4 -0
  205. package/dist/core/management-md.d.ts +2 -2
  206. package/dist/core/management-md.js +51 -13
  207. package/dist/core/morning/orchestrator.d.ts +2 -2
  208. package/dist/core/morning/orchestrator.js +2 -2
  209. package/dist/core/notification-gate.d.ts +64 -0
  210. package/dist/core/notification-gate.js +51 -0
  211. package/dist/core/notification-rate-limit.d.ts +40 -0
  212. package/dist/core/notification-rate-limit.js +50 -0
  213. package/dist/core/policy-files.d.ts +1 -1
  214. package/dist/core/policy-files.js +2 -2
  215. package/dist/core/pre-pass-freshness.d.ts +4 -4
  216. package/dist/core/retention.d.ts +5 -0
  217. package/dist/core/retention.js +20 -4
  218. package/dist/core/review-context.d.ts +1 -1
  219. package/dist/core/review-context.js +10 -5
  220. package/dist/core/roadmap-write-lock.d.ts +2 -1
  221. package/dist/core/roadmap-write-lock.js +15 -10
  222. package/dist/core/routine-acquisition-plan.d.ts +47 -1
  223. package/dist/core/routine-acquisition-plan.js +78 -20
  224. package/dist/core/routine-fetch-window-retry.js +7 -4
  225. package/dist/core/routine-fetch-window-runner.d.ts +39 -3
  226. package/dist/core/routine-fetch-window-runner.js +264 -13
  227. package/dist/core/routine-windows.d.ts +2 -2
  228. package/dist/core/routine-windows.js +8 -5
  229. package/dist/core/scheduler.d.ts +175 -16
  230. package/dist/core/scheduler.js +559 -102
  231. package/dist/core/signal-detector.d.ts +51 -1
  232. package/dist/core/signal-detector.js +321 -24
  233. package/dist/core/skills-compiler-denied-tools.js +2 -2
  234. package/dist/core/skills-compiler-skill-index.d.ts +2 -2
  235. package/dist/core/skills-compiler-skill-index.js +2 -2
  236. package/dist/core/skills-compiler-variants.d.ts +1 -1
  237. package/dist/core/skills-compiler-variants.js +8 -0
  238. package/dist/core/skills-compiler.d.ts +29 -26
  239. package/dist/core/skills-compiler.js +117 -81
  240. package/dist/core/skills-manifest.d.ts +37 -0
  241. package/dist/core/skills-manifest.js +73 -2
  242. package/dist/core/sleep-inhibitor.d.ts +79 -0
  243. package/dist/core/sleep-inhibitor.js +132 -0
  244. package/dist/core/slim-system-prompt-loader.d.ts +77 -0
  245. package/dist/core/slim-system-prompt-loader.js +141 -0
  246. package/dist/core/spawn-gates.d.ts +126 -0
  247. package/dist/core/spawn-gates.js +180 -0
  248. package/dist/core/today-direct-writer.d.ts +60 -14
  249. package/dist/core/today-direct-writer.js +90 -13
  250. package/dist/core/today-write-lock.d.ts +4 -2
  251. package/dist/core/today-write-lock.js +30 -20
  252. package/dist/core/wake-detector.d.ts +55 -0
  253. package/dist/core/wake-detector.js +80 -0
  254. package/dist/core/wiki/compile-lock.d.ts +1 -1
  255. package/dist/core/wiki/compile-lock.js +1 -1
  256. package/dist/core/wiki/wiki-fts.js +13 -6
  257. package/dist/core/workdir.js +15 -6
  258. package/dist/db/activity-scan-signals.d.ts +77 -0
  259. package/dist/db/activity-scan-signals.js +378 -0
  260. package/dist/db/agents-store.d.ts +28 -0
  261. package/dist/db/agents-store.js +62 -0
  262. package/dist/db/background-task-clarifications-store.d.ts +81 -0
  263. package/dist/db/background-task-clarifications-store.js +152 -0
  264. package/dist/db/background-task-store.d.ts +207 -0
  265. package/dist/db/background-task-store.js +380 -0
  266. package/dist/db/browser-history-store.d.ts +39 -6
  267. package/dist/db/browser-history-store.js +51 -7
  268. package/dist/db/browser-task-clarifications-store.d.ts +12 -0
  269. package/dist/db/browser-task-clarifications-store.js +35 -5
  270. package/dist/db/browser-task-store.d.ts +3 -0
  271. package/dist/db/browser-task-store.js +29 -4
  272. package/dist/db/deferred-dm.d.ts +86 -0
  273. package/dist/db/deferred-dm.js +199 -0
  274. package/dist/db/feedback-signals-store.d.ts +77 -0
  275. package/dist/db/feedback-signals-store.js +144 -0
  276. package/dist/db/migrations.js +380 -0
  277. package/dist/db/observations.d.ts +2 -2
  278. package/dist/db/observations.js +3 -3
  279. package/dist/db/schema.js +260 -22
  280. package/dist/db/voice-transcripts-store.d.ts +1 -1
  281. package/dist/index.js +86 -29
  282. package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
  283. package/dist/messaging/browser-task-mcp-notifier.js +30 -151
  284. package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
  285. package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
  286. package/dist/observers/delegated-sync-worker.d.ts +6 -6
  287. package/dist/observers/delegated-sync-worker.js +10 -10
  288. package/dist/observers/git-delegated-cron.d.ts +1 -1
  289. package/dist/observers/git-delegated-cron.js +2 -2
  290. package/dist/observers/github-poller-classifier.d.ts +3 -3
  291. package/dist/observers/github-poller-classifier.js +3 -3
  292. package/dist/observers/imminent-event-scheduler.d.ts +1 -1
  293. package/dist/observers/imminent-event-scheduler.js +1 -1
  294. package/dist/observers/mail-poller.d.ts +1 -0
  295. package/dist/observers/mail-poller.js +42 -3
  296. package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
  297. package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
  298. package/dist/observers/observation-summarizer/worker.d.ts +2 -2
  299. package/dist/observers/observation-summarizer/worker.js +4 -4
  300. package/dist/observers/obsidian-watcher.d.ts +1 -1
  301. package/dist/observers/obsidian-watcher.js +1 -1
  302. package/dist/safety/agent-write-tracker.d.ts +4 -4
  303. package/dist/safety/agent-write-tracker.js +4 -4
  304. package/dist/safety/always-disallowed.d.ts +1 -1
  305. package/dist/safety/always-disallowed.js +39 -0
  306. package/dist/safety/audit.d.ts +43 -5
  307. package/dist/safety/audit.js +86 -18
  308. package/dist/safety/risk-classifier.d.ts +6 -0
  309. package/dist/safety/risk-classifier.js +97 -18
  310. package/dist/scheduler/activity-scan-gate.d.ts +86 -0
  311. package/dist/scheduler/activity-scan-gate.js +132 -0
  312. package/dist/services/background-task/background-task-budget.d.ts +80 -0
  313. package/dist/services/background-task/background-task-budget.js +91 -0
  314. package/dist/services/background-task/background-task-driver.d.ts +105 -0
  315. package/dist/services/background-task/background-task-driver.js +416 -0
  316. package/dist/services/background-task/background-task-runner.d.ts +96 -0
  317. package/dist/services/background-task/background-task-runner.js +673 -0
  318. package/dist/services/background-task/background-task-tools.d.ts +84 -0
  319. package/dist/services/background-task/background-task-tools.js +247 -0
  320. package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
  321. package/dist/services/background-task/background-task-transition-events.js +54 -0
  322. package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
  323. package/dist/services/browser-history/automation/egress-denylist.js +34 -8
  324. package/dist/services/browser-history/lifecycle/platform.js +44 -2
  325. package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
  326. package/dist/services/browser-task/browser-task-runner.js +53 -8
  327. package/dist/services/mcp/probe.js +30 -8
  328. package/dist/services/observations-batch.d.ts +1 -1
  329. package/dist/services/observations-batch.js +2 -2
  330. package/dist/settings/runtime-settings.d.ts +45 -12
  331. package/dist/settings/runtime-settings.js +215 -40
  332. package/dist/settings/settings-store.js +11 -3
  333. package/package.json +4 -4
@@ -1,9 +1,15 @@
1
1
  /**
2
- * Daemon-direct writer that appends a single bullet to today.md
3
- * `## Agent Log` without going through the agent at all. Used by the
4
- * three-stage hourly_check gate (cost-reduction-structural §B) on the
5
- * stage0_silent / stage2_log_only paths so a "no-op" cron tick still
6
- * leaves an audit trail in today.md without paying for an LLM session.
2
+ * Daemon-direct, lock-aware writes to today.md that run *before* (or
3
+ * instead of) an agent session bypassing `/api/context/*` because there
4
+ * is no subprocess to issue the curl call from. Two operations live here:
5
+ *
6
+ * 1. {@link appendAgentLogLine} append a single `## Agent Log` bullet.
7
+ * Used by the three-stage activity_scan gate (cost-reduction-structural
8
+ * §B) on the stage0_silent / stage2_log_only paths so a "no-op" cron
9
+ * tick still leaves an audit trail without paying for an LLM session.
10
+ * 2. {@link ensureTodaySkeleton} — seed the canonical empty skeleton when
11
+ * today.md is **absent**, so a section-only refresh routine has a valid
12
+ * PATCH target instead of 404-ing and budget-burning on full-file PUTs.
7
13
  *
8
14
  * Why this lives in the daemon (not /api/context/* via curl):
9
15
  * - These paths run *before* the agent is spawned. There is no
@@ -12,20 +18,22 @@
12
18
  * `context-staleness.ts`) — the same tier the PATCH route already
13
19
  * classifies for `## Agent Log` appends. Bypassing the route is
14
20
  * fine because we never touch the prompt-context-changed hook here.
15
- * - The today-write-lock invariant is preserved: we acquire it before
16
- * mutating the file, so morning_routine and direct writes never
17
- * interleave.
21
+ * - The today-write-lock invariant is preserved: both functions acquire
22
+ * it before mutating the file, so morning_routine and direct writes
23
+ * never interleave.
18
24
  *
19
- * Failure mode: when today.md is missing, malformed, or lacks the
20
- * `## Agent Log` heading, the writer logs a warning and returns false
21
- * rather than synthesizing the structure — that is the morning routine's
22
- * job. The gate caller treats false as "log not appended; consume
23
- * observations regardless".
25
+ * Synthesis boundary: `appendAgentLogLine` NEVER synthesizes structure
26
+ * a missing / malformed / heading-less file returns false and the gate
27
+ * caller proceeds. `ensureTodaySkeleton` synthesizes ONLY the empty
28
+ * skeleton, ONLY when the file is entirely absent, and never touches a
29
+ * present file. Neither populates today.md — full creation and repair stay
30
+ * the morning routine's job.
24
31
  */
25
32
  import { existsSync, readFileSync } from "node:fs";
26
33
  import { writeFileAtomically } from "./atomic-write.js";
27
34
  import { serializeContextFileWrite } from "./context-file-serializer.js";
28
35
  import { fullPath, CONTEXT_RELATIVE_PATHS } from "./context-paths.js";
36
+ import { FALLBACK_PLACEHOLDERS } from "./skeleton.js";
29
37
  import { createLogger } from "../logging.js";
30
38
  const logger = createLogger("today-direct-writer");
31
39
  const AGENT_LOG_HEADER = "## Agent Log";
@@ -87,6 +95,75 @@ export async function appendAgentLogLine(input) {
87
95
  input.todayWriteLock.release(lock.lockId);
88
96
  }
89
97
  }
98
+ /**
99
+ * Canonical empty `today.md` skeleton, reused byte-for-byte from the
100
+ * boot-time seeder (`skeleton.ts`) so a refresh-path seed and a
101
+ * fresh-install seed produce identical structure. `skeleton.test.ts`
102
+ * asserts this placeholder matches `agent-assets/templates/state/today.md`
103
+ * byte-for-byte, so the two definitions never drift. The non-null
104
+ * assertion is safe: the key is a literal entry of `FALLBACK_PLACEHOLDERS`.
105
+ */
106
+ const TODAY_SKELETON = FALLBACK_PLACEHOLDERS[CONTEXT_RELATIVE_PATHS.today];
107
+ /**
108
+ * Guarantee a `today.md` working surface exists before a section-only
109
+ * refresh routine (`routine.today_refresh`) assumes it.
110
+ *
111
+ * `rotateDayFiles()` intentionally renames `today.md` → `yesterday.md` at
112
+ * the day boundary and relies on the morning routine to recreate the
113
+ * dated file. When the morning routine has not run yet — or failed (e.g.
114
+ * a quota/budget death with no fallback backend) — `today.md` is absent
115
+ * and the refresh task flow's `PATCH section=user_schedule` 404s. The
116
+ * agent then improvises full-file `PUT`s, which the strict
117
+ * `validateTodayContent` schema rejects line-by-line; on a single-backend
118
+ * binding with a tight per-turn budget that loop tips into
119
+ * `BackendQuotaError(max_budget_usd)` and the refresh dies without ever
120
+ * writing the file — the "Refresh Today does nothing" symptom.
121
+ *
122
+ * This deterministic pre-step removes that whole failure mode: when the
123
+ * file is **entirely absent** we seed the canonical empty skeleton so the
124
+ * agent's section PATCH always has a valid target. A file that already
125
+ * exists is left byte-untouched — a valid dated file OR the legacy
126
+ * `# Today` bridge stub both accept the section PATCH (the route's
127
+ * `allowLegacyToday` branch). We never repair a malformed-but-present
128
+ * file and never overwrite user content; full creation/repair stays the
129
+ * morning routine's job. The seeded skeleton is dateless (`# Today`), so
130
+ * it does NOT satisfy `hasCurrentAgentDayTodayMd()` and the pending
131
+ * morning-routine retry still fires and upgrades it.
132
+ *
133
+ * Lock-aware exactly like {@link appendAgentLogLine}: if the morning
134
+ * routine holds the today-write-lock (mid-creation) we skip and let it
135
+ * win — the refresh session then 409-defers on its own PATCH.
136
+ */
137
+ export async function ensureTodaySkeleton(input) {
138
+ const lock = input.todayWriteLock.acquire();
139
+ if (!lock.ok) {
140
+ logger.info({ holder: lock.holder }, "Skipping today.md skeleton seed — today-write-lock held");
141
+ return { seeded: false, reason: "lock_unavailable" };
142
+ }
143
+ try {
144
+ const path = fullPath(input.contextDir, CONTEXT_RELATIVE_PATHS.today);
145
+ return await serializeContextFileWrite(path, () => {
146
+ if (existsSync(path)) {
147
+ return {
148
+ seeded: false,
149
+ reason: "already_present",
150
+ };
151
+ }
152
+ try {
153
+ writeFileAtomically(path, TODAY_SKELETON);
154
+ logger.info({ path }, "Seeded today.md skeleton for refresh — file was absent (morning routine not yet run for the agent-day)");
155
+ return { seeded: true };
156
+ }
157
+ catch (err) {
158
+ logger.error({ err, path }, "Failed to seed today.md skeleton");
159
+ return { seeded: false, reason: "io_error" };
160
+ }
161
+ });
162
+ }
163
+ finally {
164
+ input.todayWriteLock.release(lock.lockId);
165
+ }
166
+ }
90
167
  /**
91
168
  * Splice a new bullet line into the `## Agent Log` section, immediately
92
169
  * before the next `## ` heading or end-of-file. Returns null when the
@@ -13,8 +13,9 @@ export interface TodayWriteLockManager {
13
13
  export declare class InMemoryTodayWriteLockManager implements TodayWriteLockManager {
14
14
  private readonly timeoutMs;
15
15
  private holder;
16
- private timer;
16
+ private expiresAtMs;
17
17
  constructor(timeoutMs: number);
18
+ private expireIfStale;
18
19
  acquire(): {
19
20
  ok: true;
20
21
  lockId: string;
@@ -38,8 +39,9 @@ export declare function getTodayWriteLockTimeoutMs(executeTimeoutMinutes: number
38
39
  export declare class MigrationLock {
39
40
  private readonly timeoutMs;
40
41
  private holder;
41
- private timer;
42
+ private expiresAtMs;
42
43
  constructor(timeoutMs: number);
44
+ private expireIfStale;
43
45
  acquire(): {
44
46
  ok: true;
45
47
  lockId: string;
@@ -4,44 +4,50 @@ const logger = createLogger("today-write-lock");
4
4
  export class InMemoryTodayWriteLockManager {
5
5
  timeoutMs;
6
6
  holder = null;
7
- timer = null;
7
+ expiresAtMs = 0;
8
8
  constructor(timeoutMs) {
9
9
  this.timeoutMs = timeoutMs;
10
10
  }
11
+ // Expiry is wall-clock (Date.now) checked lazily on access, not a
12
+ // setTimeout: Node timers run on the monotonic clock and don't advance
13
+ // while the machine sleeps, so a timer armed before sleep would hold the
14
+ // lock up to the whole sleep duration past its intended TTL.
15
+ expireIfStale() {
16
+ if (this.holder && Date.now() >= this.expiresAtMs) {
17
+ logger.warn({ lockId: this.holder }, "Today write lock expired by timeout");
18
+ this.holder = null;
19
+ }
20
+ }
11
21
  acquire() {
22
+ this.expireIfStale();
12
23
  if (this.holder) {
13
24
  logger.debug({ existingHolder: this.holder }, "Lock acquire rejected — already held");
14
25
  return { ok: false, holder: this.holder };
15
26
  }
16
27
  const lockId = randomUUID();
17
28
  this.holder = lockId;
18
- this.timer = setTimeout(() => {
19
- logger.warn({ lockId: this.holder }, "Today write lock expired by timeout");
20
- this.holder = null;
21
- this.timer = null;
22
- }, this.timeoutMs);
29
+ this.expiresAtMs = Date.now() + this.timeoutMs;
23
30
  logger.debug({ lockId }, "Today write lock acquired");
24
31
  return { ok: true, lockId };
25
32
  }
26
33
  release(lockId) {
34
+ this.expireIfStale();
27
35
  if (!this.holder || this.holder !== lockId) {
28
36
  return false;
29
37
  }
30
38
  this.holder = null;
31
- if (this.timer) {
32
- clearTimeout(this.timer);
33
- this.timer = null;
34
- }
35
39
  logger.debug({ lockId }, "Today write lock released");
36
40
  return true;
37
41
  }
38
42
  isHeldBy(lockId) {
43
+ this.expireIfStale();
39
44
  if (!this.holder) {
40
45
  return false;
41
46
  }
42
47
  return this.holder === lockId;
43
48
  }
44
49
  getHolder() {
50
+ this.expireIfStale();
45
51
  return this.holder;
46
52
  }
47
53
  }
@@ -62,41 +68,45 @@ export function getTodayWriteLockTimeoutMs(executeTimeoutMinutes) {
62
68
  export class MigrationLock {
63
69
  timeoutMs;
64
70
  holder = null;
65
- timer = null;
71
+ expiresAtMs = 0;
66
72
  constructor(timeoutMs) {
67
73
  this.timeoutMs = timeoutMs;
68
74
  }
75
+ // Wall-clock lazy expiry — see InMemoryTodayWriteLockManager for why
76
+ // this is not a setTimeout.
77
+ expireIfStale() {
78
+ if (this.holder && Date.now() >= this.expiresAtMs) {
79
+ logger.warn({ lockId: this.holder }, "Migration lock expired by timeout");
80
+ this.holder = null;
81
+ }
82
+ }
69
83
  acquire() {
84
+ this.expireIfStale();
70
85
  if (this.holder) {
71
86
  logger.debug({ existingHolder: this.holder }, "Migration lock rejected — already held");
72
87
  return { ok: false, holder: this.holder };
73
88
  }
74
89
  const lockId = randomUUID();
75
90
  this.holder = lockId;
76
- this.timer = setTimeout(() => {
77
- logger.warn({ lockId: this.holder }, "Migration lock expired by timeout");
78
- this.holder = null;
79
- this.timer = null;
80
- }, this.timeoutMs);
91
+ this.expiresAtMs = Date.now() + this.timeoutMs;
81
92
  logger.debug({ lockId }, "Migration lock acquired");
82
93
  return { ok: true, lockId };
83
94
  }
84
95
  release(lockId) {
96
+ this.expireIfStale();
85
97
  if (!this.holder || this.holder !== lockId) {
86
98
  return false;
87
99
  }
88
100
  this.holder = null;
89
- if (this.timer) {
90
- clearTimeout(this.timer);
91
- this.timer = null;
92
- }
93
101
  logger.debug({ lockId }, "Migration lock released");
94
102
  return true;
95
103
  }
96
104
  isHeld() {
105
+ this.expireIfStale();
97
106
  return this.holder !== null;
98
107
  }
99
108
  getHolder() {
109
+ this.expireIfStale();
100
110
  return this.holder;
101
111
  }
102
112
  }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Default tick cadence. One minute keeps detection latency low while the
3
+ * per-tick work (one Date.now() subtraction) is negligible.
4
+ */
5
+ export declare const WAKE_DETECTOR_INTERVAL_MS = 60000;
6
+ /**
7
+ * Minimum unexplained gap between ticks that counts as a sleep / suspend /
8
+ * forward clock jump. Five minutes is far above any plausible event-loop
9
+ * stall on a healthy daemon, and far below the shortest sleep that can
10
+ * swallow a cron tick worth catching up (the activity scan's default
11
+ * 60-minute cadence).
12
+ */
13
+ export declare const WAKE_GAP_THRESHOLD_MS: number;
14
+ export interface WakeDetectorOptions {
15
+ /**
16
+ * Invoked once per detected wake with the gap length. Errors (sync or
17
+ * async) are caught and logged — the detector keeps ticking.
18
+ */
19
+ onWake: (gapMs: number) => void | Promise<void>;
20
+ intervalMs?: number;
21
+ gapThresholdMs?: number;
22
+ /** Injectable wall clock for tests. */
23
+ now?: () => number;
24
+ }
25
+ /**
26
+ * Detects machine sleep / suspend / forward clock jumps from inside the
27
+ * process.
28
+ *
29
+ * node-cron (and every other timer in the daemon) runs on Node's timer
30
+ * wheel, which does not fire while the host is suspended — a cron tick
31
+ * scheduled inside a sleep window is silently lost, not replayed on wake.
32
+ * The boot-time catchup in `bootstrap/catchup.ts` covers daemon *restarts*,
33
+ * but a long sleep with the process still alive had no equivalent until
34
+ * this detector.
35
+ *
36
+ * Mechanism: a short `setInterval` notes the wall-clock time of each tick.
37
+ * Timers freeze during sleep, so the first tick after wake observes a
38
+ * wall-clock gap of roughly the sleep duration; anything above
39
+ * `gapThresholdMs` beyond the expected interval fires `onWake`. Backward
40
+ * clock jumps are ignored — there is nothing to catch up when time moves
41
+ * backward, and the next tick re-baselines automatically.
42
+ */
43
+ export declare class WakeDetector {
44
+ private readonly onWake;
45
+ private readonly intervalMs;
46
+ private readonly gapThresholdMs;
47
+ private readonly now;
48
+ private timer;
49
+ private lastTickMs;
50
+ constructor(options: WakeDetectorOptions);
51
+ start(): void;
52
+ stop(): void;
53
+ /** Test seam — exercises one tick without waiting on real timers. */
54
+ tick(): void;
55
+ }
@@ -0,0 +1,80 @@
1
+ import { createLogger } from "../logging.js";
2
+ const logger = createLogger("wake-detector");
3
+ /**
4
+ * Default tick cadence. One minute keeps detection latency low while the
5
+ * per-tick work (one Date.now() subtraction) is negligible.
6
+ */
7
+ export const WAKE_DETECTOR_INTERVAL_MS = 60_000;
8
+ /**
9
+ * Minimum unexplained gap between ticks that counts as a sleep / suspend /
10
+ * forward clock jump. Five minutes is far above any plausible event-loop
11
+ * stall on a healthy daemon, and far below the shortest sleep that can
12
+ * swallow a cron tick worth catching up (the activity scan's default
13
+ * 60-minute cadence).
14
+ */
15
+ export const WAKE_GAP_THRESHOLD_MS = 5 * 60_000;
16
+ /**
17
+ * Detects machine sleep / suspend / forward clock jumps from inside the
18
+ * process.
19
+ *
20
+ * node-cron (and every other timer in the daemon) runs on Node's timer
21
+ * wheel, which does not fire while the host is suspended — a cron tick
22
+ * scheduled inside a sleep window is silently lost, not replayed on wake.
23
+ * The boot-time catchup in `bootstrap/catchup.ts` covers daemon *restarts*,
24
+ * but a long sleep with the process still alive had no equivalent until
25
+ * this detector.
26
+ *
27
+ * Mechanism: a short `setInterval` notes the wall-clock time of each tick.
28
+ * Timers freeze during sleep, so the first tick after wake observes a
29
+ * wall-clock gap of roughly the sleep duration; anything above
30
+ * `gapThresholdMs` beyond the expected interval fires `onWake`. Backward
31
+ * clock jumps are ignored — there is nothing to catch up when time moves
32
+ * backward, and the next tick re-baselines automatically.
33
+ */
34
+ export class WakeDetector {
35
+ onWake;
36
+ intervalMs;
37
+ gapThresholdMs;
38
+ now;
39
+ timer = null;
40
+ lastTickMs = 0;
41
+ constructor(options) {
42
+ this.onWake = options.onWake;
43
+ this.intervalMs = options.intervalMs ?? WAKE_DETECTOR_INTERVAL_MS;
44
+ this.gapThresholdMs = options.gapThresholdMs ?? WAKE_GAP_THRESHOLD_MS;
45
+ this.now = options.now ?? Date.now;
46
+ }
47
+ start() {
48
+ if (this.timer)
49
+ return;
50
+ this.lastTickMs = this.now();
51
+ this.timer = setInterval(() => this.tick(), this.intervalMs);
52
+ this.timer.unref?.();
53
+ }
54
+ stop() {
55
+ if (!this.timer)
56
+ return;
57
+ clearInterval(this.timer);
58
+ this.timer = null;
59
+ }
60
+ /** Test seam — exercises one tick without waiting on real timers. */
61
+ tick() {
62
+ const current = this.now();
63
+ const gapMs = current - this.lastTickMs - this.intervalMs;
64
+ this.lastTickMs = current;
65
+ if (gapMs < this.gapThresholdMs)
66
+ return;
67
+ logger.warn({ gapMinutes: Math.round(gapMs / 60_000) }, "Wall-clock gap detected (machine sleep or clock jump) — running wake catch-up");
68
+ try {
69
+ const result = this.onWake(gapMs);
70
+ if (result && typeof result.then === "function") {
71
+ result.then(undefined, (err) => {
72
+ logger.error({ err }, "Wake catch-up handler failed");
73
+ });
74
+ }
75
+ }
76
+ catch (err) {
77
+ logger.error({ err }, "Wake catch-up handler threw");
78
+ }
79
+ }
80
+ }
@@ -12,7 +12,7 @@
12
12
  * time. The lock is held until the dispatcher releases it via
13
13
  * `releaseWikiCompileLock` in `executeDefault`'s `finally` block.
14
14
  * - The lock is purely in-process; that matches the dispatcher's
15
- * existing concurrency invariants (`hourlyCheckInProgress`,
15
+ * existing concurrency invariants (`activityScanInProgress`,
16
16
  * `morningRoutineActive` are also in-memory flags). A second
17
17
  * daemon process would not see the lock — but the daemon is
18
18
  * single-process by design.
@@ -12,7 +12,7 @@
12
12
  * time. The lock is held until the dispatcher releases it via
13
13
  * `releaseWikiCompileLock` in `executeDefault`'s `finally` block.
14
14
  * - The lock is purely in-process; that matches the dispatcher's
15
- * existing concurrency invariants (`hourlyCheckInProgress`,
15
+ * existing concurrency invariants (`activityScanInProgress`,
16
16
  * `morningRoutineActive` are also in-memory flags). A second
17
17
  * daemon process would not see the lock — but the daemon is
18
18
  * single-process by design.
@@ -110,15 +110,22 @@ function extractTitleAndBody(content) {
110
110
  return { title, body };
111
111
  }
112
112
  function stripFrontmatter(content) {
113
- if (!content.startsWith("---\n")) {
114
- return { frontmatterKeys: {}, body: content };
113
+ // Obsidian vaults authored/synced on Windows (or checked out under
114
+ // git core.autocrlf=true) are CRLF. The frontmatter fence gate below is
115
+ // LF-only, so without this normalize a `---\r\n…---\r\n` block leaks into
116
+ // the indexed body and the title fallback never populates. Indexed
117
+ // body/title are search tokens only and never round-trip to disk, so
118
+ // collapsing interior CRLF to LF is harmless (LF input is unchanged).
119
+ const normalized = content.replace(/\r\n/g, "\n");
120
+ if (!normalized.startsWith("---\n")) {
121
+ return { frontmatterKeys: {}, body: normalized };
115
122
  }
116
- const end = content.indexOf("\n---\n", 4);
123
+ const end = normalized.indexOf("\n---\n", 4);
117
124
  if (end < 0) {
118
- return { frontmatterKeys: {}, body: content };
125
+ return { frontmatterKeys: {}, body: normalized };
119
126
  }
120
- const frontmatter = content.slice(4, end);
121
- const body = content.slice(end + 5);
127
+ const frontmatter = normalized.slice(4, end);
128
+ const body = normalized.slice(end + 5);
122
129
  const keys = {};
123
130
  for (const line of frontmatter.split("\n")) {
124
131
  const match = line.match(/^title:\s*(.*?)\s*$/);
@@ -25,7 +25,7 @@ import { createLogger } from "../logging.js";
25
25
  import { SkillsCompiler } from "./skills-compiler.js";
26
26
  import { EMPTY_MAIL_ACCOUNTS_MD, renderMailAccountsMd, } from "./skills-compiler-tree.js";
27
27
  import { refreshSkillIndexBlock } from "./skills-compiler-skill-index.js";
28
- import { getProfileForEvent, resolveSkillManifest, resolveSkillManifestForProcess, } from "./skills-manifest.js";
28
+ import { eventTypeAcceptsUserSkills, getProfileForEvent, resolveSkillManifest, resolveSkillManifestForProcess, } from "./skills-manifest.js";
29
29
  import { ensureDaemonApiCli } from "./daemon-api-cli.js";
30
30
  import { computeInstructionAssetStatus, readInstructionStampManifest, sessionInstructionAssetsStale, writeInstructionAssetStamp, } from "./release-assets.js";
31
31
  const logger = createLogger("workdir");
@@ -252,11 +252,15 @@ export function createSessionWorkdir(projectRoot, eventType, userSkillsDir, opti
252
252
  // turns, manifest now resolves to a different skill set).
253
253
  { processKey: options?.processKey ?? eventType, skillSlugs: deployed.skills });
254
254
  ensureDaemonApiCli(sessionDir);
255
- // User skills: every skill the user has authored, regardless of event type.
256
- // Uses the manifest-backed sync so the initial population is consistent
257
- // with the per-message sync the dispatcher runs on Opus events.
255
+ // User skills: every skill the user has authored, EXCEPT for
256
+ // narrow-persona keys (wiki.* / routine.research_*) which run a tight
257
+ // built-in manifest and would only be diluted by the owner's general
258
+ // skill library — see `eventTypeAcceptsUserSkills`. Uses the
259
+ // manifest-backed sync so the initial population is consistent with the
260
+ // per-message sync the dispatcher runs on Opus events.
258
261
  let userSync = null;
259
- if (userSkillsDir) {
262
+ if (userSkillsDir &&
263
+ eventTypeAcceptsUserSkills(options?.processKey ?? eventType)) {
260
264
  userSync = syncAllUserSkills(sessionDir, userSkillsDir);
261
265
  // docs/design/appendices/skills-unification.md Phase 1 §R4 — splice user-authored
262
266
  // slugs into the `<skill-index>` block AFTER they land on disk.
@@ -419,7 +423,12 @@ export function ensureSessionWorkdir(projectRoot, dataDir, dbSessionId, eventTyp
419
423
  // CONTEXT_VAULT_REDESIGN — falling back there would resurrect the
420
424
  // Obsidian-mode divergence bug v4 V11 fixed.
421
425
  const userSkillsDir = join(options?.contextDir ?? join(dataDir, "context"), "policies", "skills");
422
- const userSync = syncAllUserSkills(sessionDir, userSkillsDir);
426
+ // Narrow-persona keys (wiki.* / routine.research_*) skip the owner's
427
+ // user-skill library; their tight built-in manifest is the whole
428
+ // surface. See `eventTypeAcceptsUserSkills`.
429
+ const userSync = eventTypeAcceptsUserSkills(options?.processKey ?? eventType)
430
+ ? syncAllUserSkills(sessionDir, userSkillsDir)
431
+ : null;
423
432
  // docs/design/appendices/skills-unification.md Phase 1 §R4 — fold user-authored slugs
424
433
  // into the `<skill-index>` block now that they're on disk. Idempotent
425
434
  // and inexpensive (single instruction-file rewrite).
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Stage-0 signal compute for the three-stage activity_scan gate
3
+ * (cost-reduction-structural §B). Pure DB-read shape: every value is
4
+ * derived from existing tables (`observations`, `mail_messages_index`,
5
+ * `agent_schedule`, `agent_actions`) plus the optional today.md content
6
+ * passed in by the caller. No LLM call, no filesystem I/O of its own —
7
+ * the dispatcher injects the today.md snapshot (when available) so this
8
+ * module stays unit-testable from a synthetic Database handle.
9
+ *
10
+ * The shape mirrors the design doc 1:1 so the gate (Stage 1) can be
11
+ * a pure function over `ActivityScanSignals` + a config block.
12
+ */
13
+ import type Database from "better-sqlite3";
14
+ export interface ActivityScanSignals {
15
+ /** Post dedup-against-today user-actor pending observation count. */
16
+ pendingObsCount: number;
17
+ /**
18
+ * Maximum `novelty_score` across pending user observations whose
19
+ * `summary_status='done'`. `null` when no observation has a done
20
+ * summary yet — the gate treats null as a cautious mid-novelty default
21
+ * (see decideStage).
22
+ */
23
+ maxNoveltyScore: number | null;
24
+ /** Histogram of novelty levels across pending+done observations. */
25
+ noveltyDistribution: {
26
+ low: number;
27
+ mid: number;
28
+ high: number;
29
+ };
30
+ /** Count of unread mail rows whose sender is in the VIP list. */
31
+ vipMailUnreadCount: number;
32
+ /** True when at least one pending calendar observation exists. */
33
+ calendarHas24hChange: boolean;
34
+ /**
35
+ * True when at least two pending calendar observations carry
36
+ * overlapping `start`/`end` ISO timestamps in their JSON payload
37
+ * within the next 24 hours. Tolerant of missing fields — when the
38
+ * payload shape isn't recognizable, returns false (the LLM still
39
+ * sees the underlying observation in Stage 3 if escalated).
40
+ */
41
+ calendarHasConflict: boolean;
42
+ /**
43
+ * Number of `## Agent Plan` rows in today.md whose HH:MM is before
44
+ * `now` in the agent timezone. Best-effort regex parse — when
45
+ * today.md is missing or unparseable, returns 0.
46
+ */
47
+ agentPlanOverdueCount: number;
48
+ /** Count of `agent_schedule` rows due in the next 6 hours. */
49
+ scheduleApproachingCount: number;
50
+ /**
51
+ * Hours since the last activity_scan that escalated to Stage 3 (or
52
+ * before the gate was deployed, since the last activity_scan run at
53
+ * all). `Infinity` when no row has been recorded yet.
54
+ */
55
+ hoursSinceLastStage3Run: number;
56
+ }
57
+ export interface ComputeActivityScanSignalsOptions {
58
+ /** Owner-VIP mail addresses, lowercased exact-match. */
59
+ vipMailSenders?: readonly string[];
60
+ /** Look-ahead window for `scheduleApproachingCount`. Default 6h. */
61
+ scheduleHorizonHours?: number;
62
+ /** Look-ahead window for calendar conflict detection. Default 24h. */
63
+ calendarHorizonHours?: number;
64
+ /** today.md content snapshot (or null when missing/unreadable). */
65
+ todayMd?: string | null;
66
+ /** Wall-clock anchor — injectable for tests. Defaults to `new Date()`. */
67
+ now?: Date;
68
+ /**
69
+ * Agent timezone (IANA name, e.g. `Asia/Tokyo`) used to compare
70
+ * `## Agent Plan` HH:MM rows against `now`. When omitted, falls back
71
+ * to the JS engine's local timezone — fine in the common single-user
72
+ * deployment but wrong if the daemon runs in UTC and the operator
73
+ * pinned a different `config.timezone`.
74
+ */
75
+ agentTimezone?: string;
76
+ }
77
+ export declare function computeActivityScanSignals(db: Database.Database, options?: ComputeActivityScanSignalsOptions): ActivityScanSignals;