@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
@@ -224,7 +224,7 @@ function recordCoalescedObservation(db, params) {
224
224
  // novelty_score populated. Without this, coalesced inserts only get
225
225
  // summarized via the daemon-startup reclaim sweep — a row that lands
226
226
  // mid-run sits at `summary_status='pending'` forever and the
227
- // hourly_check skill is forced into the legacy fetch-on-doubt path.
227
+ // activity_scan skill is forced into the legacy fetch-on-doubt path.
228
228
  if (inserted)
229
229
  notifyObservationSummarizer(inserted.id);
230
230
  return 1;
@@ -257,7 +257,7 @@ function recordCoalescedObservation(db, params) {
257
257
  // summary describes obsolete content. Reset the summarizer-owned columns
258
258
  // to mirror `recordObservation`'s UPSERT on payload change — without
259
259
  // this, `summary_text` / `novelty_score` linger from the prior payload
260
- // and the hourly_check skill consumes a stale summary as if it were
260
+ // and the activity_scan skill consumes a stale summary as if it were
261
261
  // current (`summary_status='done'` with `summaryStale=false`).
262
262
  db.prepare(`UPDATE observations
263
263
  SET change_type = ?,
@@ -99,10 +99,14 @@ function round2(value) {
99
99
  function storeFileAttr(scope) {
100
100
  return scopeStoreFile(scope) ?? "";
101
101
  }
102
- /** Collapse a one-line excerpt of a signal/lesson for an XML text node. */
102
+ /** Collapse a one-line excerpt of a signal/lesson for an XML text node.
103
+ * The clip strips a trailing lone high surrogate so cutting through an
104
+ * astral char (emoji) can't leave a U+FFFD in the worksheet. */
103
105
  function inline(text, max = 300) {
104
106
  const flat = text.replace(/\s+/g, " ").trim();
105
- const clipped = flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
107
+ const clipped = flat.length > max
108
+ ? `${flat.slice(0, max - 1).replace(/[\uD800-\uDBFF]$/, "")}…`
109
+ : flat;
106
110
  return xmlEscape(clipped);
107
111
  }
108
112
  /** Authority ranking for picking a candidate's representative `src=` trailer. */
@@ -154,12 +158,20 @@ function renderLessonsScope(input, opts, out) {
154
158
  ? extractMarkdownSection(input.existingFileMd, "Lessons")
155
159
  : null;
156
160
  const existing = sectionBody ? parseLessonsSection(sectionBody) : [];
157
- const currentBytes = input.existingFileMd
158
- ? Buffer.byteLength(input.existingFileMd, "utf-8")
161
+ // `current_bytes` measures the on-disk `## Lessons` SECTION body — the
162
+ // §6 cap unit (`lessonsSectionByteLength` in lesson-format.ts), the same
163
+ // unit `enforceCaps` below derives `over_cap` from. Measuring the whole
164
+ // file here while `over_cap` measured the section produced a
165
+ // self-contradictory tag (and disagreed with the §6 unit the eviction
166
+ // engine actually controls).
167
+ const currentBytes = sectionBody
168
+ ? Buffer.byteLength(sectionBody, "utf-8")
159
169
  : 0;
160
170
  // Eviction ranking: ascending score → rank 1 = evict-first. The plan
161
171
  // (post-dedupe) tells the LLM whether the store is already over cap.
162
- const plan = enforceCaps(existing, { maxBytes: input.caps.capBytes, maxEntries: input.caps.maxEntries }, opts.nowIso, { scopeLabel: label });
172
+ // Pass the same half-life the displayed `ranked` scores use so the plan
173
+ // and the ranking can never derive from two different scorings.
174
+ const plan = enforceCaps(existing, { maxBytes: input.caps.capBytes, maxEntries: input.caps.maxEntries }, opts.nowIso, { scopeLabel: label }, undefined, halfLife);
163
175
  const ranked = [...existing].sort((a, b) => scoreLesson(a, opts.nowIso, undefined, halfLife) -
164
176
  scoreLesson(b, opts.nowIso, undefined, halfLife));
165
177
  out.push(` <scope label="${xmlEscape(label)}" store="${xmlEscape(storeFile)}" ` +
@@ -103,9 +103,13 @@ export function enforceCaps(lessons, cap, nowIso, opts, weights = DEFAULT_EVICTI
103
103
  scoreLesson(a, nowIso, weights, halfLifeDays));
104
104
  const evicted = [];
105
105
  let keep = sorted;
106
- // Entry cap first — cheap, and shrinks the byte-cap work.
106
+ // Entry cap first — cheap, and shrinks the byte-cap work. Reverse the
107
+ // overflow slice (it comes off the descending-sorted array) so `evicted`
108
+ // honours its documented lowest-scored-first order; the byte-cap loop
109
+ // below already pushes lowest-first, so the combined array stays
110
+ // ascending by score.
107
111
  if (keep.length > cap.maxEntries) {
108
- evicted.push(...keep.slice(cap.maxEntries));
112
+ evicted.push(...keep.slice(cap.maxEntries).reverse());
109
113
  keep = keep.slice(0, cap.maxEntries);
110
114
  }
111
115
  const sectionOpts = {
@@ -152,13 +152,18 @@ export function parseLessonsSection(sectionBody) {
152
152
  flush();
153
153
  continue;
154
154
  }
155
- if (current && (line.startsWith(" ") || line.trim().length > 0)) {
156
- // Continuation of the current lesson (indented, or a trailer comment
157
- // the author placed on its own non-indented line).
155
+ if (current &&
156
+ (line.startsWith(" ") || line.trim().startsWith("<!--"))) {
157
+ // Continuation of the current lesson: indented prose, or a trailer
158
+ // comment the author placed on its own non-indented line.
158
159
  current.buffer.push(line.trim());
159
160
  }
160
161
  else if (current) {
161
- // Blank line ends the current lesson.
162
+ // Blank line or non-indented stray prose ends the current lesson.
163
+ // The stray line itself is ignored (module contract: non-lesson
164
+ // lines are ignored) — folding it into the preceding lesson would
165
+ // absorb a hand-written note into an injectable directive and
166
+ // re-serialize it permanently on the next consolidation.
162
167
  flush();
163
168
  }
164
169
  }
@@ -37,7 +37,7 @@
37
37
  */
38
38
  /**
39
39
  * Hard inject-time byte cap for the slim hourly notify-discipline variant (§6
40
- * table: "slim notify-discipline subset injected to hourly_check · hard 2048 at
40
+ * table: "slim notify-discipline subset injected to activity_scan · hard 2048 at
41
41
  * inject"). Exported so the builder and tests share one constant.
42
42
  */
43
43
  export declare const AGENT_LESSONS_SLIM_CAP_BYTES = 2048;
@@ -39,7 +39,7 @@ import { extractMarkdownSection, parseLessonsSection, } from "./lesson-format.js
39
39
  import { scoreLesson } from "./eviction-scorer.js";
40
40
  /**
41
41
  * Hard inject-time byte cap for the slim hourly notify-discipline variant (§6
42
- * table: "slim notify-discipline subset injected to hourly_check · hard 2048 at
42
+ * table: "slim notify-discipline subset injected to activity_scan · hard 2048 at
43
43
  * inject"). Exported so the builder and tests share one constant.
44
44
  */
45
45
  export const AGENT_LESSONS_SLIM_CAP_BYTES = 2048;
@@ -69,9 +69,24 @@ function activeLessons(fileMd) {
69
69
  return [];
70
70
  return parseLessonsSection(section).filter((lesson) => !lesson.provisional && lesson.text.length > 0);
71
71
  }
72
+ /**
73
+ * Neutralise XML-tag breakout in lesson prose. Lesson text is
74
+ * LLM-synthesized during consolidation from signals that can carry
75
+ * untrusted excerpts, and the rendered block is framed to the model as
76
+ * standing directives — a body containing `</agent_lessons>` must not be
77
+ * able to close the wrapper and forge a sibling high-trust block. Same
78
+ * escape set the worksheet builders' `xmlEscape` applies to the same text
79
+ * on the consolidation side (`consolidation-prep.ts`).
80
+ */
81
+ function escapeLessonText(text) {
82
+ return text
83
+ .replace(/&/g, "&amp;")
84
+ .replace(/</g, "&lt;")
85
+ .replace(/>/g, "&gt;");
86
+ }
72
87
  /** One lesson as an agent-facing bullet (trailer + date stripped). */
73
88
  function bulletFor(lesson) {
74
- return `- ${lesson.text}`;
89
+ return `- ${escapeLessonText(lesson.text)}`;
75
90
  }
76
91
  function wrap(style, bullets) {
77
92
  return [style.openTag, style.preamble, ...bullets, "</agent_lessons>"].join("\n");
@@ -13,7 +13,7 @@
13
13
  * (here, 100% covered), while the route owns only the FS read + JSON assembly.
14
14
  */
15
15
  export interface LessonStoreSummary {
16
- /** UTF-8 byte size of the whole file (the cap unit, §6). */
16
+ /** UTF-8 byte size of the on-disk `## Lessons` section (the cap unit, §6). */
17
17
  bytes: number;
18
18
  /** Per-scope byte cap. */
19
19
  capBytes: number;
@@ -32,9 +32,13 @@ export interface LessonStoreSummary {
32
32
  * Summarise one lesson store from its raw file contents. A file with no
33
33
  * `## Lessons` section (or an empty one) reports zero entries — never throws,
34
34
  * so a hand-edited or partially-written file degrades to "empty store" rather
35
- * than breaking the overview. `bytes` is measured against the *whole* file
36
- * because that is what the consolidation cap and the eviction scorer both
37
- * guard, matching what lands on disk.
35
+ * than breaking the overview. `bytes` measures the on-disk `## Lessons`
36
+ * section body the §6 cap unit (`lessonsSectionByteLength` in
37
+ * lesson-format.ts) the eviction scorer and the nightly worksheet's
38
+ * `over_cap` enforce. Measuring the whole file here previously reported a
39
+ * permanently-stuck `overCap: true` in the band where the section fit the
40
+ * cap but frontmatter + heading overhead pushed the file past it — a state
41
+ * no enforcement actor would ever clear.
38
42
  */
39
43
  export declare function summarizeLessonStore(fileMd: string, caps: {
40
44
  capBytes: number;
@@ -17,15 +17,19 @@ import { extractMarkdownSection, parseLessonsSection, } from "./lesson-format.js
17
17
  * Summarise one lesson store from its raw file contents. A file with no
18
18
  * `## Lessons` section (or an empty one) reports zero entries — never throws,
19
19
  * so a hand-edited or partially-written file degrades to "empty store" rather
20
- * than breaking the overview. `bytes` is measured against the *whole* file
21
- * because that is what the consolidation cap and the eviction scorer both
22
- * guard, matching what lands on disk.
20
+ * than breaking the overview. `bytes` measures the on-disk `## Lessons`
21
+ * section body the §6 cap unit (`lessonsSectionByteLength` in
22
+ * lesson-format.ts) the eviction scorer and the nightly worksheet's
23
+ * `over_cap` enforce. Measuring the whole file here previously reported a
24
+ * permanently-stuck `overCap: true` in the band where the section fit the
25
+ * cap but frontmatter + heading overhead pushed the file past it — a state
26
+ * no enforcement actor would ever clear.
23
27
  */
24
28
  export function summarizeLessonStore(fileMd, caps) {
25
29
  const sectionBody = extractMarkdownSection(fileMd, "Lessons");
26
30
  const lessons = sectionBody ? parseLessonsSection(sectionBody) : [];
27
31
  const provisional = lessons.filter((lesson) => lesson.provisional).length;
28
- const bytes = Buffer.byteLength(fileMd, "utf-8");
32
+ const bytes = sectionBody ? Buffer.byteLength(sectionBody, "utf-8") : 0;
29
33
  return {
30
34
  bytes,
31
35
  capBytes: caps.capBytes,
@@ -54,25 +54,32 @@ function xmlEscape(value) {
54
54
  function round2(value) {
55
55
  return (Math.round(value * 100) / 100).toFixed(2);
56
56
  }
57
- /** Collapse a one-line excerpt of a lesson for an XML text node. */
57
+ /** Collapse a one-line excerpt of a lesson for an XML text node.
58
+ * The clip strips a trailing lone high surrogate so cutting through an
59
+ * astral char (emoji) can't leave a U+FFFD in the worksheet. */
58
60
  function inline(text, max = 300) {
59
61
  const flat = text.replace(/\s+/g, " ").trim();
60
- const clipped = flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
62
+ const clipped = flat.length > max
63
+ ? `${flat.slice(0, max - 1).replace(/[\uD800-\uDBFF]$/, "")}…`
64
+ : flat;
61
65
  return xmlEscape(clipped);
62
66
  }
63
- function parseScopeLessons(existingFileMd) {
64
- const sectionBody = extractMarkdownSection(existingFileMd, "Lessons");
65
- return sectionBody ? parseLessonsSection(sectionBody) : [];
66
- }
67
- function renderScope(input, activeLessons, totalEntries, opts, out) {
67
+ function renderScope(input, sectionBody, activeLessons, totalEntries, opts, out) {
68
68
  const label = formatScope(input.scope);
69
69
  const section = scopeSectionSlug(input.scope);
70
70
  const halfLife = opts.recencyHalfLifeDays ?? DEFAULT_RECENCY_HALFLIFE_DAYS;
71
- const currentBytes = Buffer.byteLength(input.existingFileMd, "utf-8");
72
- // `current_bytes` / `current_entries` describe the WHOLE on-disk file
73
- // (active + provisional), because that is exactly what the byte/entry caps
74
- // guard (§6). `over_cap` therefore reflects the real file state, not the
75
- // collapsible subset the LLM's Step-12 eviction targets the disk cap.
71
+ // `current_bytes` / `current_entries` describe the on-disk `## Lessons`
72
+ // SECTION (active + provisional) the §6 cap unit
73
+ // (`lessonsSectionByteLength` in lesson-format.ts), the same unit the
74
+ // nightly worksheet's `over_cap` and the eviction engine measure. The
75
+ // whole-file measure used previously disagreed with the nightly pass in a
76
+ // narrow band (frontmatter + `# heading` overhead), making the two
77
+ // worksheets contradict each other on the same store. `over_cap` covers
78
+ // the full entry set, not just the collapsible active subset — the LLM's
79
+ // Step-12 eviction targets the disk cap. The caller extracted
80
+ // `sectionBody` once during eligibility (a scope is only eligible when
81
+ // its `## Lessons` section parsed), so it is measured here verbatim.
82
+ const currentBytes = Buffer.byteLength(sectionBody, "utf-8");
76
83
  const overCap = currentBytes > input.caps.capBytes || totalEntries > input.caps.maxEntries;
77
84
  const provisionalHeld = totalEntries - activeLessons.length;
78
85
  // Ascending score → rank 1 = lowest score = drop-first, the same convention
@@ -111,13 +118,19 @@ function renderScope(input, activeLessons, totalEntries, opts, out) {
111
118
  export function buildRegeneralizationWorksheet(scopes, opts) {
112
119
  const eligible = [];
113
120
  for (const input of scopes) {
114
- const allLessons = parseScopeLessons(input.existingFileMd);
121
+ // Single extraction per scope — `renderScope` reuses this body for its
122
+ // `current_bytes` measure instead of re-extracting (the old double
123
+ // extraction left renderScope with an unreachable missing-section arm).
124
+ const sectionBody = extractMarkdownSection(input.existingFileMd, "Lessons");
125
+ if (!sectionBody)
126
+ continue;
127
+ const allLessons = parseLessonsSection(sectionBody);
115
128
  // Collapse the ACTIVE set only — provisional lessons are owned by the
116
129
  // evening promotion gate (see module header); merging them here would
117
130
  // bypass the `ignored`-only-never-promotes guard.
118
131
  const active = allLessons.filter((lesson) => !lesson.provisional);
119
132
  if (active.length >= MIN_LESSONS_FOR_REGENERALIZATION) {
120
- eligible.push({ input, active, totalEntries: allLessons.length });
133
+ eligible.push({ input, sectionBody, active, totalEntries: allLessons.length });
121
134
  }
122
135
  }
123
136
  if (eligible.length === 0)
@@ -126,8 +139,8 @@ export function buildRegeneralizationWorksheet(scopes, opts) {
126
139
  out.push(`<feedback_regeneralization generated_at="${xmlEscape(opts.nowIso)}" ` +
127
140
  `scopes="${eligible.length}">`);
128
141
  let lessonCount = 0;
129
- for (const { input, active, totalEntries } of eligible) {
130
- renderScope(input, active, totalEntries, opts, out);
142
+ for (const { input, sectionBody, active, totalEntries } of eligible) {
143
+ renderScope(input, sectionBody, active, totalEntries, opts, out);
131
144
  lessonCount += active.length;
132
145
  }
133
146
  out.push("</feedback_regeneralization>");
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Self-Tuning Review Cycle — Measure stage (SELF_TUNING_REVIEW_CYCLE_DESIGN.md
3
+ * §3.1, Phase 1).
4
+ *
5
+ * The daemon-side, deterministic Measure step ($0 — LLM tokens buy judgment
6
+ * only, P1). On the weekly-review dispatch it computes SQL aggregates over
7
+ * `agent_actions`, `notification_log`, and the `runtime_state` self-tuning
8
+ * ledger for a 7-day window plus a 7-day-prior baseline (trend column), and
9
+ * renders one compact `<self_performance>` block — hard-capped at
10
+ * {@link SELF_PERFORMANCE_MAX_BYTES} — so the weekly review's "Metrics (agent
11
+ * side)" section copies daemon-computed facts instead of paying Sonnet prices
12
+ * to re-count them.
13
+ *
14
+ * Two layers, mirroring `consolidation-prep.ts`:
15
+ * - {@link gatherSelfPerformanceData} — the single DB read (side-effect
16
+ * free): per-`action_type` run/cost/duration aggregates (`agent_actions`
17
+ * has no process_key column; `action_type` carries the routine identity),
18
+ * the `routine.fetch_window` empty-run rate per integration (from the
19
+ * fan-out audit rows' `detail.prePass` payload the runner persists), the
20
+ * `activity_scan.gate` stage distribution (from `buildGateAuditDetail`'s
21
+ * historical per-tick rows), per-notification-type `user_reaction`
22
+ * breakdowns (the first reader of the column `signal-detector.ts`
23
+ * populates), and the `runtime_state.self_tuning:*` ledger.
24
+ * - {@link buildSelfPerformanceBlock} — pure renderer. Deterministic
25
+ * byte-capped output: per-section row budgets shrink one row at a time
26
+ * (largest section first) until the block fits the cap, and clipped rows
27
+ * surface as `omitted="N"` so truncation is never silent.
28
+ *
29
+ * Phase 1 carries no actuator: nothing here writes config, schedules, or
30
+ * lessons. Later phases (Recommend / Judge / Actuate) consume the same data
31
+ * shape; {@link SELF_TUNING_LEDGER_PREFIX} is exported so the Phase 3
32
+ * actuator writes the ledger keys this module already reads.
33
+ */
34
+ import type Database from "better-sqlite3";
35
+ /** Measurement window length; the baseline is the same span immediately prior. */
36
+ export declare const SELF_PERFORMANCE_WINDOW_DAYS = 7;
37
+ /** §3.1 — hard cap on the rendered `<self_performance>` block, in UTF-8 bytes. */
38
+ export declare const SELF_PERFORMANCE_MAX_BYTES = 1500;
39
+ /**
40
+ * §3.4 ledger key prefix. Phase 3's actuator writes
41
+ * `runtime_state.self_tuning:<key> = {prev, applied_at, baselineMetric, rule}`;
42
+ * Phase 1 already reads (and renders) whatever sits under the prefix so the
43
+ * weekly review sees applied changes the cycle they land.
44
+ */
45
+ export declare const SELF_TUNING_LEDGER_PREFIX = "self_tuning:";
46
+ /** Fan-out audit rows carry the fetcher event's type as `action_type`. */
47
+ export declare const FETCH_WINDOW_ACTION_TYPE = "routine.fetch_window";
48
+ /** Per-tick gate audit rows (`buildGateAuditDetail` payload in `detail`). */
49
+ export declare const ACTIVITY_SCAN_GATE_ACTION_TYPE = "activity_scan.gate";
50
+ export interface ActionTypeStats {
51
+ actionType: string;
52
+ runs: number;
53
+ success: number;
54
+ partial: number;
55
+ failed: number;
56
+ skipped: number;
57
+ costUsd: number;
58
+ /** Median over rows with a non-null `duration_ms`; null when none have one. */
59
+ p50DurationMs: number | null;
60
+ }
61
+ export interface FetchWindowIntegrationStats {
62
+ integrationKey: string;
63
+ /** Completed fan-out attempts (prePass status success|partial). */
64
+ runs: number;
65
+ /** Completed attempts with `fetched=0 ∧ posted=0` (§3.1 empty-run def). */
66
+ empty: number;
67
+ }
68
+ export interface HourlyGateStats {
69
+ /** Every audited cron tick, including rows whose detail failed to parse. */
70
+ ticks: number;
71
+ stage0: number;
72
+ /**
73
+ * Lite-triage ticks that stayed silent. The writer persists the alias
74
+ * `stage2_log_only` verbatim (`dispatcher-activity-scan.ts:logGateAuditRow`
75
+ * overrides `stage_reached` with the applied decision, and a Stage-2 tick
76
+ * only ever settles to log_only-silent or stage3); a bare `stage2` is
77
+ * accepted defensively but never occurs in production rows.
78
+ */
79
+ stage2: number;
80
+ /**
81
+ * Full-session escalations that actually ran. Rows the legacy
82
+ * min-observations floor short-circuited (`result='skipped'` with
83
+ * `stage_reached='stage3'`) are excluded — no session ran, no spend.
84
+ */
85
+ stage3: number;
86
+ /**
87
+ * …of those, ticks whose `gate_reason` was the low-signal fallback (R3).
88
+ * Forced ticks (`!run` / run-now, `detail.forced=true`) are excluded:
89
+ * they escalate at any signal level and say nothing about the gate.
90
+ */
91
+ stage3LowSignal: number;
92
+ /** …of those, ticks whose snapshot max novelty was ≤ 1 (null counts as ≤1). */
93
+ stage3LowSignalLowNovelty: number;
94
+ }
95
+ export interface NotificationTypeStats {
96
+ notificationType: string;
97
+ /** Rows with status delivered|batched (suppressed/failed are not "sent"). */
98
+ sent: number;
99
+ replied: number;
100
+ acted: number;
101
+ corrected: number;
102
+ ignored: number;
103
+ /** No-reaction-yet (incl. unrecognised reaction values, clamped ≥ 0). */
104
+ pending: number;
105
+ }
106
+ export interface SelfPerformanceWindow {
107
+ actions: ActionTypeStats[];
108
+ fetchWindow: FetchWindowIntegrationStats[];
109
+ gate: HourlyGateStats;
110
+ notifications: NotificationTypeStats[];
111
+ }
112
+ export interface SelfTuningLedgerEntry {
113
+ /** Knob name — the runtime_state key with the prefix stripped. */
114
+ key: string;
115
+ prev: unknown;
116
+ appliedAt: string | null;
117
+ rule: string | null;
118
+ /**
119
+ * §3.4 — the rule's target metric captured at apply time, so the weekly
120
+ * review can compare it against the current `<self_performance>` numbers
121
+ * (the "measured effect" §3.1 asks the ledger section to carry). Absent
122
+ * until Phase 3's actuator writes it.
123
+ */
124
+ baselineMetric?: unknown;
125
+ /**
126
+ * §3.4 — stamped by the Phase 3 auto-revert monitor. Present means the
127
+ * change regressed and was rolled back; the Phase 2 recommender reads it
128
+ * to apply the extended 28-day cool-down (vs the normal 14-day
129
+ * hysteresis) so the apply→revert→re-apply cycle can't flap.
130
+ */
131
+ revertedAt?: string;
132
+ /**
133
+ * §3.4 — stamped by the auto-revert monitor after a clean 7-day verify
134
+ * window (`pass`, `no_baseline`, …). Rendered so the weekly judge can
135
+ * tell a verified-clean change from one still inside its window.
136
+ */
137
+ verifyResult?: string;
138
+ }
139
+ export interface SelfPerformanceData {
140
+ windowDays: number;
141
+ current: SelfPerformanceWindow;
142
+ baseline: SelfPerformanceWindow;
143
+ ledger: SelfTuningLedgerEntry[];
144
+ }
145
+ /** §3.5 — lesson-store byte pressure, the standing cost multiplier to watch. */
146
+ export interface LessonStoreUtilization {
147
+ /** Canonical scope label — `agent` or `agent:<slug>`. */
148
+ scope: string;
149
+ /** UTF-8 byte size of the `## Lessons` section body (the §6 cap unit). */
150
+ bytes: number;
151
+ capBytes: number;
152
+ entries: number;
153
+ /** Median `ev=` across all entries (R5's evidence signal); null when empty. */
154
+ medianEv: number | null;
155
+ }
156
+ /**
157
+ * The single DB read. Computes the current window `[now − windowDays, now)`
158
+ * and the baseline window `[now − 2·windowDays, now − windowDays)` over
159
+ * `agent_actions` / `notification_log` (both store SQLite UTC
160
+ * `YYYY-MM-DD HH:MM:SS` timestamps, so lexicographic comparison against
161
+ * `formatSqliteDatetime` cutoffs is exact), plus the self-tuning ledger.
162
+ */
163
+ export declare function gatherSelfPerformanceData(db: Database.Database, opts: {
164
+ now: Date;
165
+ windowDays?: number;
166
+ }): SelfPerformanceData;
167
+ /**
168
+ * §3.5 — summarise one lesson store's byte pressure from its raw file
169
+ * contents. Pure (the caller does the FS read); a file with no `## Lessons`
170
+ * section degrades to an empty store, never a throw. `medianEv` carries the
171
+ * evidence signal R5's Phase 2 rule keys on (utilization > 90% with median
172
+ * evidence ≤ 1), so the measurement lands one phase ahead of the rule.
173
+ */
174
+ export declare function summarizeLessonStoreUtilization(scope: string, fileMd: string, capBytes: number): LessonStoreUtilization;
175
+ /**
176
+ * Compose the `<self_performance>` block. Returns `null` when there is no
177
+ * telemetry at all (fresh install) so the caller stamps nothing — no empty
178
+ * block in the prompt. The byte cap is a hard guarantee: row budgets shrink
179
+ * until the block fits; in the (synthetic) case where even the skeleton
180
+ * exceeds the cap, a minimal self-closing element is emitted instead.
181
+ */
182
+ export declare function buildSelfPerformanceBlock(data: SelfPerformanceData, opts: {
183
+ generatedAt: string;
184
+ lessonStores?: ReadonlyArray<LessonStoreUtilization>;
185
+ maxBytes?: number;
186
+ }): string | null;