@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
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `!revert tuning` — undo the most recent applied self-tuning config change
3
+ * (SELF_TUNING_REVIEW_CYCLE_DESIGN.md §3.4, Phase 3).
4
+ *
5
+ * The owner-side escape hatch for the Autonomous-plus-DM actuation posture
6
+ * (D1): every applied change DMs "Reply `!revert tuning` to undo", and this
7
+ * command is that reply. It restores the ledger's `prev` value through the
8
+ * same `applyConfigUpdates` chokepoint the actuator used, stamps
9
+ * `reverted_at` (which puts the key into the 28-day re-proposal cool-down),
10
+ * audits `self_tuning.reverted`, and records an explicit-correction
11
+ * feedback signal so the lesson loop learns from the owner's override.
12
+ *
13
+ * `runsWhilePaused: true` — a pure DB/config write with no LLM dispatch,
14
+ * and the owner may well have paused the agent *because* of a bad tuning
15
+ * change; the undo must not be locked behind `!start`.
16
+ */
17
+ import { BangArgError } from "./registry.js";
18
+ import { applyConfigUpdates } from "../../api/env-writer.js";
19
+ import { createSettingsStore } from "../../settings/settings-store.js";
20
+ import { findLatestRevertableEntry, listLedgerEntries, revertAppliedTuningChange, } from "../feedback/tuning-actuator.js";
21
+ const USAGE = "Usage: `!revert tuning` — undo the most recent self-tuning config change.";
22
+ export const revertTuningCommand = {
23
+ prefix: "!revert",
24
+ title: "Revert self-tuning",
25
+ describe: "undo the most recent self-tuning config change",
26
+ details: [
27
+ "Restores the previous value of the most recently applied self-tuning config change via the standard config chokepoint.",
28
+ "The reverted key enters a 28-day re-proposal cool-down so the loop cannot immediately re-apply it.",
29
+ "Pure DB/config write — works while the agent is paused.",
30
+ ],
31
+ runsWhilePaused: true,
32
+ parseArgs: (rest) => {
33
+ if (rest.toLowerCase() !== "tuning") {
34
+ throw new BangArgError(USAGE);
35
+ }
36
+ return undefined;
37
+ },
38
+ handler: async (ctx) => {
39
+ const entry = findLatestRevertableEntry(listLedgerEntries(ctx.db));
40
+ if (!entry) {
41
+ await ctx.notify("No applied self-tuning change to revert. Applied changes show up in the daemon's per-change DM and the weekly review's tuning ledger.");
42
+ return;
43
+ }
44
+ const settingsStore = createSettingsStore(ctx.db);
45
+ const result = await revertAppliedTuningChange({
46
+ db: ctx.db,
47
+ applyUpdates: (updates) => applyConfigUpdates(ctx.config, settingsStore, updates, { db: ctx.db }),
48
+ feedbackLearningEnabled: ctx.config.feedbackLearningEnabled,
49
+ }, entry, {
50
+ trigger: "bang_command",
51
+ reason: "owner requested revert via !revert tuning",
52
+ now: new Date(),
53
+ });
54
+ if (!result.ok) {
55
+ await ctx.notify(`Could not revert ${entry.key}: ${result.error}`);
56
+ return;
57
+ }
58
+ await ctx.notify(`Reverted ${entry.key} ${String(entry.blob.proposed)} → ` +
59
+ `${String(entry.blob.prev)} (rule ${entry.blob.rule}, applied ` +
60
+ `${entry.blob.applied_at.slice(0, 10)}). The key is now in a ` +
61
+ "28-day re-proposal cool-down.");
62
+ },
63
+ };
@@ -45,7 +45,7 @@ export const stopCommand = {
45
45
  "",
46
46
  "Halted:",
47
47
  "- Morning / evening / weekly / monthly review",
48
- "- Hourly check, profile sweep",
48
+ "- Activity scan, profile sweep",
49
49
  "- Scheduled DMs",
50
50
  "",
51
51
  "Still running:",
@@ -63,7 +63,7 @@ export const startCommand = {
63
63
  describe: "Resume autonomous work paused by !stop.",
64
64
  details: [
65
65
  "Clears the user-paused state.",
66
- "Queued observations are consumed by the next eligible hourly check.",
66
+ "Queued observations are consumed by the next eligible activity scan.",
67
67
  ],
68
68
  runsWhilePaused: true,
69
69
  handler: async (ctx) => {
@@ -82,7 +82,7 @@ export const startCommand = {
82
82
  "",
83
83
  `Was paused: ${formatLocalLong(prev.since, ctx.config)}`,
84
84
  "",
85
- "Next hourly check will consume any observations",
85
+ "Next activity scan will consume any observations",
86
86
  "queued during the pause.",
87
87
  ].join("\n"));
88
88
  },
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Per-task control bang-commands — `!status` (list the active detached
3
+ * tasks) and `!stop <id>` (cancel one of them). BACKGROUND_TASK_RUNNER_
4
+ * DESIGN.md Phase 4.
5
+ *
6
+ * Both run OUTSIDE the SessionGate (the bang interceptor in
7
+ * `dispatcher-message-handler` runs before any agent backend, ahead of
8
+ * the gate), so the owner can inspect and cancel a long-running worker
9
+ * even while a DM turn is in flight or a delivery turn is gated. Both are
10
+ * `runsWhilePaused: true` — pure DB reads + a runner abort, no LLM work.
11
+ *
12
+ * `!stop` is a PREFIX command. Bare `!stop` resolves to the exact-match
13
+ * pause command (`commands-stop-start.ts`); `!stop <id>` falls through to
14
+ * the prefix matcher here and cancels that single task. The two never
15
+ * collide because the registry resolves exact matches before prefixes.
16
+ */
17
+ import type { BangCommand, BangPrefixCommand } from "./registry.js";
18
+ export declare const statusCommand: BangCommand;
19
+ export declare const stopTaskCommand: BangPrefixCommand;
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Per-task control bang-commands — `!status` (list the active detached
3
+ * tasks) and `!stop <id>` (cancel one of them). BACKGROUND_TASK_RUNNER_
4
+ * DESIGN.md Phase 4.
5
+ *
6
+ * Both run OUTSIDE the SessionGate (the bang interceptor in
7
+ * `dispatcher-message-handler` runs before any agent backend, ahead of
8
+ * the gate), so the owner can inspect and cancel a long-running worker
9
+ * even while a DM turn is in flight or a delivery turn is gated. Both are
10
+ * `runsWhilePaused: true` — pure DB reads + a runner abort, no LLM work.
11
+ *
12
+ * `!stop` is a PREFIX command. Bare `!stop` resolves to the exact-match
13
+ * pause command (`commands-stop-start.ts`); `!stop <id>` falls through to
14
+ * the prefix matcher here and cancels that single task. The two never
15
+ * collide because the registry resolves exact matches before prefixes.
16
+ */
17
+ import { getBackgroundTask, listBackgroundTasks, } from "../../db/background-task-store.js";
18
+ import { getBrowserTask, listBrowserTasks, } from "../../db/browser-task-store.js";
19
+ const BACKGROUND_ACTIVE_STATES = ["pending", "running", "awaiting_user"];
20
+ const BROWSER_ACTIVE_STATES = [
21
+ "pending",
22
+ "running",
23
+ "awaiting_user",
24
+ "final_confirm",
25
+ ];
26
+ function backgroundTitle(row) {
27
+ return row.title ?? row.brief.slice(0, 60);
28
+ }
29
+ function browserTitle(row) {
30
+ return row.description.slice(0, 60);
31
+ }
32
+ /** Gather every non-terminal background + browser task, newest first. */
33
+ function listActiveTasks(ctx) {
34
+ const background = listBackgroundTasks(ctx.db, {
35
+ states: [...BACKGROUND_ACTIVE_STATES],
36
+ limit: 50,
37
+ }).map((row) => ({
38
+ id: row.id,
39
+ kind: "background",
40
+ state: row.state,
41
+ title: backgroundTitle(row),
42
+ }));
43
+ const browser = listBrowserTasks(ctx.db, {
44
+ states: [...BROWSER_ACTIVE_STATES],
45
+ limit: 50,
46
+ }).map((row) => ({
47
+ id: row.id,
48
+ kind: "browser",
49
+ state: row.state,
50
+ title: browserTitle(row),
51
+ }));
52
+ return [...background, ...browser];
53
+ }
54
+ /** Short, copy-pasteable id stem for the `!status` listing. The full id is
55
+ * a uuid v4; the first 8 chars are unique in practice and `!stop` accepts
56
+ * any unambiguous prefix. */
57
+ function shortId(id) {
58
+ return id.slice(0, 8);
59
+ }
60
+ function formatStatus(tasks) {
61
+ if (tasks.length === 0) {
62
+ return "No background or browser tasks are running.";
63
+ }
64
+ const lines = tasks.map((t) => {
65
+ const label = t.kind === "background" ? "bg" : "web";
66
+ return `• [${label} ${shortId(t.id)}] ${t.state} — ${t.title}`;
67
+ });
68
+ const header = tasks.length === 1
69
+ ? "1 active task:"
70
+ : `${tasks.length} active tasks:`;
71
+ return [
72
+ header,
73
+ ...lines,
74
+ "",
75
+ "Stop one with !stop <id> (the short id shown above is fine).",
76
+ ].join("\n");
77
+ }
78
+ export const statusCommand = {
79
+ name: "!status",
80
+ title: "Task status",
81
+ describe: "List running background / browser tasks",
82
+ runsWhilePaused: true,
83
+ handler: async (ctx) => {
84
+ await ctx.notify(formatStatus(listActiveTasks(ctx)));
85
+ },
86
+ };
87
+ /** Resolve the owner-typed id to a single active task. Accepts the full
88
+ * uuid or any unambiguous prefix (so the `!status` short id works). */
89
+ function resolveTarget(ctx, raw) {
90
+ const needle = raw.trim().toLowerCase();
91
+ // Exact-id fast path — a full uuid lands here without scanning.
92
+ const bg = getBackgroundTask(ctx.db, needle);
93
+ if (bg && BACKGROUND_ACTIVE_STATES.includes(bg.state)) {
94
+ return { kind: "ok", task: { id: bg.id, kind: "background", state: bg.state, title: backgroundTitle(bg) } };
95
+ }
96
+ const web = getBrowserTask(ctx.db, needle);
97
+ if (web && BROWSER_ACTIVE_STATES.includes(web.state)) {
98
+ return { kind: "ok", task: { id: web.id, kind: "browser", state: web.state, title: browserTitle(web) } };
99
+ }
100
+ // Prefix match across the active set.
101
+ const matches = listActiveTasks(ctx).filter((t) => t.id.toLowerCase().startsWith(needle));
102
+ if (matches.length === 1)
103
+ return { kind: "ok", task: matches[0] };
104
+ if (matches.length === 0)
105
+ return { kind: "none" };
106
+ return { kind: "ambiguous", count: matches.length };
107
+ }
108
+ export const stopTaskCommand = {
109
+ prefix: "!stop",
110
+ title: "Stop a task",
111
+ describe: "Cancel one task by id (bare !stop pauses the agent)",
112
+ runsWhilePaused: true,
113
+ handler: async (ctx, args) => {
114
+ const id = typeof args === "string" ? args : "";
115
+ if (id.length === 0) {
116
+ // Unreachable in practice — bare `!stop` resolves to the exact pause
117
+ // command — but guard so a future resolver change can't crash here.
118
+ await ctx.notify("Usage: !stop <id> to cancel one task (run !status for ids). " +
119
+ "Bare !stop pauses the whole agent.");
120
+ return;
121
+ }
122
+ const resolved = resolveTarget(ctx, id);
123
+ if (resolved.kind === "none") {
124
+ await ctx.notify(`No active task matches "${id}". Run !status to see what's running.`);
125
+ return;
126
+ }
127
+ if (resolved.kind === "ambiguous") {
128
+ await ctx.notify(`"${id}" matches ${resolved.count} active tasks — add more characters of the id.`);
129
+ return;
130
+ }
131
+ const task = resolved.task;
132
+ const cancel = task.kind === "background"
133
+ ? ctx.cancelBackgroundTask
134
+ : ctx.cancelBrowserTask;
135
+ if (!cancel) {
136
+ // Runner not wired (lite install / boot race). The row still exists;
137
+ // tell the owner rather than silently dropping the request.
138
+ await ctx.notify(`Can't stop that ${task.kind} task right now — the runner isn't available. ` +
139
+ "Try again in a moment.");
140
+ return;
141
+ }
142
+ const ok = await cancel(task.id, "user_bang_stop");
143
+ await ctx.notify(ok
144
+ ? `Stopping the ${task.kind} task "${task.title}".`
145
+ : `Couldn't stop "${task.title}" — it may have already finished.`);
146
+ },
147
+ };
@@ -108,7 +108,7 @@ async function requireWikiWorkspace(ctx, workspaceName = null) {
108
108
  export const ingestCommand = {
109
109
  prefix: "!ingest",
110
110
  title: "Ingest URLs",
111
- describe: "ingest one or more URLs into the wiki's raw layer (`@<workspace>` optional)",
111
+ describe: "ingest one or more URLs into the wiki's raw layer",
112
112
  parseArgs(rest) {
113
113
  try {
114
114
  const { workspaceName, rest: payload } = splitWorkspaceToken(rest);
@@ -355,7 +355,7 @@ function renderCompileInProgressDm(holder, workspaceName) {
355
355
  export const askCommand = {
356
356
  prefix: "!ask",
357
357
  title: "Ask wiki",
358
- describe: "ask a question against the wiki (`@<workspace>` optional)",
358
+ describe: "ask a question against the wiki",
359
359
  parseArgs(rest) {
360
360
  const { workspaceName, rest: payload } = splitWorkspaceToken(rest);
361
361
  const question = payload.trim();
@@ -458,7 +458,7 @@ export const wikiHelpCommand = {
458
458
  export const lintCommand = {
459
459
  prefix: "!lint",
460
460
  title: "Lint wiki",
461
- describe: "audit the wiki and write a dated health report (`@<workspace>` optional)",
461
+ describe: "audit the wiki and write a dated health report",
462
462
  parseArgs(rest) {
463
463
  const { workspaceName, rest: payload } = splitWorkspaceToken(rest);
464
464
  if (payload.trim().length > 0) {
@@ -494,7 +494,7 @@ export const lintCommand = {
494
494
  export const traceCommand = {
495
495
  prefix: "!trace",
496
496
  title: "Trace wiki topic",
497
- describe: "reconstruct an idea's evolution across raw / wiki / outputs (`@<workspace>` optional)",
497
+ describe: "reconstruct an idea's evolution across raw / wiki / outputs",
498
498
  parseArgs(rest) {
499
499
  const { workspaceName, rest: payload } = splitWorkspaceToken(rest);
500
500
  const topic = payload.trim();
@@ -560,7 +560,7 @@ export function parseConnectArgs(rest) {
560
560
  export const connectCommand = {
561
561
  prefix: "!connect",
562
562
  title: "Connect wiki domains",
563
- describe: "bridge two domains in the wiki (`@<workspace>` optional)",
563
+ describe: "bridge two domains in the wiki",
564
564
  parseArgs(rest) {
565
565
  return parseConnectArgs(rest);
566
566
  },
@@ -6,6 +6,7 @@
6
6
  export { BangCommandRegistry, BangArgError, buildPausedNotice, buildUnknownCommandReply, getBangCommandName, makeNotify, normalizeBangCommandText, tryHandle, } from "./registry.js";
7
7
  export type { BangCommand, BangCommandContext, BangCommandMatch, BangPrefixCommand, RegisteredBangCommand, } from "./registry.js";
8
8
  export { stopCommand, startCommand } from "./commands-stop-start.js";
9
+ export { statusCommand, stopTaskCommand } from "./commands-task-control.js";
9
10
  export { closeCommand } from "./commands-close.js";
10
11
  export { costAllCommand, costBackendCommands, formatCostAll, formatCostFiltered, } from "./commands-cost.js";
11
12
  export { reportCommand, formatReport } from "./commands-report.js";
@@ -13,6 +14,7 @@ export { helpCommand, formatHelp } from "./commands-help.js";
13
14
  export { askCommand, connectCommand, compileCommand, lintCommand, parseConnectArgs, traceCommand, ingestCommand, wikiHelpCommand, wikiStatusCommand, } from "./commands-wiki.js";
14
15
  export { researchCommand, parseResearchArgs } from "./commands-research.js";
15
16
  export { checksCommand, formatChecks } from "./commands-checks.js";
17
+ export { revertTuningCommand } from "./commands-revert-tuning.js";
16
18
  export { buildSystemMarker, ensureSystemMarker, formatLocalLong, formatLocalShort, formatMoney, MOBILE_REPLY_BUDGET, truncateForMobile, } from "./format-utils.js";
17
19
  export { CUSTOM_BANG_COMMAND_SOURCE, DEFAULT_USER_BANG_COMMAND_SKILLS, USER_BANG_COMMAND_NAME_PATTERN, buildUserBangCommandPrompt, createUserBangCommand, createUserBangCommandEvent, deleteUserBangCommand, getEnabledUserBangCommandByCommand, getUserBangCommandByCommand, getUserBangCommandById, listUserBangCommands, normalizeBangCommandName, parseEnabledSkills, resolveCommandSkillSlugs, serializeEnabledSkills, updateUserBangCommand, } from "./user-commands.js";
18
20
  export type { NormalizeBangCommandNameResult, UserBangCommand, UserBangCommandInput, } from "./user-commands.js";
@@ -5,6 +5,7 @@
5
5
  */
6
6
  export { BangCommandRegistry, BangArgError, buildPausedNotice, buildUnknownCommandReply, getBangCommandName, makeNotify, normalizeBangCommandText, tryHandle, } from "./registry.js";
7
7
  export { stopCommand, startCommand } from "./commands-stop-start.js";
8
+ export { statusCommand, stopTaskCommand } from "./commands-task-control.js";
8
9
  export { closeCommand } from "./commands-close.js";
9
10
  export { costAllCommand, costBackendCommands, formatCostAll, formatCostFiltered, } from "./commands-cost.js";
10
11
  export { reportCommand, formatReport } from "./commands-report.js";
@@ -12,10 +13,12 @@ export { helpCommand, formatHelp } from "./commands-help.js";
12
13
  export { askCommand, connectCommand, compileCommand, lintCommand, parseConnectArgs, traceCommand, ingestCommand, wikiHelpCommand, wikiStatusCommand, } from "./commands-wiki.js";
13
14
  export { researchCommand, parseResearchArgs } from "./commands-research.js";
14
15
  export { checksCommand, formatChecks } from "./commands-checks.js";
16
+ export { revertTuningCommand } from "./commands-revert-tuning.js";
15
17
  export { buildSystemMarker, ensureSystemMarker, formatLocalLong, formatLocalShort, formatMoney, MOBILE_REPLY_BUDGET, truncateForMobile, } from "./format-utils.js";
16
18
  export { CUSTOM_BANG_COMMAND_SOURCE, DEFAULT_USER_BANG_COMMAND_SKILLS, USER_BANG_COMMAND_NAME_PATTERN, buildUserBangCommandPrompt, createUserBangCommand, createUserBangCommandEvent, deleteUserBangCommand, getEnabledUserBangCommandByCommand, getUserBangCommandByCommand, getUserBangCommandById, listUserBangCommands, normalizeBangCommandName, parseEnabledSkills, resolveCommandSkillSlugs, serializeEnabledSkills, updateUserBangCommand, } from "./user-commands.js";
17
19
  import { BangCommandRegistry } from "./registry.js";
18
20
  import { startCommand, stopCommand } from "./commands-stop-start.js";
21
+ import { statusCommand, stopTaskCommand } from "./commands-task-control.js";
19
22
  import { closeCommand } from "./commands-close.js";
20
23
  import { costAllCommand, costBackendCommands, } from "./commands-cost.js";
21
24
  import { reportCommand } from "./commands-report.js";
@@ -23,6 +26,7 @@ import { helpCommand } from "./commands-help.js";
23
26
  import { askCommand, connectCommand, compileCommand, lintCommand, traceCommand, ingestCommand, wikiHelpCommand, wikiStatusCommand, } from "./commands-wiki.js";
24
27
  import { researchCommand } from "./commands-research.js";
25
28
  import { checksCommand } from "./commands-checks.js";
29
+ import { revertTuningCommand } from "./commands-revert-tuning.js";
26
30
  /**
27
31
  * Build a registry preloaded with the v1 built-in commands. The registry
28
32
  * is mutable — callers can `.register(...)` more commands afterwards.
@@ -31,6 +35,11 @@ export function createDefaultBangCommandRegistry() {
31
35
  const registry = new BangCommandRegistry();
32
36
  registry.register(stopCommand);
33
37
  registry.register(startCommand);
38
+ // BACKGROUND_TASK_RUNNER_DESIGN.md Phase 4 — per-task control. `!status`
39
+ // (exact) lists active detached tasks; `!stop <id>` (prefix) cancels one.
40
+ // Bare `!stop` still resolves to the exact pause command above.
41
+ registry.register(statusCommand);
42
+ registry.register(stopTaskCommand);
34
43
  registry.register(closeCommand);
35
44
  registry.register(costAllCommand);
36
45
  for (const cmd of costBackendCommands) {
@@ -54,5 +63,8 @@ export function createDefaultBangCommandRegistry() {
54
63
  // weekly surfacing lives inside `routine.weekly_review` and uses
55
64
  // `/api/browser-history/reloads/weekly` instead.
56
65
  registry.register(checksCommand);
66
+ // SELF_TUNING_REVIEW_CYCLE_DESIGN.md §3.4 Phase 3 — `!revert tuning`,
67
+ // the owner-side undo for autonomously applied tuning changes.
68
+ registry.register(revertTuningCommand);
57
69
  return registry;
58
70
  }
@@ -53,6 +53,18 @@ export interface BangCommandContext {
53
53
  }>;
54
54
  enqueueUserBangCommand?: (command: UserBangCommand, event: MessageEvent) => Promise<void>;
55
55
  enqueueWikiEvent?: (event: Event) => Promise<void>;
56
+ /**
57
+ * Per-task `!stop <id>` cancel hooks (BACKGROUND_TASK_RUNNER_DESIGN.md
58
+ * Phase 4). Wired from the dispatcher's live runner instances, which
59
+ * hold the in-memory worker handles the abort needs to reach. Optional
60
+ * so bang-command unit fixtures (and lite installs without a runner)
61
+ * can omit them — the `!stop` handler tells the owner the runner is
62
+ * unavailable rather than silently dropping the request. Return `true`
63
+ * when an abort/terminal-transition was issued, `false` when the task
64
+ * was already gone.
65
+ */
66
+ cancelBackgroundTask?: (taskId: string, reason: string) => Promise<boolean>;
67
+ cancelBrowserTask?: (taskId: string, reason: string) => Promise<boolean>;
56
68
  /**
57
69
  * BROWSER_HISTORY_INTEGRATION_PLAN P3 — wire-through for the
58
70
  * `!research accept` / `!research wiki` paths. The bang handler
@@ -2,16 +2,24 @@
2
2
  * Day-boundary fan-out helper — enumerates active research clusters
3
3
  * with new meaningful activity in the last 24h and emits one
4
4
  * `routine.research_cluster_update` event per cluster onto the
5
- * EventBus. Called once per agent-day from the scheduler's day-boundary
6
- * callback (see `bootstrap/index.ts`).
5
+ * EventBus. Called from the scheduler's day-boundary callback (see
6
+ * `index.ts`), which can fire MORE than once per agent-day — the 04:00
7
+ * cron, wake catch-up (every detected sleep gap >= 5 min), and the
8
+ * morning self-heal all invoke it.
7
9
  *
8
- * BROWSER_HISTORY_INTEGRATION_PLAN §10.6 step 3.
10
+ * BROWSER_HISTORY_INTEGRATION_PLAN §10.6 step 3; dedup added by
11
+ * RESEARCH_CLUSTER_COST_FIX_PLAN.md F1 after replayed day boundaries
12
+ * re-enqueued the same cluster ~25x in one morning.
9
13
  *
10
- * Pure orchestration — no LLM in this path. The fan-out is bounded by
11
- * the `limit` parameter (default 25) so a backlog from a long outage
12
- * cannot flood the event queue. Clusters that miss this cycle pick up
13
- * on the next day-boundary fire; the next cycle's DB query naturally
14
- * sees them as "still active with new activity since prior update".
14
+ * Pure orchestration — no LLM in this path. Each cluster is atomically
15
+ * CLAIMED for `todayAgentDay` BEFORE its event is enqueued, so neither a
16
+ * later sequential replay (`listClustersNeedingUpdate` filters claimed
17
+ * rows) nor a concurrently in-flight callback (the per-row claim is a
18
+ * single atomic UPDATE only one fire wins) can double-fire — exactly
19
+ * one enqueue per cluster per agent day. A cluster whose claimed run
20
+ * fails retries on the next agent day; the journal task flow backfills
21
+ * the missed day. The fan-out is bounded by the `limit` parameter
22
+ * (default 25) so a backlog from a long outage cannot flood the queue.
15
23
  */
16
24
  import type Database from "better-sqlite3";
17
25
  export interface EventBusLike {
@@ -23,17 +31,23 @@ export interface FanoutResult {
23
31
  enqueuedSlugs: string[];
24
32
  }
25
33
  /**
26
- * Enumerate active clusters with new meaningful activity since the last
27
- * day-boundary cycle and enqueue one `routine.research_cluster_update`
28
- * event per cluster. Always returns the slugs it enqueued so the caller
29
- * can log a structured summary.
34
+ * Enumerate active clusters with new meaningful activity not yet
35
+ * enqueued for `todayAgentDay` and enqueue one
36
+ * `routine.research_cluster_update` event per cluster. Always returns
37
+ * the slugs it enqueued so the caller can log a structured summary.
38
+ *
39
+ * `todayAgentDay` is the caller-computed local agent-day label
40
+ * (`getAgentDayDateStr(config.timezone, config.dayBoundaryHour)`) —
41
+ * the fan-out is timezone-blind by design so it stays pure and
42
+ * deterministic in tests.
30
43
  *
31
44
  * When `eventBus` is absent (early-boot path before EventBus wiring),
32
45
  * the function returns an empty result without throwing — the next
33
46
  * day-boundary tick re-evaluates and picks up the clusters once
34
- * messaging is live.
47
+ * messaging is live. Nothing is stamped on that path.
35
48
  */
36
- export declare function fanoutResearchClusterUpdates(db: Database.Database, eventBus: EventBusLike | null | undefined, options?: {
49
+ export declare function fanoutResearchClusterUpdates(db: Database.Database, eventBus: EventBusLike | null | undefined, options: {
50
+ todayAgentDay: string;
37
51
  lookbackMs?: number;
38
52
  limit?: number;
39
53
  nowMs?: number;
@@ -2,41 +2,64 @@
2
2
  * Day-boundary fan-out helper — enumerates active research clusters
3
3
  * with new meaningful activity in the last 24h and emits one
4
4
  * `routine.research_cluster_update` event per cluster onto the
5
- * EventBus. Called once per agent-day from the scheduler's day-boundary
6
- * callback (see `bootstrap/index.ts`).
5
+ * EventBus. Called from the scheduler's day-boundary callback (see
6
+ * `index.ts`), which can fire MORE than once per agent-day — the 04:00
7
+ * cron, wake catch-up (every detected sleep gap >= 5 min), and the
8
+ * morning self-heal all invoke it.
7
9
  *
8
- * BROWSER_HISTORY_INTEGRATION_PLAN §10.6 step 3.
10
+ * BROWSER_HISTORY_INTEGRATION_PLAN §10.6 step 3; dedup added by
11
+ * RESEARCH_CLUSTER_COST_FIX_PLAN.md F1 after replayed day boundaries
12
+ * re-enqueued the same cluster ~25x in one morning.
9
13
  *
10
- * Pure orchestration — no LLM in this path. The fan-out is bounded by
11
- * the `limit` parameter (default 25) so a backlog from a long outage
12
- * cannot flood the event queue. Clusters that miss this cycle pick up
13
- * on the next day-boundary fire; the next cycle's DB query naturally
14
- * sees them as "still active with new activity since prior update".
14
+ * Pure orchestration — no LLM in this path. Each cluster is atomically
15
+ * CLAIMED for `todayAgentDay` BEFORE its event is enqueued, so neither a
16
+ * later sequential replay (`listClustersNeedingUpdate` filters claimed
17
+ * rows) nor a concurrently in-flight callback (the per-row claim is a
18
+ * single atomic UPDATE only one fire wins) can double-fire — exactly
19
+ * one enqueue per cluster per agent day. A cluster whose claimed run
20
+ * fails retries on the next agent day; the journal task flow backfills
21
+ * the missed day. The fan-out is bounded by the `limit` parameter
22
+ * (default 25) so a backlog from a long outage cannot flood the queue.
15
23
  */
16
- import { listClustersNeedingUpdate } from "../../db/browser-history-store.js";
24
+ import { claimClusterJournalEnqueue, listClustersNeedingUpdate, } from "../../db/browser-history-store.js";
17
25
  import { createResearchCommandEvent } from "./research-events.js";
18
26
  export const RESEARCH_CLUSTER_FANOUT_LOOKBACK_MS = 24 * 60 * 60 * 1000;
19
27
  export const RESEARCH_CLUSTER_FANOUT_LIMIT = 25;
20
28
  /**
21
- * Enumerate active clusters with new meaningful activity since the last
22
- * day-boundary cycle and enqueue one `routine.research_cluster_update`
23
- * event per cluster. Always returns the slugs it enqueued so the caller
24
- * can log a structured summary.
29
+ * Enumerate active clusters with new meaningful activity not yet
30
+ * enqueued for `todayAgentDay` and enqueue one
31
+ * `routine.research_cluster_update` event per cluster. Always returns
32
+ * the slugs it enqueued so the caller can log a structured summary.
33
+ *
34
+ * `todayAgentDay` is the caller-computed local agent-day label
35
+ * (`getAgentDayDateStr(config.timezone, config.dayBoundaryHour)`) —
36
+ * the fan-out is timezone-blind by design so it stays pure and
37
+ * deterministic in tests.
25
38
  *
26
39
  * When `eventBus` is absent (early-boot path before EventBus wiring),
27
40
  * the function returns an empty result without throwing — the next
28
41
  * day-boundary tick re-evaluates and picks up the clusters once
29
- * messaging is live.
42
+ * messaging is live. Nothing is stamped on that path.
30
43
  */
31
- export async function fanoutResearchClusterUpdates(db, eventBus, options = {}) {
44
+ export async function fanoutResearchClusterUpdates(db, eventBus, options) {
32
45
  if (!eventBus)
33
46
  return { enqueuedSlugs: [] };
34
47
  const lookbackMs = options.lookbackMs ?? RESEARCH_CLUSTER_FANOUT_LOOKBACK_MS;
35
48
  const limit = options.limit ?? RESEARCH_CLUSTER_FANOUT_LIMIT;
36
49
  const nowMs = options.nowMs ?? Date.now();
37
- const rows = listClustersNeedingUpdate(db, lookbackMs, nowMs, limit);
50
+ const rows = listClustersNeedingUpdate(db, lookbackMs, nowMs, limit, options.todayAgentDay);
38
51
  const enqueuedSlugs = [];
39
52
  for (const row of rows) {
53
+ // Claim BEFORE the put. The claim is atomic, so if a concurrent
54
+ // day-boundary fire (the 04:00 cron is fire-and-forget and can
55
+ // overlap a wake catch-up) already claimed this cluster for today,
56
+ // our claim returns false and we skip it — exactly one enqueue per
57
+ // cluster per agent day. If the put then throws, the claim still
58
+ // persists so the cluster waits for the next agent day instead of
59
+ // re-firing on the next day-boundary replay.
60
+ if (!claimClusterJournalEnqueue(db, row.slug, options.todayAgentDay)) {
61
+ continue;
62
+ }
40
63
  await eventBus.put(createResearchCommandEvent({
41
64
  processKey: "routine.research_cluster_update",
42
65
  slug: row.slug,
@@ -1,6 +1,6 @@
1
1
  import type Database from "better-sqlite3";
2
2
  import type { AgentConfig } from "../config.js";
3
- export declare const PROACTIVE_FORWARD_TYPES: readonly ["proactive_forward", "proactive_forward_batched", "scheduled_dm"];
3
+ export declare const PROACTIVE_FORWARD_TYPES: readonly ["proactive_forward", "proactive_forward_batched", "scheduled_dm", "task_result", "task_clarification"];
4
4
  export type ProactiveForwardType = (typeof PROACTIVE_FORWARD_TYPES)[number];
5
5
  /**
6
6
  * Render-side suffix that goes after the role tag in the assembled
@@ -17,6 +17,9 @@ export type ProactiveForwardType = (typeof PROACTIVE_FORWARD_TYPES)[number];
17
17
  * embed the dispatch timestamp here: every line already carries the
18
18
  * `[timestamp]` prefix added by the caller, so a second timestamp
19
19
  * in the suffix would just duplicate that information.
20
+ * - `task_result` / `task_clarification` → task-delivery annotations
21
+ * used by the DM agent to understand that a background task result or
22
+ * clarification was already surfaced.
20
23
  */
21
24
  export declare function formatForwardSuffix(metadata: Record<string, unknown>): string;
22
25
  export interface DeliveryRow {
@@ -41,4 +44,5 @@ export declare function recordProactiveForwardDeliveries(params: {
41
44
  dispatchIds?: string[];
42
45
  originSessionIds?: Array<number | null | undefined>;
43
46
  notificationType: ProactiveForwardType;
47
+ extraMetadata?: Record<string, unknown>;
44
48
  }): ProactiveForwardRecordResult;
@@ -12,6 +12,11 @@ export const PROACTIVE_FORWARD_TYPES = [
12
12
  // `notification_log` only and the user's follow-up reply would
13
13
  // have nothing to anchor to.
14
14
  "scheduled_dm",
15
+ // BACKGROUND_TASK_RUNNER_DESIGN.md Phase 1 — task.delivery records
16
+ // browser-task results and clarification prompts into owner-facing
17
+ // conversation history.
18
+ "task_result",
19
+ "task_clarification",
15
20
  ];
16
21
  /**
17
22
  * Render-side suffix that goes after the role tag in the assembled
@@ -28,9 +33,16 @@ export const PROACTIVE_FORWARD_TYPES = [
28
33
  * embed the dispatch timestamp here: every line already carries the
29
34
  * `[timestamp]` prefix added by the caller, so a second timestamp
30
35
  * in the suffix would just duplicate that information.
36
+ * - `task_result` / `task_clarification` → task-delivery annotations
37
+ * used by the DM agent to understand that a background task result or
38
+ * clarification was already surfaced.
31
39
  */
32
40
  export function formatForwardSuffix(metadata) {
33
41
  switch (getProactiveForwardType(metadata)) {
42
+ case "task_result":
43
+ return " (background task result delivered)";
44
+ case "task_clarification":
45
+ return " (background task clarification requested)";
34
46
  case "scheduled_dm":
35
47
  return " (scheduled DM dispatched)";
36
48
  case "proactive_forward":
@@ -99,6 +111,7 @@ export function recordProactiveForwardDeliveries(params) {
99
111
  notificationType: params.notificationType,
100
112
  dispatchIds,
101
113
  originSessionIds,
114
+ ...(params.extraMetadata ?? {}),
102
115
  };
103
116
  let inserted = 0;
104
117
  const sessionIds = [];
@@ -16,6 +16,9 @@ import { CONTEXT_RELATIVE_PATHS } from "../context-paths.js";
16
16
  /** Vocabulary understood by `review-context.ts:reviewFlowsMatch`. */
17
17
  const REVIEW_FLOW_VOCAB = new Set([
18
18
  "all",
19
+ "activity-scan",
20
+ // Legacy token for the activity-scan flow (the agent was "Hourly Check"
21
+ // until v0.1.11); kept so pre-rename `_index.md` rows stay valid.
19
22
  "hourly",
20
23
  "morning",
21
24
  "evening",
@@ -205,7 +208,7 @@ export function renderReconcilerBlockBody(rows, updated) {
205
208
  lines.push("## Notes");
206
209
  lines.push("");
207
210
  lines.push("- Maintained by the daemon reconciler (`packages/daemon/src/core/context/index-reconciler.ts`).");
208
- lines.push("- `Review flows` tokens: `all`, `hourly`, `morning`, `evening`, `weekly`,");
211
+ lines.push("- `Review flows` tokens: `all`, `activity-scan`, `morning`, `evening`, `weekly`,");
209
212
  lines.push(" `monthly`, `roadmap`, or `-` when no flow should auto-load the file.");
210
213
  return lines.join("\n");
211
214
  }
@@ -271,7 +274,7 @@ function defaultCells(entry) {
271
274
  if (path === CONTEXT_RELATIVE_PATHS.today) {
272
275
  return emit({
273
276
  purpose: "Current-day schedule, tasks, agent plan, handoff",
274
- reviewFlows: "hourly, morning, evening",
277
+ reviewFlows: "activity-scan, morning, evening",
275
278
  });
276
279
  }
277
280
  if (path === CONTEXT_RELATIVE_PATHS.yesterday) {
@@ -25,10 +25,12 @@ export interface PolicySnapshotEntry {
25
25
  slug: string;
26
26
  status: PolicyStatus;
27
27
  /**
28
- * Cron expression read from the linked `policies/routines/custom/<slug>.md`'s
29
- * frontmatter. Null when no routine is linked or the routine file is
30
- * missing. Frozen at snapshot time — manual cron edits to the routine
31
- * propagate on the next reconcile pass.
28
+ * Cron expression read from the linked execution vehicle: the Agent's
29
+ * `policies/agents/<slug>/agent.md` `schedule.expression`, falling back to
30
+ * a legacy `policies/routines/custom/<slug>.md` `cron` (inert
31
+ * pre-migration files). Null when nothing is linked or neither file
32
+ * resolves. Frozen at snapshot time — edits propagate on the next
33
+ * reconcile pass.
32
34
  */
33
35
  cadence: string | null;
34
36
  /** Slug from `linked.routine` frontmatter, or null. */