@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
@@ -89,6 +89,8 @@ import { EventPriority } from "@aitne/shared";
89
89
  import { createLogger } from "../logging.js";
90
90
  import { markContextChanged } from "../core/dashboard-session-controls.js";
91
91
  import { EventDispatcher } from "../core/dispatcher.js";
92
+ import { SessionGateRegistry } from "../core/session-gate.js";
93
+ import { enqueueUndeliveredBrowserTaskDeliveries } from "../core/dispatcher-task-delivery.js";
92
94
  import { SignalDetector } from "../core/signal-detector.js";
93
95
  import { ContextBuilder } from "../core/context-builder.js";
94
96
  import { getTaskFlow, initTaskFlows } from "../core/prompts.js";
@@ -111,6 +113,7 @@ import { AuthRecovery } from "../core/backends/auth-recovery.js";
111
113
  // Workdir / skills
112
114
  import { ensureBackendMaterialized, syncAllUserSkills, buildConfiguredServices, refreshDmSessionWorkdirs, validateDelegatedStartup, } from "../core/workdir.js";
113
115
  import { validateBuiltinSkillSourceTree } from "../core/skills-compiler-variants.js";
116
+ import { eventTypeAcceptsUserSkills } from "../core/skills-manifest.js";
114
117
  // Audit / safety / messaging
115
118
  import { AuditLogger } from "../safety/audit.js";
116
119
  import { AgentExecutionRecorder } from "../core/agent-execution-recorder.js";
