@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
@@ -42,10 +42,13 @@ import { isAgentTaskEvent, isDocsQAMessage, isMessageEvent, isRoutineEvent, isSc
42
42
  import { getContextDir } from "../config.js";
43
43
  import { finalizeRetemplate } from "./template-store.js";
44
44
  import { recordManagementInitDone, recordManagementScan, } from "../db/repositories-store.js";
45
- import { formatForwardSuffix, getProactiveForwardType, isProactiveForwardMetadata, parseMessageMetadata, recordProactiveForwardDeliveries, } from "./channel-timeline.js";
45
+ import { formatForwardSuffix, isProactiveForwardMetadata, parseMessageMetadata, recordProactiveForwardDeliveries, } from "./channel-timeline.js";
46
46
  import { randomUUID } from "node:crypto";
47
+ import { statSync } from "node:fs";
48
+ import { join } from "node:path";
47
49
  import { OWNER_DM_SCOPE, OWNER_SCOPE_KEY, DASHBOARD_CHAT_SCOPE, DASHBOARD_SCOPE_KEY, getConversationScope, } from "../messaging/constants.js";
48
50
  import { readEventReplyTarget } from "./wiki/dispatcher.js";
51
+ import { TASK_DELIVERY_ATTACHMENTS_KEY } from "./dispatcher-task-delivery.js";
49
52
  import { createLogger } from "../logging.js";
50
53
  import { assertOutboundAllowedForAgent, OutboundPurchaseTemplateError, } from "../safety/outbound-purchase-guard.js";
51
54
  const logger = createLogger("dispatcher-result");
@@ -138,6 +141,16 @@ export class ResultProcessor {
138
141
  threadId: explicitReply.threadId ?? null,
139
142
  };
140
143
  }
144
+ // BACKGROUND_TASK_RUNNER_DESIGN.md Phase 1 (delivery assets) — the
145
+ // task.delivery active turn is a no-tool DM turn, so the daemon (not
146
+ // the agent) attaches any deliverable files the task produced. The
147
+ // delivery handler resolved them and stashed the refs on the
148
+ // synthetic event; carry them onto the woven reply send. Only ever
149
+ // populated for the synthetic scheduled.dm delivery event.
150
+ const deliveryAttachments = readTaskDeliveryAttachments(event);
151
+ if (deliveryAttachments.length > 0 && explicitReply) {
152
+ sendOptions.attachments = deliveryAttachments;
153
+ }
141
154
  // MANAGED_CHROMIUM_IMPLEMENTATION_PLAN.md §17.7 structural-anti-
142
155
  // spoofing layer. The LLM's outbound text reaches the messaging
143
156
  // adapter via this notificationMgr.send chokepoint; refuse any