@@ -182,7 +185,29 @@ export async function createEventPipeline(deps) {
182
185
  // row whose flush timer didn't fire (process killed) would otherwise
183
186
  // linger forever and confuse the dashboard's notification feed.
184
187
  NotificationManager.closeStaleBatchedRows(db);
185
- const notificationManager = new NotificationManager(messageHub, db, config);
188
+ // BACKGROUND_TASK_RUNNER_DESIGN.md §2.3 / §13 Decision 4 (Phase 4, opt-in)
189
+ // — router that hands an ACTIVE-owner proactive forward to the
190
+ // task-delivery gate so the real DM agent weaves it into the live thread.
191
+ // Only invoked by `deliverProactive` when the owner is active AND
192
+ // `autonomousForwardNaturalDelivery` is on (default off). Resolves the
193
+ // owner's default DM channel as the weave target; no channel ⇒ returns
194
+ // false and the verbatim path runs unchanged.
195
+ const { createAutonomousForwardDeliveryEvent: makeAutonomousForwardEvent } = await import("../core/dispatcher-task-delivery.js");
196
+ const { selectDefaultOwnerChannel: resolveOwnerDmChannel } = await import("../messaging/owner-channels.js");
197
+ const { channelRef: ownerChannelRef } = await import("../db/browser-automation-purchase-primary-channels-store.js");
198
+ const notificationManager = new NotificationManager(messageHub, db, config, {
199
+ routeAutonomousForwardNaturally: async ({ content, event }) => {
200
+ const owner = resolveOwnerDmChannel(db);
201
+ if (!owner)
202
+ return false;
203
+ await eventBus.put(makeAutonomousForwardEvent({
204
+ content,
205
+ originatingChannel: ownerChannelRef(owner.platform, owner.channelId),
206
+ correlationId: event.correlationId ?? null,
207
+ }));
208
+ return true;
209
+ },
210
+ });
186
211
  const authTelemetry = new AuthTelemetry(db);
187
212
  const makeAuthNotifier = (source) => ({
188
213
  send: async (message, options) => {
@@ -222,7 +247,13 @@ export async function createEventPipeline(deps) {
222
247
  });
223
248
  const mailAccounts = services.mail?.listActiveAccounts() ?? [];
224
249
  ensureBackendMaterialized(config.workspaceDir, sessionDir, backendId, eventType, processKey, cfgServices, mailAccounts, readIntegrations(db), config.character, wikiWorkspaceName, getContextDir(config, db), db, messageText ?? null);
225
- syncAllUserSkills(sessionDir, resolveUserSkillsRoot(config));
250
+ // Narrow-persona keys (wiki.* / routine.research_*) run a tight
251
+ // built-in manifest and never call a user skill — skip the owner's
252
+ // library on the fallback re-materialise too, matching the primary
253
+ // workdir path. See `eventTypeAcceptsUserSkills`.
254
+ if (eventTypeAcceptsUserSkills(processKey ?? eventType)) {
255
+ syncAllUserSkills(sessionDir, resolveUserSkillsRoot(config));
256
+ }
226
257
  });
227
258
  // docs/design/appendices/skills-unification.md Phase 1 §R5 / item 6 — refuse to boot on a
228
259
  // malformed source tree. Throws on slug-pattern violations and
@@ -319,7 +350,8 @@ export async function createEventPipeline(deps) {
319
350
  initTaskFlows(config.workspaceDir, config.dataDir);
320
351
  // ── Signal detector + dispatcher ──────────────────────────────────────
321
352
  const signalDetector = new SignalDetector(config, { db });
322
- const dispatcher = new EventDispatcher(eventBus, agentRouter, contextBuilder, getTaskFlow, notificationManager, sessionManager, messageRecorder, auditLogger, db, config, morningRoutineLock, services, roadmapWriteLock, writeTracker);
353
+ const sessionGates = new SessionGateRegistry();
354
+ const dispatcher = new EventDispatcher(eventBus, agentRouter, contextBuilder, getTaskFlow, notificationManager, sessionManager, messageRecorder, auditLogger, db, config, morningRoutineLock, services, roadmapWriteLock, writeTracker, sessionGates);
323
355
  notificationManager.setSignalDetector(signalDetector);
324
356
  // Wire the scoped read-token manager into every backend so daemon-API
325
357
  // calls from `<sessionDir>` workdirs carry a per-session token, not the
@@ -453,7 +485,7 @@ export async function createEventPipeline(deps) {
453
485
  // re-registration when an integration mode flips. When no delegated
454
486
  // integration is present, the worker is null and the call is a no-op.
455
487
  dispatcher.setDelegatedSyncRefresh(async () => {
456
- await delegatedSyncWorker?.runDisabledCadencesForHourlyCheck();
488
+ await delegatedSyncWorker?.runDisabledCadencesForActivityScan();
457
489
  });
458
490
  // ── Delegated probe observer (DELEGATED-MODE-V2 §7.1) ────────────────
459
491
  // Hourly re-probe of delegated integrations' connector tools so the
@@ -502,6 +534,40 @@ export async function createEventPipeline(deps) {
502
534
  return null;
503
535
  }
504
536
  };
537
+ // BACKGROUND_TASK_RUNNER_DESIGN.md Phase 1 (delivery assets) — the
538
+ // `task.delivery` idle + active branches attach a task's deliverable
539
+ // files inline: browser-task trace screenshots (resolved by key) and
540
+ // worker-produced output files like PDF / PPTX / PNG (resolved by path).
541
+ // The dispatcher owns delivery but has no messageHub / trace-store
542
+ // access, so we inject this resolver → outbound attachment refs (native
543
+ // file for messaging adapters, ingested store ref for the dashboard).
544
+ // Best-effort — unresolvable assets (retention drop, missing file,
545
+ // unknown type) are dropped; the text draft still sends.
546
+ {
547
+ const { resolveScreenshotAttachment, buildPathAttachment } = await import("../messaging/browser-task-screenshot-attachment.js");
548
+ dispatcher.setTaskDeliveryAssetResolver(async (platform, assets) => {
549
+ const resolved = await Promise.all(assets.map((asset) => {
550
+ if (asset.screenshotKey) {
551
+ return resolveScreenshotAttachment({
552
+ platform,
553
+ key: asset.screenshotKey,
554
+ paDataDir: config.dataDir,
555
+ ingestOutboundImage: ingestBrowserTaskScreenshot,
556
+ });
557
+ }
558
+ if (asset.path) {
559
+ return buildPathAttachment({
560
+ platform,
561
+ absPath: asset.path,
562
+ filename: asset.filename,
563
+ ingestOutboundImage: ingestBrowserTaskScreenshot,
564
+ });
565
+ }
566
+ return Promise.resolve(null);
567
+ }));
568
+ return resolved.filter((a) => a !== null);
569
+ });
570
+ }
505
571
  // ── Phase B-4 purchase handler ────────────────────────────────────────
506
572
  // MANAGED_CHROMIUM_IMPLEMENTATION_PLAN.md §17.3 / §13 step 50.
507
573
  //
@@ -646,9 +712,8 @@ export async function createEventPipeline(deps) {
646
712
  const { createBrowserTaskMcpNotifier } = await import("../messaging/browser-task-mcp-notifier.js");
647
713
  const browserTaskSlotStateRef = createSlotStateRef(config.browserTaskMaxConcurrent);
648
714
  const browserTaskMcpNotifier = createBrowserTaskMcpNotifier({
649
- messageHub,
650
- paDataDir: config.dataDir,
651
- ingestOutboundImage: ingestBrowserTaskScreenshot,
715
+ eventBus,
716
+ db,
652
717
  });
653
718
  const browserTaskHostProfile = createBrowserTaskHostProfile();
654
719
  // Hoisted out of the `createBrowserTaskRunner({ notifier: ... })` arg
@@ -739,6 +804,41 @@ export async function createEventPipeline(deps) {
739
804
  // a user-scheduled task can silently fail at fire time hours/days
740
805
  // after the user issued it.
741
806
  dispatcher.setBrowserTaskTerminalNotifier(browserTaskTerminalNotifier);
807
+ // ── Background-task runner (BACKGROUND_TASK_RUNNER_DESIGN.md §4) ───────
808
+ // Generic detached-task worker. Shares the proven slot manager (via a
809
+ // synthetic per-task key so tasks contend only on the global cap) and
810
+ // the gated `task.delivery` boundary. The worker is a plain SDK session
811
+ // (no Playwright). The delivery enqueuer wraps the `task.delivery` event
812
+ // factories + the event bus so the runner stays free of any core import.
813
+ const { createBackgroundTaskRunner, createBackgroundTaskSlotStateRef, } = await import("../services/background-task/background-task-runner.js");
814
+ const { createBackgroundTaskTransitionEmitter: createBackgroundTaskTransitionEmitterAtBoot, } = await import("../services/background-task/background-task-transition-events.js");
815
+ const { createBackgroundTaskResultDeliveryEvent, createBackgroundTaskClarificationDeliveryEvent, enqueueUndeliveredBackgroundTaskDeliveries, } = await import("../core/dispatcher-task-delivery.js");
816
+ const backgroundTaskSlotStateRef = createBackgroundTaskSlotStateRef(config.backgroundTaskMaxConcurrent);
817
+ const backgroundTaskTransitionEmitter = createBackgroundTaskTransitionEmitterAtBoot(eventBroadcaster);
818
+ const backgroundTaskDeliveryEnqueuer = {
819
+ async enqueueResult(input) {
820
+ await eventBus.put(createBackgroundTaskResultDeliveryEvent(input));
821
+ },
822
+ async enqueueClarification(input) {
823
+ await eventBus.put(createBackgroundTaskClarificationDeliveryEvent(input));
824
+ },
825
+ };
826
+ const backgroundTaskRunner = createBackgroundTaskRunner({
827
+ db,
828
+ slotStateRef: backgroundTaskSlotStateRef,
829
+ transitionEmitter: backgroundTaskTransitionEmitter,
830
+ deliveryEnqueuer: backgroundTaskDeliveryEnqueuer,
831
+ resumeAcrossRestart: config.backgroundTaskResumeAcrossRestart,
832
+ driver: {
833
+ db,
834
+ paDataDir: config.dataDir,
835
+ workspaceDir: config.workspaceDir,
836
+ contextDir: getContextDir(config),
837
+ clarificationTtlMs: config.backgroundTaskClarificationTtlMinutes * 60_000,
838
+ transitionEmitter: backgroundTaskTransitionEmitter,
839
+ },
840
+ });
841
+ dispatcher.setBackgroundTaskRunner(backgroundTaskRunner);
742
842
  // ── Browser-task deadline scanner tick ────────────────────────────────
743
843
  // BROWSER_TASK_REDESIGN_PLAN.md §5 ask_user "Deadline enforcement" +
744
844
  // §5.1 pending-queue timeout safety valve. Single 30 s tick handles
@@ -786,6 +886,7 @@ export async function createEventPipeline(deps) {
786
886
  expiredPending: sweep.expired,
787
887
  nowMs: now,
788
888
  });
889
+ await sweepDeliveryRecovery(now);
789
890
  if (actions.length === 0)
790
891
  return;
791
892
  for (const action of actions) {
@@ -859,6 +960,16 @@ export async function createEventPipeline(deps) {
859
960
  logger.warn({ err }, "browser-task deadline tick failed (continuing)");
860
961
  }
861
962
  }
963
+ async function sweepDeliveryRecovery(nowMs) {
964
+ const count = await enqueueUndeliveredBrowserTaskDeliveries({
965
+ db,
966
+ eventBus,
967
+ nowMs,
968
+ });
969
+ if (count > 0) {
970
+ logger.debug({ count }, "browser-task delivery recovery enqueued task.delivery events");
971
+ }
972
+ }
862
973
  async function sendDeadlineDm(ref, text) {
863
974
  if (!ref)
864
975
  return;
@@ -884,8 +995,132 @@ export async function createEventPipeline(deps) {
884
995
  // a 30 s tick is housekeeping, not load-bearing for liveness.
885
996
  if (typeof timer.unref === "function")
886
997
  timer.unref();
998
+ void tick();
887
999
  return timer;
888
1000
  })();
1001
+ // ── Background-task housekeeping tick ──────────────────────────────────
1002
+ // BACKGROUND_TASK_RUNNER_DESIGN.md §6 / §10.2 — one 30 s tick that:
1003
+ // 1. transitions overdue clarifications (resolved=0 AND deadline_at <
1004
+ // now) to `timeout` via the runner (releases the slot + synthesizes
1005
+ // a fail-loud artifact + enqueues delivery);
1006
+ // 2. sweeps pending-queue timeouts (a task that sat behind the
1007
+ // concurrency cap too long);
1008
+ // 3. runs the delivery recovery sweep (re-enqueue `task.delivery` for
1009
+ // completed notify=true artifacts whose DM was lost, + undelivered
1010
+ // open clarifications).
1011
+ // Best-effort + idempotent: per-row failures log and continue; the
1012
+ // runner's `expireForDeadline` is a no-op on already-terminal rows.
1013
+ const backgroundTaskHousekeepingTimer = await (async () => {
1014
+ const { listOverdueClarifications, expireClarification, } = await import("../db/background-task-clarifications-store.js");
1015
+ const { sweepPendingTimeouts } = await import("../services/browser-task/browser-task-slots.js");
1016
+ const TICK_MS = 30_000;
1017
+ async function tick() {
1018
+ try {
1019
+ const nowMs = Date.now();
1020
+ // (1) clarification deadlines
1021
+ for (const clar of listOverdueClarifications(db, nowMs)) {
1022
+ try {
1023
+ expireClarification(db, clar.id, nowMs);
1024
+ await backgroundTaskRunner.expireForDeadline(clar.taskId, "clarification_deadline");
1025
+ }
1026
+ catch (err) {
1027
+ logger.warn({ err, taskId: clar.taskId }, "background-task clarification-deadline action failed (continuing)");
1028
+ }
1029
+ }
1030
+ // (2) pending-queue timeouts
1031
+ const sweep = sweepPendingTimeouts(backgroundTaskSlotStateRef.state, nowMs, config.backgroundTaskPendingQueueTimeoutMinutes);
1032
+ backgroundTaskSlotStateRef.state = sweep.state;
1033
+ for (const expired of sweep.expired) {
1034
+ try {
1035
+ await backgroundTaskRunner.expireForDeadline(expired.taskId, "queue_timeout", expired.waitedMs);
1036
+ }
1037
+ catch (err) {
1038
+ logger.warn({ err, taskId: expired.taskId }, "background-task queue-timeout action failed (continuing)");
1039
+ }
1040
+ }
1041
+ // (3) delivery recovery
1042
+ const count = await enqueueUndeliveredBackgroundTaskDeliveries({
1043
+ db,
1044
+ eventBus,
1045
+ nowMs,
1046
+ });
1047
+ if (count > 0) {
1048
+ logger.debug({ count }, "background-task delivery recovery enqueued task.delivery events");
1049
+ }
1050
+ }
1051
+ catch (err) {
1052
+ logger.warn({ err }, "background-task housekeeping tick failed (continuing)");
1053
+ }
1054
+ }
1055
+ const timer = setInterval(() => {
1056
+ void tick();
1057
+ }, TICK_MS);
1058
+ if (typeof timer.unref === "function")
1059
+ timer.unref();
1060
+ void tick();
1061
+ return timer;
1062
+ })();
1063
+ // ── Background-task boot recovery (resume / re-dispatch) ──────────────
1064
+ // BACKGROUND_TASK_RUNNER_DESIGN.md §10.2 — unlike browser_task (whose
1065
+ // in-memory session is unrecoverable, so its rows are force-failed),
1066
+ // background tasks survive a restart: their brief is self-contained AND
1067
+ // (Phase 4) their SDK session id is persisted.
1068
+ //
1069
+ // With `backgroundTaskResumeAcrossRestart` ON (default):
1070
+ // - `running` rows that captured a session id → RESUME the warm SDK
1071
+ // session (`resumeFromBoot`), falling back to re-dispatch on any
1072
+ // "couldn't load the session" signal.
1073
+ // - `awaiting_user` rows that captured a session id → LEFT as-is, so a
1074
+ // later `/clarify` reconstructs the handle and resumes (the delivery
1075
+ // recovery sweep re-sends the question if it never reached the owner).
1076
+ // - everything else (no session id, `pending`, or never-inited)
1077
+ // → re-dispatch from brief.
1078
+ // With the toggle OFF: the v1 behaviour — every non-terminal row is
1079
+ // re-dispatched from brief.
1080
+ //
1081
+ // Best-effort — failures log but do not abort startup.
1082
+ void (async () => {
1083
+ try {
1084
+ const { resetNonTerminalForBootRedispatch, listNonTerminalBackgroundTasks, } = await import("../db/background-task-store.js");
1085
+ if (!config.backgroundTaskResumeAcrossRestart) {
1086
+ const reset = resetNonTerminalForBootRedispatch(db);
1087
+ if (reset.length === 0)
1088
+ return;
1089
+ logger.warn({ count: reset.length }, "background-task boot recovery — re-dispatching non-terminal tasks from brief (resume disabled)");
1090
+ for (const { id } of reset) {
1091
+ void backgroundTaskRunner.runFromPost(id).catch((err) => {
1092
+ logger.error({ err, taskId: id }, "background-task boot re-dispatch threw (continuing)");
1093
+ });
1094
+ }
1095
+ return;
1096
+ }
1097
+ const rows = listNonTerminalBackgroundTasks(db);
1098
+ if (rows.length === 0)
1099
+ return;
1100
+ const resumeRunning = rows.filter((r) => r.state === "running" && r.backendSessionId);
1101
+ const leaveParked = rows.filter((r) => r.state === "awaiting_user" && r.backendSessionId);
1102
+ // Everything not resumed and not left parked is re-dispatched: the
1103
+ // runner's `resumeFromBoot` resets+re-runs each one (a `running` row
1104
+ // without a session id, a `pending` row, etc.).
1105
+ const redispatchIds = rows
1106
+ .filter((r) => !(r.state === "running" && r.backendSessionId)
1107
+ && !(r.state === "awaiting_user" && r.backendSessionId))
1108
+ .map((r) => r.id);
1109
+ logger.warn({
1110
+ resume: resumeRunning.length,
1111
+ leftParked: leaveParked.length,
1112
+ redispatch: redispatchIds.length,
1113
+ }, "background-task boot recovery — resuming / re-dispatching non-terminal tasks");
1114
+ for (const { id } of [...resumeRunning, ...redispatchIds.map((id) => ({ id }))]) {
1115
+ void backgroundTaskRunner.resumeFromBoot(id).catch((err) => {
1116
+ logger.error({ err, taskId: id }, "background-task boot resume/re-dispatch threw (continuing)");
1117
+ });
1118
+ }
1119
+ }
1120
+ catch (err) {
1121
+ logger.warn({ err }, "background-task boot recovery failed (continuing; rows stay non-terminal for next boot)");
1122
+ }
1123
+ })();
889
1124
  // ── Browser-task boot-recovery DM fan-out ─────────────────────────────
890
1125
  // BROWSER_TASK_REDESIGN_PLAN.md §6.5 — `initDatabase` flipped every
891
1126
  // non-terminal `browser_task` row to `failed (daemon_restarted)` and
@@ -1024,6 +1259,9 @@ export async function createEventPipeline(deps) {
1024
1259
  browserTaskDeadlineTimer,
1025
1260
  browserTaskSlotStateRef,
1026
1261
  browserTaskRunner,
1262
+ backgroundTaskSlotStateRef,
1263
+ backgroundTaskRunner,
1264
+ backgroundTaskHousekeepingTimer,
1027
1265
  };
1028
1266
  }
1029
1267
  /**
@@ -58,6 +58,7 @@
58
58
  */
59
59
  import { join } from "node:path";
60
60
  import { getContextDir } from "../config.js";
61
+ import { getAgentEnabled } from "../db/agents-store.js";
61
62
  import { isSetupCompleted, isDegraded as readDegradedMode, } from "../db/runtime-state.js";
62
63
  import { getRepositoryByGithub, getRepositoryByLocalPath, listRepositories, selectGithubRepoSlugs, selectGitWatchedRepos, } from "../db/repositories-store.js";
63
64
  import { dispatchMatchingTriggers } from "../core/trigger-dispatch.js";
@@ -191,7 +192,7 @@ export async function createObservers(deps) {
191
192
  eventBus,
192
193
  // Threaded through so observations of agent-originated commits
193
194
  // flip to `actor='agent'` (C1). Without this, daemon-side
194
- // commits would feed the hourly_check pending floor as user
195
+ // commits would feed the activity_scan pending floor as user
195
196
  // activity and re-invoke the agent in a loop.
196
197
  writeTracker,
197
198
  pushOverdueMinutes: config.gitPushOverdueMinutes,
@@ -307,8 +308,8 @@ export async function createObservers(deps) {
307
308
  }
308
309
  // Each call returns a fresh observer so the integration-lifecycle
309
310
  // helper re-registers a new instance after a mode flip — picking up
310
- // any gitPollIntervalSeconds / gitPushOverdueMinutes /
311
- // hourlyCheckEnabled PATCH that landed while the cron was idle.
311
+ // any gitPollIntervalSeconds / gitPushOverdueMinutes PATCH (or an
312
+ // activity-scan agent toggle) that landed while the cron was idle.
312
313
  const buildGitDelegatedCronObserver = () => new GitDelegatedCronObserver({
313
314
  db,
314
315
  eventBus,
@@ -316,7 +317,9 @@ export async function createObservers(deps) {
316
317
  githubRepos: selectGithubRepoSlugs(db),
317
318
  cadenceSeconds: config.gitPollIntervalSeconds,
318
319
  pushOverdueMinutes: config.gitPushOverdueMinutes,
319
- hourlyCheckEnabled: config.hourlyCheckEnabled,
320
+ // Collision-avoidance hint only: the activity-scan agent row's enabled
321
+ // flag is the single switch (AGENTS_HUB_REDESIGN_PLAN.md §2).
322
+ activityScanEnabled: getAgentEnabled(db, "activity-scan", true),
320
323
  });
321
324
  if (hasActiveDelegatedGitLifecycleIntegration(db)) {
322
325
  observerManager.register(buildGitDelegatedCronObserver());
@@ -509,7 +512,7 @@ export async function createObservers(deps) {
509
512
  // ── 7.2 Observation summarizer (cost-reduction-structural §A) ──
510
513
  // Drains pending observation rows asynchronously: pre-filter →
511
514
  // per-source LLM call → `summary_text` + `novelty_score` written
512
- // back to the row. The hourly_check skill consumes summaries instead
515
+ // back to the row. The activity_scan skill consumes summaries instead
513
516
  // of re-fetching raw content. Disabled cleanly via
514
517
  // `observationSummarizerEnabled` — when off, observations stay
515
518
  // pending and the skill drops to legacy fetch-on-doubt.
@@ -545,7 +548,7 @@ export async function createObservers(deps) {
545
548
  }
546
549
  // Codex / Gemini summarizer support is not yet implemented; the
547
550
  // worker translates `unsupported_backend` into a 'skipped' row so
548
- // the hourly_check skill drops to its legacy fetch path.
551
+ // the activity_scan skill drops to its legacy fetch path.
549
552
  return new UnsupportedSummarizerClient(fallbackBackend, fallbackModel);
550
553
  })();
551
554
  observerManager.register(new ObservationSummarizerWorker({
@@ -25,11 +25,11 @@ export declare const WEEKLY_REVIEW_CATCHUP_DAYS_OF_WEEK: Set<number>;
25
25
  export declare function getDueCatchupRoutines(db: Database.Database, config: AgentConfig, agentDayStartUtc: string, agentDayEndUtc: string, now: Date): string[];
26
26
  /**
27
27
  * Decide whether the boot sequence should immediately fire one
28
- * catch-up `routine.hourly_check` (because the cron callback never ran
28
+ * catch-up `routine.activity_scan` (because the cron callback never ran
29
29
  * for the current slot — typically because the host was asleep / the
30
30
  * daemon was stopped during the slot window).
31
31
  *
32
- * Slot math mirrors `shouldFireHourlyTickAt` in `scheduler.ts` so the
32
+ * Slot math mirrors `shouldFireActivityScanTickAt` in `scheduler.ts` so the
33
33
  * catch-up always lands on the same slot the cron would have fired at.
34
34
  *
35
35
  * **Wrap-around active hours are NOT supported.** The active-hours
@@ -49,12 +49,12 @@ export declare function getDueCatchupRoutines(db: Database.Database, config: Age
49
49
  * config-write time, or (b) split the window into two non-wrap
50
50
  * ranges in the same call site that consumes them.
51
51
  */
52
- export declare function shouldCatchUpHourlyCheck(db: Database.Database, config: AgentConfig, now: Date): boolean;
52
+ export declare function shouldCatchUpActivityScan(db: Database.Database, config: AgentConfig, now: Date): boolean;
53
53
  export declare function getProgressMinutesForHour(hour: number, dayBoundaryHour: number): number;
54
54
  export declare function hasFreshAgentDayTodayMd(todayMdPath: string, timezone: string | undefined, dayBoundaryHour: number, now?: Date): boolean;
55
55
  /**
56
56
  * Did `routine.morning_routine` complete successfully within the current
57
- * agent-day window? Used by the pre-routine gate that fronts hourly_check
57
+ * agent-day window? Used by the pre-routine gate that fronts activity_scan
58
58
  * and the review routines (evening / weekly / monthly) so they refuse to
59
59
  * run before the day has been properly opened.
60
60
  *
@@ -117,10 +117,10 @@ export interface StalledMorningRoutineWake {
117
117
  * without producing a successful `agent_actions` row. Returns the
118
118
  * offending row's metadata if stalled, null when the system is healthy.
119
119
  *
120
- * Pairs with `queueMorningRoutineWake` + the hourly-check pre-routine
120
+ * Pairs with `queueMorningRoutineWake` + the activity-scan pre-routine
121
121
  * gate. The dedup that keeps `queueMorningRoutineWake` from re-inserting
122
122
  * means a stuck wake row leaves the system in a silent freeze — the gate
123
- * skips `routine.hourly_check`, `routine.evening_review`, etc. forever
123
+ * skips `routine.activity_scan`, `routine.evening_review`, etc. forever
124
124
  * without surfacing to the user. This helper is the externally visible
125
125
  * signal the watchdog uses to break the silence.
126
126
  *
@@ -136,4 +136,102 @@ export declare function getStalledMorningRoutineWake(db: Database.Database, agen
136
136
  timezone?: string;
137
137
  dayBoundaryHour: number;
138
138
  }, thresholdMinutes: number, now?: Date): StalledMorningRoutineWake | null;
139
+ /**
140
+ * Audit-row action types that count as "a morning-routine attempt started"
141
+ * for the missed-fire predicate below. The parent row
142
+ * (`routine.morning_routine`) is written when the pipeline finishes
143
+ * (success or failure); the Stage A row
144
+ * (`routine.morning_routine_today`) is written `in_progress` the moment
145
+ * the agent execution starts, so a hung Stage A is still visible as an
146
+ * attempt. The fetch-window pre-pass is deliberately NOT included — it
147
+ * also runs for hourly/scheduled flows, and counting it would let an
148
+ * unrelated pre-pass mask a dead morning pipeline.
149
+ */
150
+ export declare const MORNING_ROUTINE_ATTEMPT_ACTION_TYPES: readonly ["routine.morning_routine", "routine.morning_routine_today"];
151
+ /**
152
+ * Grace period after the agent-day boundary before the missed-fire
153
+ * self-heal may conclude the 04:00 cron tick was swallowed. Wide enough
154
+ * that the normal cron → queueMorningRoutineWake → ScheduleWatcher claim
155
+ * → Stage A start chain has long since produced either a wake row or an
156
+ * attempt row; short enough that a swallowed tick costs at most ~25 min
157
+ * (grace + one self-heal interval) once the machine is awake.
158
+ */
159
+ export declare const MORNING_MISSED_FIRE_GRACE_MINUTES = 15;
160
+ /**
161
+ * Epoch ms of the most recent morning-routine attempt (any result,
162
+ * including `in_progress`) started within the current agent-day, or null
163
+ * when nothing has been attempted yet. Pure read.
164
+ */
165
+ export declare function getLatestMorningAttemptStartMs(db: Database.Database, agentDayConfig: {
166
+ timezone?: string;
167
+ dayBoundaryHour: number;
168
+ }, now?: Date): number | null;
169
+ export interface RecoverableStalledMorningWake {
170
+ /** agent_schedule.id of the `running` wake row to flip back to pending. */
171
+ id: number;
172
+ /** Minutes since the ScheduleWatcher claimed the row. */
173
+ claimedAgeMinutes: number;
174
+ }
175
+ /**
176
+ * Detect the hung-execution variant of the morning-routine silent stall:
177
+ * a wake row claimed to `running` at least `thresholdMinutes` ago with
178
+ * still no `routine.morning_routine` success for the current agent-day
179
+ * (machine slept mid-run and the backend stream died on the network
180
+ * change, the SDK call wedged, or the dispatch event was lost).
181
+ *
182
+ * `queueMorningRoutineWake`'s dedup treats a `running` row as "already
183
+ * in flight" and merges into it forever, so without recovery the day
184
+ * stays frozen until a daemon restart. The caller (scheduler self-heal
185
+ * tick) flips the returned row back to `pending` so the ScheduleWatcher
186
+ * re-claims it; the today-write-lock's wall-clock TTL guarantees a hung
187
+ * original cannot hold the lock against the re-run indefinitely.
188
+ *
189
+ * Staleness is measured from `task_context.claimedAt`, stamped by the
190
+ * ScheduleWatcher at claim time. That is the only signal that survives
191
+ * every confounder: `created_at` predates sleeps that delay the claim,
192
+ * `scheduled_for` is bumped by dedup merges, and attempt audit rows are
193
+ * windowed to the current agent-day (a run that started before the
194
+ * 04:00 boundary and hung across it has no attempt row "today").
195
+ * Invariants this leans on:
196
+ * - boot recovery (`recoverOrphanedRunningSchedules`) clears every
197
+ * `running` row at daemon start, so a live `running` row was always
198
+ * claimed by the current process — which always stamps;
199
+ * - `queueMorningRoutineWake`'s dedup-merge preserves unknown
200
+ * task_context keys, so a 04:00 cron merge cannot strip the stamp.
201
+ * A row without a readable stamp (stamp UPDATE failed, malformed JSON)
202
+ * is left to the alert-only watchdog rather than guessed at.
203
+ *
204
+ * Pure read — the caller owns the UPDATE.
205
+ */
206
+ export declare function getRecoverableStalledMorningWake(db: Database.Database, agentDayConfig: {
207
+ timezone?: string;
208
+ dayBoundaryHour: number;
209
+ }, thresholdMinutes: number, now?: Date): RecoverableStalledMorningWake | null;
210
+ /**
211
+ * Detect the missed-fire variant of the morning-routine silent stall:
212
+ * the machine was asleep at the day-boundary minute, node-cron silently
213
+ * dropped the tick (it never replays missed firings), and nothing since
214
+ * has re-queued the routine. Observable signature, all three at once:
215
+ *
216
+ * 1. no morning-routine attempt has *started* this agent-day (any
217
+ * result — a failed/exhausted retry chain counts as attempted, so
218
+ * this self-heal never resurrects a chain that
219
+ * `scheduleMorningRetry` deliberately stopped after MAX_RETRIES);
220
+ * 2. no `pending`/`running` morning wake row exists (the cron, the
221
+ * wake catch-up, and the retry chain all leave one when they are
222
+ * mid-flight);
223
+ * 3. the agent-day is at least `graceMinutes` old, so a healthy cron
224
+ * fire has had ample time to produce 1 or 2.
225
+ *
226
+ * Complements the WakeDetector: gaps shorter than its 5-min threshold
227
+ * that straddle the boundary minute (lid closed 03:59–04:02), or a
228
+ * detector failure, still converge here within one self-heal interval.
229
+ *
230
+ * Pure read — the caller routes through `queueMorningRoutineWake`,
231
+ * whose DB-backed dedup makes double-queueing impossible.
232
+ */
233
+ export declare function shouldQueueMissedMorningFire(db: Database.Database, agentDayConfig: {
234
+ timezone?: string;
235
+ dayBoundaryHour: number;
236
+ }, graceMinutes: number, now?: Date): boolean;
139
237
  export declare function readSkillCurationCadence(db: Database.Database): "daily" | "weekly" | "monthly";