@@ -189,6 +202,7 @@ export class ResultProcessor {
189
202
  // the send itself, so wrap in try/catch.
190
203
  if (explicitReply && output.length > 0) {
191
204
  try {
205
+ const taskDeliveryRecord = readTaskDeliveryRecord(event);
192
206
  recordProactiveForwardDeliveries({
193
207
  db: this.db,
194
208
  config: this.config,
@@ -203,7 +217,10 @@ export class ResultProcessor {
203
217
  ...(options.originSessionId !== undefined
204
218
  ? { originSessionIds: [options.originSessionId] }
205
219
  : {}),
206
- notificationType: "proactive_forward",
220
+ notificationType: taskDeliveryRecord?.notificationType ?? "proactive_forward",
221
+ ...(taskDeliveryRecord
222
+ ? { extraMetadata: taskDeliveryRecord.metadata }
223
+ : {}),
207
224
  });
208
225
  }
209
226
  catch (err) {
@@ -213,6 +230,24 @@ export class ResultProcessor {
213
230
  }
214
231
  }
215
232
  }
233
+ // RESEARCH_CLUSTER_COST_FIX_PLAN F5 — outcome verification. The
234
+ // routine.research_cluster_update flow's ONLY deliverable is the
235
+ // per-cluster journal at context/research/<slug>.md. The 2026-06-11
236
+ // incident showed the flow ending "successfully" (no backend error,
237
+ // num_turns=1) while never writing the file — every "success" was a
238
+ // text-only narration. Upgrade "success" from "the session ended
239
+ // without a backend error" to "...AND the journal was written this
240
+ // run": a clean-but-unwritten run is recorded as `partial` /
241
+ // `journal_write_missing` (surfaced in the dashboard activity feed,
242
+ // `!report`, and `aitne audit --result partial`) rather than a
243
+ // misleading success. Gated to clean runs — a hard error already took
244
+ // the logError path with a more informative message.
245
+ const outcomeOverride = isRoutineEvent(event)
246
+ && event.routine === "research_cluster_update"
247
+ && !result.isError
248
+ && !this.researchClusterJournalWritten(event, result)
249
+ ? { result: "partial", error: "journal_write_missing" }
250
+ : undefined;
216
251
  this.audit.logAction({
217
252
  event,
218
253
  model: result.model,
@@ -226,6 +261,7 @@ export class ResultProcessor {
226
261
  costSource: result.costSource,
227
262
  contextUpdated: result.contextUpdated,
228
263
  advisorCallCount: result.advisorCallCount,
264
+ ...(outcomeOverride ?? {}),
229
265
  ...(options.dmFreshness ? { dmFreshness: options.dmFreshness } : {}),
230
266
  ...(options.dailyWrite ? { dailyWrite: options.dailyWrite } : {}),
231
267
  });
@@ -249,7 +285,7 @@ export class ResultProcessor {
249
285
  // - github.* (GitHub poller high-priority events)
250
286
  // - git.* (git watcher batched events)
251
287
  // - notion.* (notion poller)
252
- // - routine.hourly_check (Phase-9 polling sink for obsidian/git/notion)
288
+ // - routine.activity_scan (Phase-9 polling sink for obsidian/git/notion)
253
289
  if (this.isObserverEvent(event)) {
254
290
  logger.info({
255
291
  eventType: event.type,
@@ -450,13 +486,9 @@ export class ResultProcessor {
450
486
  if (message.role !== "assistant")
451
487
  return message.role;
452
488
  const metadata = parseMessageMetadata(message.metadata);
453
- const type = getProactiveForwardType(metadata);
454
- if (type === "scheduled_dm") {
455
- return "assistant (scheduled DM dispatched)";
456
- }
457
- if (type !== null) {
458
- return "assistant (forwarded from autonomous run)";
459
- }
489
+ const suffix = formatForwardSuffix(metadata);
490
+ if (suffix.length > 0)
491
+ return `assistant${suffix}`;
460
492
  return message.role;
461
493
  }
462
494
  buildCrossSessionConversationHistory(event) {
@@ -546,13 +578,91 @@ export class ResultProcessor {
546
578
  * contextUpdated observability log in processResult.
547
579
  */
548
580
  isObserverEvent(event) {
549
- return ((isRoutineEvent(event) && event.routine === "hourly_check") ||
581
+ return ((isRoutineEvent(event) && event.routine === "activity_scan") ||
550
582
  event.type.startsWith("calendar.") ||
551
583
  event.type === "schedule.approaching" ||
552
584
  event.type.startsWith("notion.") ||
553
585
  event.type.startsWith("github.") ||
554
586
  event.type.startsWith("git."));
555
587
  }
588
+ /**
589
+ * RESEARCH_CLUSTER_COST_FIX_PLAN F5 — was the cluster journal actually
590
+ * written during this `routine.research_cluster_update` run?
591
+ *
592
+ * Detection is the on-disk journal file itself — the authoritative,
593
+ * path-specific, backend-agnostic signal. The daemon writes the journal
594
+ * via the context-API chokepoint (`writeFileAtomically`), so the file's
595
+ * mtime reflects the real write time; a write that landed inside the run
596
+ * window (`[min(event enqueue, now − durationMs) − buffer, now]`) proves
597
+ * the agent executed the append rather than narrating success.
598
+ *
599
+ * Why not the `context_write` audit row (the plan's first idea): that
600
+ * row is emitted only inside `notifyPromptContextChanged`, which fires
601
+ * solely for prompt-refreshing paths (`shouldRefreshPromptContext`).
602
+ * `research/*` is intentionally a *quiet* namespace — it never triggers
603
+ * a refresh — so a research write records **no** `context_write` row.
604
+ * (The plan's §1 "zero context_write rows ⇒ zero writes" inference was
605
+ * therefore unsound for this namespace; the on-disk check has none of
606
+ * that coupling.) F1 guarantees ≤ 1 cluster_update run/cluster/day, so
607
+ * the run window cannot collide with another run writing the same file;
608
+ * the sibling research flows write *different* files
609
+ * (`<slug>-assistance-<date>.md`, `<slug>-wiki.md`), never `<slug>.md`.
610
+ *
611
+ * Returns `true` (treat as written — do NOT downgrade) when the slug is
612
+ * absent/invalid or the stat fails for any reason other than ENOENT: a
613
+ * verification fault must never relabel a genuinely-successful run.
614
+ */
615
+ researchClusterJournalWritten(event, result) {
616
+ const data = (event.data ?? {});
617
+ const slug = typeof data.slug === "string" ? data.slug : null;
618
+ // Defense in depth against path traversal even though the slug comes
619
+ // from the daemon's own cluster table — same sanitized character set
620
+ // the context-API whitelist enforces (PLACEHOLDER_SEGMENT_RE).
621
+ if (!slug || !/^[a-z0-9._-]+$/.test(slug)) {
622
+ logger.warn({ correlationId: event.correlationId, slug }, "research_cluster_update: missing/invalid slug — skipping journal-write verification");
623
+ return true;
624
+ }
625
+ // Resolve the dir WITHOUT the db argument, mirroring the context
626
+ // route's own `getCurrentContextDir` (api/routes/context/index.ts):
627
+ // with `db`, `getContextDir` falls back to the legacy
628
+ // `<dataDir>/context` while the vault is degraded — but the write
629
+ // route never writes there (degraded mode 503s every request), so
630
+ // statting the fallback could mislabel a run that wrote to the real
631
+ // vault just before degraded mode engaged. Always stat the path the
632
+ // agent's writes would have landed at.
633
+ const journalPath = join(getContextDir(this.config), "research", `${slug}.md`);
634
+ // Anchor the window at the event's enqueue time when it is older than
635
+ // `now − durationMs`: `durationMs` covers only the FINAL backend
636
+ // attempt, so a write made during an earlier `executeWithRetry`
637
+ // attempt (or before any dispatcher latency between run end and this
638
+ // check) would otherwise fall outside the window and downgrade a run
639
+ // that DID write. Widening backwards is safe — F1 guarantees at most
640
+ // one run per cluster per agent day and no sibling flow writes
641
+ // `<slug>.md`, so the only possible writer inside the widened window
642
+ // is this run.
643
+ const bufferMs = 5_000;
644
+ const eventCreatedMs = event.timestamp instanceof Date
645
+ ? event.timestamp.getTime()
646
+ : Date.parse(String(event.timestamp));
647
+ const runStartMs = Date.now() - (result.durationMs ?? 0);
648
+ const windowStartMs = (Number.isFinite(eventCreatedMs)
649
+ ? Math.min(eventCreatedMs, runStartMs)
650
+ : runStartMs) - bufferMs;
651
+ try {
652
+ return statSync(journalPath).mtimeMs >= windowStartMs;
653
+ }
654
+ catch (err) {
655
+ if (err.code === "ENOENT") {
656
+ // File never created — the strongest "not written" signal.
657
+ return false;
658
+ }
659
+ /* c8 ignore next 5 — non-ENOENT stat failure (e.g. EACCES) is not
660
+ * reproducible in the test harness; the conservative fall-through
661
+ * (treat as written) is asserted indirectly by the ENOENT path. */
662
+ logger.warn({ err, journalPath, correlationId: event.correlationId }, "research_cluster_update: journal stat failed — skipping verification");
663
+ return true;
664
+ }
665
+ }
556
666
  /**
557
667
  * Confirm that a `wiki.ingest_url` session actually wrote a raw note via
558
668
  * the Wiki API before letting the agent's claimed success DM reach the
@@ -620,3 +730,49 @@ export class ResultProcessor {
620
730
  return `Failed ${url ?? "<url>"} — agent reported completion but no raw note was POSTed via the Wiki API; the vault is unchanged.`;
621
731
  }
622
732
  }
733
+ export function readTaskDeliveryRecord(event) {
734
+ const raw = event.data
735
+ ?.task_delivery_record;
736
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
737
+ return null;
738
+ const record = raw;
739
+ if (record.notificationType !== "task_result"
740
+ && record.notificationType !== "task_clarification"
741
+ // Phase 4 autonomous-forward natural delivery: the active weave turn
742
+ // carries a task_delivery_record whose notificationType is
743
+ // "proactive_forward" (autonomous forwards have no task row, so they
744
+ // reuse the proactive_forward type). Its metadata still tags the woven
745
+ // message with taskKind/deliveredTaskId so `deliverActive`'s
746
+ // message-existence check can confirm the weave landed and skip the
747
+ // verbatim re-send. Without accepting it here the metadata is dropped,
748
+ // the existence check misses, and the owner receives BOTH the woven DM
749
+ // and a verbatim duplicate.
750
+ && record.notificationType !== "proactive_forward") {
751
+ return null;
752
+ }
753
+ const metadata = record.metadata
754
+ && typeof record.metadata === "object"
755
+ && !Array.isArray(record.metadata)
756
+ ? record.metadata
757
+ : {};
758
+ return {
759
+ notificationType: record.notificationType,
760
+ metadata,
761
+ };
762
+ }
763
+ /**
764
+ * Resolved deliverable-file refs the task.delivery handler stashed on the
765
+ * synthetic `scheduled.dm` event (BACKGROUND_TASK_RUNNER_DESIGN.md Phase 1
766
+ * — delivery assets). Returns `[]` for every ordinary event. Shape is
767
+ * validated loosely — a malformed entry is dropped rather than thrown so a
768
+ * bad ref never blocks the woven reply.
769
+ */
770
+ function readTaskDeliveryAttachments(event) {
771
+ const raw = event.data?.[TASK_DELIVERY_ATTACHMENTS_KEY];
772
+ if (!Array.isArray(raw))
773
+ return [];
774
+ return raw.filter((a) => !!a
775
+ && typeof a === "object"
776
+ && typeof a.path === "string"
777
+ && typeof a.id === "string");
778
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Dispatcher handler for `scheduled.background_task`
3
+ * (BACKGROUND_TASK_RUNNER_DESIGN.md §4.2).
4
+ *
5
+ * Lifts fire-time row creation + runner handoff out of `dispatcher.ts`,
6
+ * mirroring `dispatcher-scheduled-browser-task.ts` but with the whole
7
+ * allowlist / site-registry plane removed — a background task carries
8
+ * only a self-contained brief.
9
+ *
10
+ * Flow:
11
+ * 1. Re-validate the persisted `task_context` (a hand-crafted DB row
12
+ * cannot smuggle invalid fields through the scheduler boundary).
13
+ * 2. Dedup on `preGeneratedTaskId` (a scheduler/dispatcher restart
14
+ * between event.put and dispatch must not double-insert).
15
+ * 3. Insert the `background_task` row pinned to the pre-generated id.
16
+ * 4. Hand off to the runner. The runner's RunResult drives the
17
+ * `background_task` lifecycle from here; the `agent_schedule` row's
18
+ * status is independent (the dispatcher marks it completed on
19
+ * successful dispatch, failed on rejection).
20
+ */
21
+ import type Database from "better-sqlite3";
22
+ import type { ScheduledBackgroundTaskEvent } from "@aitne/shared";
23
+ import type { BackgroundTaskRunner } from "../services/background-task/background-task-runner.js";
24
+ export type ScheduledBackgroundTaskOutcome = {
25
+ kind: "dispatched";
26
+ taskId: string;
27
+ } | {
28
+ kind: "task_context_invalid";
29
+ reason: string;
30
+ } | {
31
+ kind: "row_already_exists";
32
+ taskId: string;
33
+ } | {
34
+ kind: "runner_unavailable";
35
+ taskId: string;
36
+ };
37
+ export interface BackgroundTaskDispatchDeps {
38
+ db: Database.Database;
39
+ runner: BackgroundTaskRunner | null;
40
+ nowFn?: () => number;
41
+ }
42
+ export declare function handleScheduledBackgroundTask(deps: BackgroundTaskDispatchDeps, event: ScheduledBackgroundTaskEvent): Promise<ScheduledBackgroundTaskOutcome>;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Dispatcher handler for `scheduled.background_task`
3
+ * (BACKGROUND_TASK_RUNNER_DESIGN.md §4.2).
4
+ *
5
+ * Lifts fire-time row creation + runner handoff out of `dispatcher.ts`,
6
+ * mirroring `dispatcher-scheduled-browser-task.ts` but with the whole
7
+ * allowlist / site-registry plane removed — a background task carries
8
+ * only a self-contained brief.
9
+ *
10
+ * Flow:
11
+ * 1. Re-validate the persisted `task_context` (a hand-crafted DB row
12
+ * cannot smuggle invalid fields through the scheduler boundary).
13
+ * 2. Dedup on `preGeneratedTaskId` (a scheduler/dispatcher restart
14
+ * between event.put and dispatch must not double-insert).
15
+ * 3. Insert the `background_task` row pinned to the pre-generated id.
16
+ * 4. Hand off to the runner. The runner's RunResult drives the
17
+ * `background_task` lifecycle from here; the `agent_schedule` row's
18
+ * status is independent (the dispatcher marks it completed on
19
+ * successful dispatch, failed on rejection).
20
+ */
21
+ import { z } from "zod";
22
+ import { createBackgroundTask, getBackgroundTask, markTerminal, } from "../db/background-task-store.js";
23
+ import { createLogger } from "../logging.js";
24
+ const logger = createLogger("dispatcher-scheduled-background-task");
25
+ const taskContextSchema = z.object({
26
+ preGeneratedTaskId: z.string().uuid(),
27
+ brief: z.string().min(1).max(16_384),
28
+ title: z.string().min(1).max(200).nullable().optional(),
29
+ notificationPolicy: z
30
+ .enum(["always", "if_significant", "silent"])
31
+ .optional(),
32
+ significanceCriteria: z
33
+ .array(z.string().min(1).max(500))
34
+ .max(12)
35
+ .nullable()
36
+ .optional(),
37
+ tier: z.enum(["lite", "medium", "high"]).nullable().optional(),
38
+ maxBudgetUsd: z.number().positive().max(15).nullable().optional(),
39
+ originatingChannel: z.string().nullable().optional(),
40
+ });
41
+ export async function handleScheduledBackgroundTask(deps, event) {
42
+ const now = deps.nowFn ?? (() => Date.now());
43
+ const parsed = taskContextSchema.safeParse(event.taskContext);
44
+ if (!parsed.success) {
45
+ const reason = parsed.error.issues
46
+ .slice(0, 3)
47
+ .map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`)
48
+ .join("; ");
49
+ logger.error({ scheduleId: event.scheduleId, reason }, "scheduled.background_task: task_context failed schema validation");
50
+ return { kind: "task_context_invalid", reason };
51
+ }
52
+ const ctx = parsed.data;
53
+ const existing = getBackgroundTask(deps.db, ctx.preGeneratedTaskId);
54
+ if (existing) {
55
+ logger.warn({ taskId: ctx.preGeneratedTaskId, scheduleId: event.scheduleId, state: existing.state }, "scheduled.background_task: row already exists for preGeneratedTaskId — skipping re-dispatch");
56
+ return { kind: "row_already_exists", taskId: ctx.preGeneratedTaskId };
57
+ }
58
+ createBackgroundTask(deps.db, {
59
+ id: ctx.preGeneratedTaskId,
60
+ brief: ctx.brief,
61
+ title: ctx.title ?? null,
62
+ notificationPolicy: ctx.notificationPolicy ?? "always",
63
+ significanceCriteria: ctx.significanceCriteria ?? null,
64
+ originatingChannel: ctx.originatingChannel ?? null,
65
+ correlationId: event.correlationId ?? null,
66
+ scheduleRowId: event.scheduleId,
67
+ tier: ctx.tier ?? null,
68
+ maxBudgetUsd: ctx.maxBudgetUsd ?? null,
69
+ createdAt: now(),
70
+ });
71
+ if (!deps.runner) {
72
+ markTerminal(deps.db, {
73
+ id: ctx.preGeneratedTaskId,
74
+ state: "failed",
75
+ outcomeDetail: "runner_unavailable",
76
+ finishedAt: now(),
77
+ report: "The background-task runner was not wired at fire time.",
78
+ draft: "That scheduled task couldn't start — the runner was unavailable.",
79
+ notify: true,
80
+ });
81
+ logger.warn({ taskId: ctx.preGeneratedTaskId, scheduleId: event.scheduleId }, "scheduled.background_task: runner not wired — marked failed (runner_unavailable)");
82
+ return { kind: "runner_unavailable", taskId: ctx.preGeneratedTaskId };
83
+ }
84
+ void deps.runner.runFromScheduleRow(ctx.preGeneratedTaskId).catch((err) => {
85
+ logger.error({ err, taskId: ctx.preGeneratedTaskId, scheduleId: event.scheduleId }, "background-task runFromScheduleRow threw — task left in pending state");
86
+ });
87
+ logger.info({ taskId: ctx.preGeneratedTaskId, scheduleId: event.scheduleId }, "scheduled.background_task dispatched to runner");
88
+ return { kind: "dispatched", taskId: ctx.preGeneratedTaskId };
89
+ }
@@ -196,6 +196,29 @@ export declare const SKILL_CURATION_OPTIMIZER_ALLOWED_TOOLS: readonly ["Read", "
196
196
  * consciously.
197
197
  */
198
198
  export declare const REFRESH_ARCHITECTURE_ALLOWED_TOOLS: readonly ["Read", "Glob", "Grep", "Bash(curl http://localhost:8321/api/repositories/*/architecture-section*)", "Bash(jq *)"];
199
+ /**
200
+ * BACKGROUND_TASK_RUNNER_DESIGN.md §4.5 / §4.5-bis / Phase 4 — the active
201
+ * delivery turn is a NO-TOOL DM turn. Its sole job is to weave the
202
+ * already-injected artifact (`taskContext.task_delivery.report` carries the
203
+ * full verbatim result) into the live conversation; the agent's reply text
204
+ * IS the DM (recorded by the result processor) and any deliverable files
205
+ * are attached by the daemon, not by the agent. So the turn needs no tools
206
+ * at all — and an EMPTY override structurally prevents it from taking
207
+ * "further action" (spawning another task, writing memory, sending mail)
208
+ * during what should be a pure phrasing turn. Follow-up turns ("what did it
209
+ * find?") are ordinary DM turns and keep the full envelope, including the
210
+ * `GET /api/background-task/:id` read affordance.
211
+ */
212
+ export declare const TASK_DELIVERY_TURN_ALLOWED_TOOLS: readonly string[];
213
+ /**
214
+ * True when `taskCtx` is a synthetic `scheduled.dm` event minted by the
215
+ * task-delivery handler (`createScheduledDmDeliveryEvent`) — the only place
216
+ * that sets the `task_delivery` block. Used to pin the no-tool clamp above.
217
+ * Keying off this structural marker (not `event.source`) is fail-safe: the
218
+ * clamp only ever NARROWS the envelope, so a false positive degrades a turn
219
+ * to no-tool rather than widening anything.
220
+ */
221
+ export declare function isTaskDeliveryTurn(taskCtx: AgentTaskEvent["taskContext"]): boolean;
199
222
  /**
200
223
  * Backends that honor the per-execute `allowedToolsOverride` clamp end-to-
201
224
  * end. Claude consumes the list verbatim through the SDK's `dontAsk` +
@@ -207,6 +230,14 @@ export declare const REFRESH_ARCHITECTURE_ALLOWED_TOOLS: readonly ["Read", "Glob
207
230
  * envelope; the operator sees an `agent_actions` row of action_type
208
231
  * `scheduled_task_clamp_unsupported` and a clear log line.
209
232
  *
233
+ * Exception — the Phase-4 task-delivery turn (`isTaskDeliveryTurn`)
234
+ * consumes this same set but DEGRADES rather than refuses: when the
235
+ * resolved backend can't enforce `[]` it still runs the delivery turn
236
+ * with the default envelope (logged), because failing to deliver the
237
+ * owner's result is worse than running the phrasing turn untooled. The
238
+ * clamp is keyed off `binding.main.backendId`, so — as with the refuse
239
+ * path — a runtime fallback to a non-claude backend is not re-evaluated.
240
+ *
210
241
  * Add a backend here only after verifying its core threads
211
242
  * `allowedToolsOverride` through to its concrete deny enforcement layer
212
243
  * — NOT just into the CLI flag set.
@@ -227,7 +258,7 @@ export interface ScheduledTaskRunnerDeps {
227
258
  * in `ROUTINE_WINDOWS` (today_refresh / evening_review / weekly_review;
228
259
  * monthly_review is registered but has zero rows so the runner
229
260
  * short-circuits without dispatching a session). Idempotent against
230
- * the morning_routine + hourly_check paths: when the upstream
261
+ * the morning_routine + activity_scan paths: when the upstream
231
262
  * dispatcher already attached a `fetchReportBlock`, `executeDefault`
232
263
  * skips re-running the pre-pass.
233
264
  */
@@ -465,6 +496,37 @@ export declare class ScheduledTaskRunner {
465
496
  * in `buildFeedbackWorksheet`, which is 100% unit-tested.
466
497
  */
467
498
  private prepareFeedbackWorksheet;
499
+ /**
500
+ * SELF_TUNING_REVIEW_CYCLE_DESIGN.md §3.1 + §3.2 / Phases 1–2 — the
501
+ * deterministic Measure + Recommend pre-steps. Computes the 7-day window +
502
+ * 7-day-prior baseline SQL aggregates over `agent_actions` /
503
+ * `notification_log` / the `runtime_state.self_tuning:*` ledger, reads each
504
+ * lesson store's byte pressure (§3.5), and composes the
505
+ * `<self_performance>` block via the pure
506
+ * `core/feedback/self-performance-prep.ts` module. The same gathered data
507
+ * then feeds the Phase 2 rule table (`core/feedback/tuning-recommender.ts`):
508
+ * the resulting pending cycle is persisted to
509
+ * `runtime_state.self_tuning.pending_cycle` — overwriting (and thereby
510
+ * expiring, §3.4 single-use ids) the previous cycle even when this week
511
+ * produced zero recommendations — and rendered as the
512
+ * `<tuning_recommendations>` block for the Phase 3c verdict step.
513
+ *
514
+ * Either field is `null` when there is nothing to inject or its step threw —
515
+ * the caller simply skips stamping and the weekly review proceeds
516
+ * unchanged. The Recommend step is failure-isolated from the Measure step:
517
+ * a recommender throw never drops the `<self_performance>` block.
518
+ *
519
+ * The DB handle + lesson-file FS reads live here (the dispatcher is
520
+ * coverage-excluded); the byte-deterministic aggregation, rule table, and
521
+ * rendering live in the pure modules, which are 100% unit-tested. Unlike
522
+ * the consolidation/re-generalization pre-steps this is NOT gated on
523
+ * `feedbackLearningEnabled` — it measures core daemon telemetry, not the
524
+ * lesson loop; nor on `selfTuningEnabled` — that flag gates Phase 3
525
+ * *actuation* only, while recommendation generation + verdict recording IS
526
+ * the Phase 2 shadow period (§7). An unreadable lesson store only drops the
527
+ * `<lesson_stores>` rows / the R5 input, never the whole block.
528
+ */
529
+ private prepareSelfTuningBlocks;
468
530
  /**
469
531
  * FEEDBACK_LEARNING_LOOP_DESIGN.md §4 "Monthly re-generalization" / Phase 5 —
470
532
  * the deterministic monthly pre-step. Enumerates the consolidated lesson