@ouro.bot/cli 0.1.0-alpha.49 → 0.1.0-alpha.491

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 (365) hide show
  1. package/README.md +133 -19
  2. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  4. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  5. package/changelog.json +3126 -0
  6. package/dist/arc/attention-types.js +8 -0
  7. package/dist/arc/cares.js +140 -0
  8. package/dist/arc/episodes.js +117 -0
  9. package/dist/arc/intentions.js +133 -0
  10. package/dist/arc/json-store.js +117 -0
  11. package/dist/arc/obligations.js +237 -0
  12. package/dist/arc/packets.js +193 -0
  13. package/dist/arc/presence.js +185 -0
  14. package/dist/arc/task-lifecycle.js +65 -0
  15. package/dist/heart/active-work.js +989 -0
  16. package/dist/heart/agent-entry.js +58 -3
  17. package/dist/heart/attachments/image-normalize.js +194 -0
  18. package/dist/heart/attachments/materialize.js +97 -0
  19. package/dist/heart/attachments/originals.js +88 -0
  20. package/dist/heart/attachments/render.js +29 -0
  21. package/dist/heart/attachments/sources/adapter.js +2 -0
  22. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  23. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  24. package/dist/heart/attachments/sources/index.js +16 -0
  25. package/dist/heart/attachments/store.js +103 -0
  26. package/dist/heart/attachments/types.js +93 -0
  27. package/dist/heart/auth/auth-flow.js +426 -0
  28. package/dist/heart/background-operations.js +281 -0
  29. package/dist/heart/bridges/manager.js +37 -0
  30. package/dist/heart/bridges/state-machine.js +20 -0
  31. package/dist/heart/bundle-state.js +168 -0
  32. package/dist/heart/commitments.js +111 -0
  33. package/dist/heart/config-registry.js +304 -0
  34. package/dist/heart/config.js +119 -129
  35. package/dist/heart/core.js +845 -229
  36. package/dist/heart/cross-chat-delivery.js +131 -0
  37. package/dist/heart/daemon/agent-config-check.js +490 -0
  38. package/dist/heart/daemon/agent-discovery.js +79 -3
  39. package/dist/heart/daemon/agent-service.js +360 -0
  40. package/dist/heart/daemon/agentic-repair.js +216 -0
  41. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  42. package/dist/heart/daemon/cadence.js +70 -0
  43. package/dist/heart/daemon/cli-defaults.js +640 -0
  44. package/dist/heart/daemon/cli-exec.js +7239 -0
  45. package/dist/heart/daemon/cli-help.js +493 -0
  46. package/dist/heart/daemon/cli-parse.js +1533 -0
  47. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  48. package/dist/heart/daemon/cli-render.js +561 -0
  49. package/dist/heart/daemon/cli-types.js +8 -0
  50. package/dist/heart/daemon/connect-bay.js +323 -0
  51. package/dist/heart/daemon/daemon-cli.js +29 -1616
  52. package/dist/heart/daemon/daemon-entry.js +345 -3
  53. package/dist/heart/daemon/daemon-health.js +141 -0
  54. package/dist/heart/daemon/daemon-runtime-sync.js +190 -12
  55. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  56. package/dist/heart/daemon/daemon.js +677 -58
  57. package/dist/heart/daemon/dns-workflow.js +394 -0
  58. package/dist/heart/daemon/doctor-types.js +8 -0
  59. package/dist/heart/daemon/doctor.js +486 -0
  60. package/dist/heart/daemon/health-monitor.js +92 -1
  61. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  62. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  63. package/dist/heart/daemon/http-health-probe.js +80 -0
  64. package/dist/heart/daemon/human-command-screens.js +234 -0
  65. package/dist/heart/daemon/human-readiness.js +114 -0
  66. package/dist/heart/daemon/inner-status.js +89 -0
  67. package/dist/heart/daemon/interactive-repair.js +394 -0
  68. package/dist/heart/daemon/launchd.js +25 -5
  69. package/dist/heart/daemon/log-tailer.js +82 -12
  70. package/dist/heart/daemon/logs-prune.js +110 -0
  71. package/dist/heart/daemon/message-router.js +2 -2
  72. package/dist/heart/daemon/os-cron-deps.js +134 -0
  73. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  74. package/dist/heart/daemon/ouro-entry.js +3 -1
  75. package/dist/heart/daemon/process-manager.js +214 -0
  76. package/dist/heart/daemon/provider-discovery.js +137 -0
  77. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  78. package/dist/heart/daemon/pulse.js +475 -0
  79. package/dist/heart/daemon/readiness-repair.js +365 -0
  80. package/dist/heart/daemon/run-hooks.js +2 -0
  81. package/dist/heart/daemon/runtime-logging.js +67 -16
  82. package/dist/heart/daemon/runtime-metadata.js +73 -0
  83. package/dist/heart/daemon/runtime-mode.js +67 -0
  84. package/dist/heart/daemon/safe-mode.js +161 -0
  85. package/dist/heart/daemon/sense-manager.js +178 -37
  86. package/dist/heart/daemon/session-id-resolver.js +131 -0
  87. package/dist/heart/daemon/skill-management-installer.js +94 -0
  88. package/dist/heart/daemon/socket-client.js +109 -4
  89. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  90. package/dist/heart/daemon/startup-tui.js +264 -0
  91. package/dist/heart/daemon/task-scheduler.js +3 -25
  92. package/dist/heart/daemon/terminal-ui.js +499 -0
  93. package/dist/heart/daemon/thoughts.js +162 -17
  94. package/dist/heart/daemon/up-progress.js +366 -0
  95. package/dist/heart/daemon/vault-items.js +56 -0
  96. package/dist/heart/delegation.js +62 -0
  97. package/dist/heart/habits/habit-migration.js +189 -0
  98. package/dist/heart/habits/habit-parser.js +140 -0
  99. package/dist/heart/habits/habit-runtime-state.js +100 -0
  100. package/dist/heart/habits/habit-scheduler.js +372 -0
  101. package/dist/heart/{daemon → hatch}/hatch-flow.js +52 -117
  102. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  103. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  104. package/dist/heart/{daemon → hatch}/specialist-tools.js +35 -12
  105. package/dist/heart/identity.js +201 -66
  106. package/dist/heart/kept-notes.js +357 -0
  107. package/dist/heart/kicks.js +1 -1
  108. package/dist/heart/machine-identity.js +161 -0
  109. package/dist/heart/mail-import-discovery.js +353 -0
  110. package/dist/heart/mcp/mcp-server.js +653 -0
  111. package/dist/heart/migrate-config.js +100 -0
  112. package/dist/heart/model-capabilities.js +59 -0
  113. package/dist/heart/outlook/outlook-http-hooks.js +66 -0
  114. package/dist/heart/outlook/outlook-http-response.js +7 -0
  115. package/dist/heart/outlook/outlook-http-routes.js +244 -0
  116. package/dist/heart/outlook/outlook-http-static.js +103 -0
  117. package/dist/heart/outlook/outlook-http-transport.js +116 -0
  118. package/dist/heart/outlook/outlook-http.js +99 -0
  119. package/dist/heart/outlook/outlook-read.js +31 -0
  120. package/dist/heart/outlook/outlook-types.js +27 -0
  121. package/dist/heart/outlook/outlook-view.js +195 -0
  122. package/dist/heart/outlook/readers/agent-machine.js +382 -0
  123. package/dist/heart/outlook/readers/continuity-readers.js +336 -0
  124. package/dist/heart/outlook/readers/mail.js +362 -0
  125. package/dist/heart/outlook/readers/runtime-readers.js +644 -0
  126. package/dist/heart/outlook/readers/sessions.js +232 -0
  127. package/dist/heart/outlook/readers/shared.js +111 -0
  128. package/dist/heart/platform.js +81 -0
  129. package/dist/heart/progress-story.js +42 -0
  130. package/dist/heart/provider-attempt.js +134 -0
  131. package/dist/heart/provider-binding-resolver.js +255 -0
  132. package/dist/heart/provider-credentials.js +424 -0
  133. package/dist/heart/provider-failover.js +301 -0
  134. package/dist/heart/provider-models.js +81 -0
  135. package/dist/heart/provider-ping.js +262 -0
  136. package/dist/heart/provider-state.js +216 -0
  137. package/dist/heart/provider-visibility.js +188 -0
  138. package/dist/heart/providers/anthropic-token.js +131 -0
  139. package/dist/heart/providers/anthropic.js +193 -55
  140. package/dist/heart/providers/azure.js +104 -13
  141. package/dist/heart/providers/error-classification.js +63 -0
  142. package/dist/heart/providers/github-copilot.js +145 -0
  143. package/dist/heart/providers/minimax-vlm.js +189 -0
  144. package/dist/heart/providers/minimax.js +29 -7
  145. package/dist/heart/providers/openai-codex.js +63 -39
  146. package/dist/heart/runtime-capability-check.js +170 -0
  147. package/dist/heart/runtime-credentials.js +260 -0
  148. package/dist/heart/sense-truth.js +11 -4
  149. package/dist/heart/session-activity.js +190 -0
  150. package/dist/heart/session-events.js +981 -0
  151. package/dist/heart/session-transcript.js +167 -0
  152. package/dist/heart/start-of-turn-packet.js +345 -0
  153. package/dist/heart/streaming.js +48 -28
  154. package/dist/heart/sync.js +332 -0
  155. package/dist/heart/target-resolution.js +127 -0
  156. package/dist/heart/tempo.js +93 -0
  157. package/dist/heart/temporal-view.js +41 -0
  158. package/dist/heart/tool-activity-callbacks.js +36 -0
  159. package/dist/heart/tool-description.js +135 -0
  160. package/dist/heart/tool-friction.js +55 -0
  161. package/dist/heart/tool-loop.js +200 -0
  162. package/dist/heart/turn-context.js +372 -0
  163. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +1 -1
  164. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  165. package/dist/heart/versioning/ouro-path-installer.js +425 -0
  166. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  167. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  168. package/dist/heart/{daemon → versioning}/update-checker.js +5 -1
  169. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  170. package/dist/mailroom/attention.js +167 -0
  171. package/dist/mailroom/autonomy.js +209 -0
  172. package/dist/mailroom/blob-store.js +600 -0
  173. package/dist/mailroom/core.js +658 -0
  174. package/dist/mailroom/entry.js +160 -0
  175. package/dist/mailroom/file-store.js +426 -0
  176. package/dist/mailroom/mbox-import.js +382 -0
  177. package/dist/mailroom/outbound.js +380 -0
  178. package/dist/mailroom/policy.js +263 -0
  179. package/dist/mailroom/reader.js +219 -0
  180. package/dist/mailroom/search-cache.js +182 -0
  181. package/dist/mailroom/search-relevance.js +319 -0
  182. package/dist/mailroom/smtp-ingress.js +176 -0
  183. package/dist/mailroom/source-state.js +176 -0
  184. package/dist/mailroom/travel-extract.js +89 -0
  185. package/dist/mind/bundle-manifest.js +7 -1
  186. package/dist/mind/context.js +164 -93
  187. package/dist/mind/diary-integrity.js +60 -0
  188. package/dist/mind/{memory.js → diary.js} +74 -93
  189. package/dist/mind/embedding-provider.js +60 -0
  190. package/dist/mind/file-state.js +179 -0
  191. package/dist/mind/friends/channel.js +30 -0
  192. package/dist/mind/friends/group-context.js +144 -0
  193. package/dist/mind/friends/resolver.js +54 -2
  194. package/dist/mind/friends/store-file.js +39 -3
  195. package/dist/mind/friends/trust-explanation.js +74 -0
  196. package/dist/mind/friends/types.js +2 -2
  197. package/dist/mind/journal-index.js +161 -0
  198. package/dist/mind/note-search.js +268 -0
  199. package/dist/mind/obligation-steering.js +221 -0
  200. package/dist/mind/pending.js +56 -8
  201. package/dist/mind/prompt-refresh.js +3 -2
  202. package/dist/mind/prompt.js +973 -168
  203. package/dist/mind/provenance-trust.js +26 -0
  204. package/dist/mind/scrutiny.js +173 -0
  205. package/dist/nerves/cli-logging.js +7 -1
  206. package/dist/nerves/coverage/audit-rules.js +15 -6
  207. package/dist/nerves/coverage/audit.js +28 -2
  208. package/dist/nerves/coverage/cli.js +1 -1
  209. package/dist/nerves/coverage/contract.js +5 -5
  210. package/dist/nerves/coverage/file-completeness.js +93 -5
  211. package/dist/nerves/coverage/run-artifacts.js +1 -1
  212. package/dist/nerves/event-buffer.js +111 -0
  213. package/dist/nerves/index.js +224 -4
  214. package/dist/nerves/observation.js +20 -0
  215. package/dist/nerves/redact.js +79 -0
  216. package/dist/nerves/runtime.js +5 -1
  217. package/dist/outlook-ui/assets/index-BPr5vNuM.css +1 -0
  218. package/dist/outlook-ui/assets/index-Cm51CY9W.js +61 -0
  219. package/dist/outlook-ui/index.html +15 -0
  220. package/dist/repertoire/ado-client.js +15 -56
  221. package/dist/repertoire/ado-semantic.js +11 -10
  222. package/dist/repertoire/api-client.js +97 -0
  223. package/dist/repertoire/bitwarden-store.js +774 -0
  224. package/dist/repertoire/bundle-templates.js +72 -0
  225. package/dist/repertoire/bw-installer.js +180 -0
  226. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  227. package/dist/repertoire/coding/context-pack.js +330 -0
  228. package/dist/repertoire/coding/feedback.js +197 -30
  229. package/dist/repertoire/coding/manager.js +158 -9
  230. package/dist/repertoire/coding/spawner.js +55 -9
  231. package/dist/repertoire/coding/tools.js +170 -7
  232. package/dist/repertoire/commerce-errors.js +109 -0
  233. package/dist/repertoire/commerce-self-test.js +156 -0
  234. package/dist/repertoire/credential-access.js +111 -0
  235. package/dist/repertoire/duffel-client.js +185 -0
  236. package/dist/repertoire/github-client.js +14 -55
  237. package/dist/repertoire/graph-client.js +11 -52
  238. package/dist/repertoire/guardrails.js +396 -0
  239. package/dist/repertoire/mcp-client.js +255 -0
  240. package/dist/repertoire/mcp-manager.js +305 -0
  241. package/dist/repertoire/mcp-tools.js +63 -0
  242. package/dist/repertoire/shell-sessions.js +133 -0
  243. package/dist/repertoire/skills.js +15 -24
  244. package/dist/repertoire/stripe-client.js +131 -0
  245. package/dist/repertoire/tasks/board.js +31 -5
  246. package/dist/repertoire/tasks/fix.js +182 -0
  247. package/dist/repertoire/tasks/index.js +16 -4
  248. package/dist/repertoire/tasks/lifecycle.js +2 -2
  249. package/dist/repertoire/tasks/parser.js +3 -2
  250. package/dist/repertoire/tasks/scanner.js +194 -37
  251. package/dist/repertoire/tasks/transitions.js +16 -78
  252. package/dist/repertoire/tool-results.js +29 -0
  253. package/dist/repertoire/tools-attachments.js +317 -0
  254. package/dist/repertoire/tools-base.js +46 -842
  255. package/dist/repertoire/tools-bluebubbles.js +1 -0
  256. package/dist/repertoire/tools-bridge.js +141 -0
  257. package/dist/repertoire/tools-bundle.js +984 -0
  258. package/dist/repertoire/tools-config.js +185 -0
  259. package/dist/repertoire/tools-continuity.js +248 -0
  260. package/dist/repertoire/tools-credential.js +381 -0
  261. package/dist/repertoire/tools-files.js +342 -0
  262. package/dist/repertoire/tools-flight.js +224 -0
  263. package/dist/repertoire/tools-flow.js +105 -0
  264. package/dist/repertoire/tools-github.js +1 -7
  265. package/dist/repertoire/tools-mail.js +1281 -0
  266. package/dist/repertoire/tools-notes.js +376 -0
  267. package/dist/repertoire/tools-session.js +749 -0
  268. package/dist/repertoire/tools-shell.js +120 -0
  269. package/dist/repertoire/tools-stripe.js +180 -0
  270. package/dist/repertoire/tools-surface.js +243 -0
  271. package/dist/repertoire/tools-teams.js +9 -39
  272. package/dist/repertoire/tools-travel.js +125 -0
  273. package/dist/repertoire/tools-trip.js +356 -0
  274. package/dist/repertoire/tools-user-profile.js +144 -0
  275. package/dist/repertoire/tools-vault.js +40 -0
  276. package/dist/repertoire/tools.js +144 -115
  277. package/dist/repertoire/travel-api-client.js +360 -0
  278. package/dist/repertoire/user-profile.js +131 -0
  279. package/dist/repertoire/vault-setup.js +246 -0
  280. package/dist/repertoire/vault-unlock.js +561 -0
  281. package/dist/scripts/claude-code-hook.js +41 -0
  282. package/dist/scripts/claude-code-stop-hook.js +47 -0
  283. package/dist/senses/attention-queue.js +116 -0
  284. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  285. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  286. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +219 -18
  287. package/dist/senses/bluebubbles/entry.js +73 -0
  288. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
  289. package/dist/senses/bluebubbles/index.js +1835 -0
  290. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
  291. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
  292. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +3 -3
  293. package/dist/senses/bluebubbles/processed-log.js +111 -0
  294. package/dist/senses/bluebubbles/replay.js +129 -0
  295. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +2 -2
  296. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  297. package/dist/senses/cli/bracketed-paste.js +82 -0
  298. package/dist/senses/cli/image-paste.js +287 -0
  299. package/dist/senses/cli/image-ref-navigation.js +75 -0
  300. package/dist/senses/cli/ink-app.js +156 -0
  301. package/dist/senses/cli/inline-diff.js +64 -0
  302. package/dist/senses/cli/input-keys.js +174 -0
  303. package/dist/senses/cli/kill-ring.js +86 -0
  304. package/dist/senses/cli/message-list.js +51 -0
  305. package/dist/senses/cli/ouro-tui.js +605 -0
  306. package/dist/senses/cli/spinner-imperative.js +135 -0
  307. package/dist/senses/cli/spinner.js +101 -0
  308. package/dist/senses/cli/status-line.js +60 -0
  309. package/dist/senses/cli/streaming-markdown.js +526 -0
  310. package/dist/senses/cli/tool-display.js +83 -0
  311. package/dist/senses/cli/tool-render.js +85 -0
  312. package/dist/senses/cli/tui-store.js +240 -0
  313. package/dist/senses/cli/virtual-list.js +35 -0
  314. package/dist/senses/cli-entry.js +60 -8
  315. package/dist/senses/cli-layout.js +187 -0
  316. package/dist/senses/cli.js +515 -211
  317. package/dist/senses/commands.js +66 -3
  318. package/dist/senses/habit-turn-message.js +108 -0
  319. package/dist/senses/inner-dialog-worker.js +110 -20
  320. package/dist/senses/inner-dialog.js +408 -21
  321. package/dist/senses/mail-entry.js +66 -0
  322. package/dist/senses/mail.js +379 -0
  323. package/dist/senses/pipeline.js +588 -81
  324. package/dist/senses/proactive-content-guard.js +51 -0
  325. package/dist/senses/shared-turn.js +248 -0
  326. package/dist/senses/surface-tool.js +68 -0
  327. package/dist/senses/teams-entry.js +60 -8
  328. package/dist/senses/teams.js +412 -163
  329. package/dist/senses/trust-gate.js +100 -5
  330. package/dist/trips/core.js +138 -0
  331. package/dist/trips/store.js +146 -0
  332. package/package.json +37 -7
  333. package/skills/agent-commerce.md +106 -0
  334. package/skills/browser-navigation.md +117 -0
  335. package/skills/commerce-setup-guide.md +116 -0
  336. package/skills/commerce-setup.md +84 -0
  337. package/skills/configure-dev-tools.md +101 -0
  338. package/skills/travel-planning.md +138 -0
  339. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  340. package/dist/heart/daemon/subagent-installer.js +0 -166
  341. package/dist/heart/session-recall.js +0 -116
  342. package/dist/mind/associative-recall.js +0 -209
  343. package/dist/senses/bluebubbles-entry.js +0 -13
  344. package/dist/senses/bluebubbles.js +0 -1032
  345. package/dist/senses/debug-activity.js +0 -127
  346. package/subagents/README.md +0 -86
  347. package/subagents/work-doer.md +0 -237
  348. package/subagents/work-merger.md +0 -618
  349. package/subagents/work-planner.md +0 -390
  350. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  351. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  352. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  353. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  354. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  355. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  356. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  357. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  358. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  359. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  360. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  361. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  362. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  363. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  364. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  365. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -0,0 +1,981 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.normalizeContinuityState = normalizeContinuityState;
37
+ exports.validateSessionMessages = validateSessionMessages;
38
+ exports.repairSessionMessages = repairSessionMessages;
39
+ exports.migrateToolNames = migrateToolNames;
40
+ exports.sanitizeProviderMessages = sanitizeProviderMessages;
41
+ exports.stampIngressTime = stampIngressTime;
42
+ exports.getIngressTime = getIngressTime;
43
+ exports.projectProviderMessages = projectProviderMessages;
44
+ exports.annotateMessageTimestamps = annotateMessageTimestamps;
45
+ exports.bestEventTimestamp = bestEventTimestamp;
46
+ exports.formatSessionEventTimestamp = formatSessionEventTimestamp;
47
+ exports.extractEventText = extractEventText;
48
+ exports.deriveSessionChronology = deriveSessionChronology;
49
+ exports.describeCurrentSessionTiming = describeCurrentSessionTiming;
50
+ exports.migrateLegacySessionEnvelope = migrateLegacySessionEnvelope;
51
+ exports.parseSessionEnvelope = parseSessionEnvelope;
52
+ exports.loadSessionEnvelopeFile = loadSessionEnvelopeFile;
53
+ exports.buildCanonicalSessionEnvelope = buildCanonicalSessionEnvelope;
54
+ exports.loadFullEventHistory = loadFullEventHistory;
55
+ exports.appendEvictedToArchive = appendEvictedToArchive;
56
+ exports.appendSyntheticAssistantEvent = appendSyntheticAssistantEvent;
57
+ const fs = __importStar(require("fs"));
58
+ const runtime_1 = require("../nerves/runtime");
59
+ function formatElapsed(ms) {
60
+ const minutes = Math.max(0, Math.floor(ms / 60000));
61
+ if (minutes < 60)
62
+ return `${minutes}m ago`;
63
+ const hours = Math.floor(minutes / 60);
64
+ if (hours < 24)
65
+ return `${hours}h ago`;
66
+ const days = Math.floor(hours / 24);
67
+ return `${days}d ago`;
68
+ }
69
+ const LEGACY_WRITTEN_NOTE_PREFIX = "mem" + "ory";
70
+ const TOOL_NAME_MIGRATIONS = {
71
+ final_answer: "settle",
72
+ no_response: "observe",
73
+ go_inward: "ponder",
74
+ descend: "ponder",
75
+ [`${LEGACY_WRITTEN_NOTE_PREFIX}_save`]: "diary_write",
76
+ [`${LEGACY_WRITTEN_NOTE_PREFIX}_search`]: "search_notes",
77
+ };
78
+ function normalizeUsage(usage) {
79
+ if (!usage || typeof usage !== "object")
80
+ return null;
81
+ const record = usage;
82
+ if (typeof record.input_tokens !== "number"
83
+ || typeof record.output_tokens !== "number"
84
+ || typeof record.reasoning_tokens !== "number"
85
+ || typeof record.total_tokens !== "number") {
86
+ return null;
87
+ }
88
+ return {
89
+ input_tokens: record.input_tokens,
90
+ output_tokens: record.output_tokens,
91
+ reasoning_tokens: record.reasoning_tokens,
92
+ total_tokens: record.total_tokens,
93
+ };
94
+ }
95
+ function normalizeContinuityState(state) {
96
+ const record = state && typeof state === "object"
97
+ ? state
98
+ : null;
99
+ return {
100
+ mustResolveBeforeHandoff: record?.mustResolveBeforeHandoff === true,
101
+ lastFriendActivityAt: typeof record?.lastFriendActivityAt === "string" ? record.lastFriendActivityAt : null,
102
+ };
103
+ }
104
+ function normalizeContent(content) {
105
+ if (content == null)
106
+ return null;
107
+ if (typeof content === "string")
108
+ return content;
109
+ if (!Array.isArray(content))
110
+ return null;
111
+ return content
112
+ .filter((part) => part != null && typeof part === "object")
113
+ .map((part) => ({ ...part }));
114
+ }
115
+ const SYNTHETIC_TIMESTAMP_PREFIX_RE = /^(?:(?:\[(?:just now|-\d+[mhd])\])\s*)+/i;
116
+ function stripSyntheticTimestampPrefix(text) {
117
+ return text.replace(SYNTHETIC_TIMESTAMP_PREFIX_RE, "");
118
+ }
119
+ function sanitizeConversationContent(role, content) {
120
+ if (role !== "user" && role !== "assistant")
121
+ return content;
122
+ if (typeof content === "string")
123
+ return stripSyntheticTimestampPrefix(content);
124
+ if (!Array.isArray(content))
125
+ return content;
126
+ return content.map((part) => {
127
+ if (part.type === "text" && typeof part.text === "string") {
128
+ return { ...part, text: stripSyntheticTimestampPrefix(part.text) };
129
+ }
130
+ return part;
131
+ });
132
+ }
133
+ function normalizeToolCalls(rawToolCalls) {
134
+ if (!Array.isArray(rawToolCalls))
135
+ return [];
136
+ return rawToolCalls
137
+ .filter((call) => call != null && typeof call === "object")
138
+ .map((call) => {
139
+ const fn = call.function;
140
+ const originalName = typeof fn?.name === "string" ? fn.name : "unknown";
141
+ const migratedName = TOOL_NAME_MIGRATIONS[originalName] ?? originalName;
142
+ return {
143
+ id: typeof call.id === "string" ? call.id : "",
144
+ type: typeof call.type === "string" ? call.type : "function",
145
+ function: {
146
+ name: migratedName,
147
+ arguments: typeof fn?.arguments === "string" ? fn.arguments : JSON.stringify(fn?.arguments ?? ""),
148
+ },
149
+ };
150
+ });
151
+ }
152
+ function normalizeRole(role) {
153
+ if (role === "developer")
154
+ return "system";
155
+ return role === "system" || role === "user" || role === "assistant" || role === "tool"
156
+ ? role
157
+ : "user";
158
+ }
159
+ function normalizeMessage(message) {
160
+ const record = message;
161
+ const role = normalizeRole(record.role);
162
+ const normalizedContent = sanitizeConversationContent(role, normalizeContent(record.content));
163
+ if (role === "assistant") {
164
+ return {
165
+ role,
166
+ content: normalizedContent,
167
+ name: typeof record.name === "string" ? record.name : null,
168
+ toolCallId: null,
169
+ toolCalls: normalizeToolCalls(record.tool_calls),
170
+ hadToolCallsField: Array.isArray(record.tool_calls),
171
+ };
172
+ }
173
+ if (role === "tool") {
174
+ return {
175
+ role,
176
+ content: typeof record.content === "string" ? record.content : "",
177
+ name: null,
178
+ toolCallId: typeof record.tool_call_id === "string" ? record.tool_call_id : null,
179
+ toolCalls: [],
180
+ hadToolCallsField: false,
181
+ };
182
+ }
183
+ return {
184
+ role,
185
+ content: normalizedContent ?? "",
186
+ name: typeof record.name === "string" ? record.name : null,
187
+ toolCallId: null,
188
+ toolCalls: [],
189
+ hadToolCallsField: false,
190
+ };
191
+ }
192
+ function contentText(content) {
193
+ if (typeof content === "string")
194
+ return content;
195
+ if (!Array.isArray(content))
196
+ return "";
197
+ return content
198
+ .map((part) => (part.type === "text" && typeof part.text === "string"
199
+ ? part.text
200
+ : ""))
201
+ .filter((text) => text.length > 0)
202
+ .join("");
203
+ }
204
+ function toProviderMessage(message) {
205
+ if (message.role === "assistant") {
206
+ const assistant = {
207
+ role: "assistant",
208
+ content: message.content,
209
+ };
210
+ if (message.name)
211
+ assistant.name = message.name;
212
+ if (message.hadToolCallsField || message.toolCalls.length > 0) {
213
+ assistant.tool_calls = message.toolCalls.map((call) => ({
214
+ id: call.id,
215
+ type: call.type,
216
+ function: {
217
+ name: call.function.name,
218
+ arguments: call.function.arguments,
219
+ },
220
+ }));
221
+ }
222
+ return assistant;
223
+ }
224
+ if (message.role === "tool") {
225
+ return {
226
+ role: "tool",
227
+ content: typeof message.content === "string" ? message.content : contentText(message.content),
228
+ tool_call_id: message.toolCallId ?? "",
229
+ };
230
+ }
231
+ if (message.role === "system") {
232
+ return {
233
+ role: "system",
234
+ content: typeof message.content === "string" ? message.content : contentText(message.content),
235
+ ...(message.name ? { name: message.name } : {}),
236
+ };
237
+ }
238
+ return {
239
+ role: "user",
240
+ content: (typeof message.content === "string" || Array.isArray(message.content) ? message.content : ""),
241
+ ...(message.name ? { name: message.name } : {}),
242
+ };
243
+ }
244
+ function messageFingerprint(message) {
245
+ const normalized = normalizeMessage(message);
246
+ return JSON.stringify({
247
+ role: normalized.role,
248
+ content: normalized.content,
249
+ name: normalized.name,
250
+ tool_call_id: normalized.toolCallId,
251
+ tool_calls: normalized.toolCalls,
252
+ });
253
+ }
254
+ function makeEventId(sequence) {
255
+ return `evt-${String(sequence).padStart(6, "0")}`;
256
+ }
257
+ /**
258
+ * Collapse duplicate event ids to a single entry, last-occurrence-wins.
259
+ *
260
+ * Concurrent writers in older versions of postTurnPersist could each load the
261
+ * envelope, compute `events.length + 1` for the next sequence, and both write
262
+ * an event with the same id. The duplicates would persist in the saved JSON
263
+ * and confuse downstream replay (the same outbound message could appear to
264
+ * have been sent twice from the agent's perspective without the agent knowing
265
+ * it sent it). We dedupe defensively on every load so corrupted sessions
266
+ * self-heal on the next save and so any future race produces a consistent
267
+ * view.
268
+ */
269
+ function dedupeEventsByIdLastWins(events) {
270
+ // Index id → last position so we can preserve original order while
271
+ // collapsing duplicates to their final occurrence.
272
+ const lastIndexById = new Map();
273
+ for (let i = 0; i < events.length; i++) {
274
+ lastIndexById.set(events[i].id, i);
275
+ }
276
+ return events.filter((event, index) => lastIndexById.get(event.id) === index);
277
+ }
278
+ /**
279
+ * The next sequence to assign for a freshly-built event. Uses max(existing
280
+ * sequences) + 1 rather than `events.length + 1` so that gaps from earlier
281
+ * pruning, archive replay, or self-heal dedup never produce a colliding id.
282
+ */
283
+ function nextEventSequence(existing) {
284
+ return existing.reduce((max, event) => Math.max(max, event.sequence), 0) + 1;
285
+ }
286
+ function validateSessionMessages(messages) {
287
+ const violations = [];
288
+ let prevNonToolRole = null;
289
+ let prevAssistantHadToolCalls = false;
290
+ let sawToolResultSincePrevAssistant = false;
291
+ for (let i = 0; i < messages.length; i++) {
292
+ const msg = normalizeMessage(messages[i]);
293
+ if (msg.role === "system")
294
+ continue;
295
+ if (msg.role === "tool") {
296
+ sawToolResultSincePrevAssistant = true;
297
+ continue;
298
+ }
299
+ if (msg.role === "assistant" && prevNonToolRole === "assistant") {
300
+ if (!(prevAssistantHadToolCalls && sawToolResultSincePrevAssistant)) {
301
+ violations.push(`back-to-back assistant at index ${i}`);
302
+ }
303
+ }
304
+ prevAssistantHadToolCalls = msg.role === "assistant" && msg.toolCalls.length > 0;
305
+ sawToolResultSincePrevAssistant = false;
306
+ prevNonToolRole = msg.role;
307
+ }
308
+ return violations;
309
+ }
310
+ function repairSessionMessages(messages) {
311
+ const normalized = messages.map(normalizeMessage);
312
+ const violations = validateSessionMessages(messages);
313
+ if (violations.length === 0)
314
+ return normalized.map(toProviderMessage);
315
+ const result = [];
316
+ for (const msg of normalized) {
317
+ if (msg.role === "assistant" && result.length > 0) {
318
+ const prev = result[result.length - 1];
319
+ if (prev.role === "assistant" && prev.toolCalls.length === 0) {
320
+ const prevContent = contentText(prev.content);
321
+ const curContent = contentText(msg.content);
322
+ prev.content = `${prevContent}\n\n${curContent}`;
323
+ continue;
324
+ }
325
+ }
326
+ result.push(msg);
327
+ }
328
+ (0, runtime_1.emitNervesEvent)({
329
+ level: "info",
330
+ event: "mind.session_invariant_repair",
331
+ component: "mind",
332
+ message: "repaired session invariant violations",
333
+ meta: { violations },
334
+ });
335
+ return result.map(toProviderMessage);
336
+ }
337
+ function repairToolCallSequences(messages) {
338
+ const normalized = messages.map(normalizeMessage);
339
+ const validCallIds = new Set();
340
+ for (const msg of normalized) {
341
+ if (msg.role !== "assistant")
342
+ continue;
343
+ for (const toolCall of msg.toolCalls)
344
+ validCallIds.add(toolCall.id);
345
+ }
346
+ let removed = 0;
347
+ const repaired = normalized.filter((msg) => {
348
+ if (msg.role !== "tool")
349
+ return true;
350
+ const keep = msg.toolCallId !== null && validCallIds.has(msg.toolCallId);
351
+ if (!keep)
352
+ removed++;
353
+ return keep;
354
+ });
355
+ if (removed > 0) {
356
+ (0, runtime_1.emitNervesEvent)({
357
+ level: "info",
358
+ event: "mind.session_orphan_tool_result_repair",
359
+ component: "mind",
360
+ message: "removed orphaned tool results from session history",
361
+ meta: { removed },
362
+ });
363
+ }
364
+ let injected = 0;
365
+ for (let i = 0; i < repaired.length; i++) {
366
+ const msg = repaired[i];
367
+ if (msg.role !== "assistant" || msg.toolCalls.length === 0)
368
+ continue;
369
+ const resultIds = new Set();
370
+ for (let j = i + 1; j < repaired.length; j++) {
371
+ const following = repaired[j];
372
+ if (following.role === "tool" && following.toolCallId !== null) {
373
+ resultIds.add(following.toolCallId);
374
+ continue;
375
+ }
376
+ if (following.role === "assistant" || following.role === "user")
377
+ break;
378
+ }
379
+ const missing = msg.toolCalls.filter((toolCall) => !resultIds.has(toolCall.id));
380
+ if (missing.length === 0)
381
+ continue;
382
+ const syntheticResults = missing.map((toolCall) => ({
383
+ role: "tool",
384
+ content: "error: tool call was interrupted (previous turn timed out or was aborted)",
385
+ name: null,
386
+ toolCallId: toolCall.id,
387
+ toolCalls: [],
388
+ hadToolCallsField: false,
389
+ }));
390
+ let insertAt = i + 1;
391
+ while (insertAt < repaired.length && repaired[insertAt].role === "tool")
392
+ insertAt++;
393
+ repaired.splice(insertAt, 0, ...syntheticResults);
394
+ injected += syntheticResults.length;
395
+ }
396
+ if (injected > 0) {
397
+ (0, runtime_1.emitNervesEvent)({
398
+ level: "info",
399
+ event: "mind.session_orphan_tool_call_repair",
400
+ component: "mind",
401
+ message: "injected synthetic tool results for orphaned tool calls",
402
+ meta: { injected },
403
+ });
404
+ }
405
+ return repaired.map(toProviderMessage);
406
+ }
407
+ function canonicalizeSystemMessageSequence(messages) {
408
+ const normalized = messages.map(normalizeMessage);
409
+ const firstSystemIndex = normalized.findIndex((msg) => msg.role === "system");
410
+ if (firstSystemIndex === -1)
411
+ return normalized.map(toProviderMessage);
412
+ const extraSystemCount = normalized.filter((msg) => msg.role === "system").length - 1;
413
+ if (firstSystemIndex === 0 && extraSystemCount === 0) {
414
+ return normalized.map(toProviderMessage);
415
+ }
416
+ const primarySystem = normalized[firstSystemIndex];
417
+ const nonSystemMessages = normalized.filter((msg) => msg.role !== "system");
418
+ const repaired = [primarySystem, ...nonSystemMessages].map(toProviderMessage);
419
+ (0, runtime_1.emitNervesEvent)({
420
+ level: "info",
421
+ event: "mind.session_system_prompt_repair",
422
+ component: "mind",
423
+ message: "canonicalized session system prompt sequence",
424
+ meta: {
425
+ firstSystemIndex,
426
+ extraSystemCount,
427
+ finalMessageCount: repaired.length,
428
+ },
429
+ });
430
+ return repaired;
431
+ }
432
+ function migrateToolNames(messages) {
433
+ const safeMessages = messages.filter((message) => Boolean(message) && typeof message === "object");
434
+ let migrated = 0;
435
+ for (const message of safeMessages) {
436
+ const record = message;
437
+ if (record.role !== "assistant" || !Array.isArray(record.tool_calls))
438
+ continue;
439
+ for (const toolCall of record.tool_calls) {
440
+ if (!toolCall || typeof toolCall !== "object")
441
+ continue;
442
+ const toolRecord = toolCall;
443
+ if (toolRecord.type !== "function")
444
+ continue;
445
+ const originalName = toolRecord.function?.name;
446
+ if (typeof originalName !== "string")
447
+ continue;
448
+ if (TOOL_NAME_MIGRATIONS[originalName])
449
+ migrated += 1;
450
+ }
451
+ }
452
+ if (migrated > 0) {
453
+ (0, runtime_1.emitNervesEvent)({
454
+ level: "info",
455
+ event: "mind.session_tool_name_migration",
456
+ component: "mind",
457
+ message: "migrated deprecated tool names in session history",
458
+ meta: { migrated },
459
+ });
460
+ }
461
+ return safeMessages.map(normalizeMessage).map(toProviderMessage);
462
+ }
463
+ function sanitizeProviderMessages(messages) {
464
+ const safeMessages = messages.filter((message) => Boolean(message) && typeof message === "object");
465
+ const normalized = safeMessages.map(normalizeMessage);
466
+ const violations = validateSessionMessages(safeMessages);
467
+ if (violations.length > 0) {
468
+ (0, runtime_1.emitNervesEvent)({
469
+ level: "info",
470
+ event: "mind.session_invariant_violation",
471
+ component: "mind",
472
+ message: "session invariant violated",
473
+ meta: { violations },
474
+ });
475
+ }
476
+ return canonicalizeSystemMessageSequence(migrateToolNames(repairToolCallSequences(repairSessionMessages(normalized.map(toProviderMessage)))));
477
+ }
478
+ function stampIngressTime(msg) {
479
+ msg._ingressAt = new Date().toISOString();
480
+ }
481
+ function getIngressTime(msg) {
482
+ const value = msg._ingressAt;
483
+ return typeof value === "string" ? value : null;
484
+ }
485
+ function createEventTime(role, recordedAt, captureKind, ingressAt) {
486
+ if (captureKind === "migration") {
487
+ return {
488
+ authoredAt: null,
489
+ authoredAtSource: "migration",
490
+ observedAt: null,
491
+ observedAtSource: "migration",
492
+ recordedAt,
493
+ recordedAtSource: "migration",
494
+ };
495
+ }
496
+ if (role === "user") {
497
+ return {
498
+ authoredAt: null,
499
+ authoredAtSource: "unknown",
500
+ observedAt: ingressAt ?? recordedAt,
501
+ observedAtSource: "ingest",
502
+ recordedAt,
503
+ recordedAtSource: "save",
504
+ };
505
+ }
506
+ return {
507
+ authoredAt: recordedAt,
508
+ authoredAtSource: "local",
509
+ observedAt: recordedAt,
510
+ observedAtSource: "local",
511
+ recordedAt,
512
+ recordedAtSource: "save",
513
+ };
514
+ }
515
+ function buildEventFromMessage(message, sequence, recordedAt, captureKind, sourceMessageIndex, legacyVersion, ingressAt) {
516
+ const normalized = normalizeMessage(message);
517
+ const role = normalized.role;
518
+ return {
519
+ id: makeEventId(sequence),
520
+ sequence,
521
+ role,
522
+ content: normalized.content,
523
+ name: normalized.name,
524
+ toolCallId: role === "tool" ? normalized.toolCallId : null,
525
+ toolCalls: role === "assistant" ? normalized.toolCalls : [],
526
+ attachments: [],
527
+ time: createEventTime(role, recordedAt, captureKind, ingressAt),
528
+ relations: {
529
+ replyToEventId: null,
530
+ threadRootEventId: null,
531
+ references: [],
532
+ toolCallId: role === "tool" ? normalized.toolCallId : null,
533
+ supersedesEventId: null,
534
+ redactsEventId: null,
535
+ },
536
+ provenance: {
537
+ captureKind,
538
+ legacyVersion,
539
+ sourceMessageIndex,
540
+ },
541
+ };
542
+ }
543
+ function projectProviderMessages(envelope) {
544
+ const eventIds = envelope.projection.eventIds.length > 0
545
+ ? envelope.projection.eventIds
546
+ : envelope.events.map((event) => event.id);
547
+ const byId = new Map(envelope.events.map((event) => [event.id, event]));
548
+ return eventIds
549
+ .map((id) => byId.get(id))
550
+ .filter((event) => Boolean(event))
551
+ .map((event) => toProviderMessage({
552
+ role: event.role,
553
+ content: event.content,
554
+ name: event.name,
555
+ toolCallId: event.toolCallId,
556
+ toolCalls: event.toolCalls,
557
+ hadToolCallsField: event.toolCalls.length > 0,
558
+ }));
559
+ }
560
+ /**
561
+ * Annotate user and assistant messages with a relative time offset tag.
562
+ * System and tool messages are untouched.
563
+ */
564
+ function annotateMessageTimestamps(envelope, messages, nowMs = Date.now()) {
565
+ const eventIds = envelope.projection.eventIds.length > 0
566
+ ? envelope.projection.eventIds
567
+ : envelope.events.map((event) => event.id);
568
+ const byId = new Map(envelope.events.map((event) => [event.id, event]));
569
+ const events = eventIds
570
+ .map((id) => byId.get(id))
571
+ .filter((event) => Boolean(event));
572
+ return messages.map((msg, i) => {
573
+ const event = events[i];
574
+ if (!event)
575
+ return msg;
576
+ if (event.role !== "user" && event.role !== "assistant")
577
+ return msg;
578
+ const ts = bestEventTimestamp(event);
579
+ const elapsed = nowMs - Date.parse(ts);
580
+ if (elapsed < 0)
581
+ return msg;
582
+ const tag = elapsed < 60000 ? "[just now]" : `[-${formatElapsedCompact(elapsed)}]`;
583
+ if (typeof msg.content === "string" && msg.content.length > 0) {
584
+ return { ...msg, content: `${tag} ${msg.content}` };
585
+ }
586
+ return msg;
587
+ });
588
+ }
589
+ /** Compact elapsed format for message annotations: "3m", "2h", "1d". */
590
+ function formatElapsedCompact(ms) {
591
+ const minutes = Math.max(1, Math.floor(ms / 60000));
592
+ if (minutes < 60)
593
+ return `${minutes}m`;
594
+ const hours = Math.floor(minutes / 60);
595
+ if (hours < 24)
596
+ return `${hours}h`;
597
+ const days = Math.floor(hours / 24);
598
+ return `${days}d`;
599
+ }
600
+ function bestEventTimestamp(event) {
601
+ return event.time.authoredAt ?? event.time.observedAt ?? event.time.recordedAt;
602
+ }
603
+ function formatSessionEventTimestamp(event) {
604
+ const iso = bestEventTimestamp(event);
605
+ const date = new Date(iso);
606
+ const year = date.getUTCFullYear();
607
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
608
+ const day = String(date.getUTCDate()).padStart(2, "0");
609
+ const hour = String(date.getUTCHours()).padStart(2, "0");
610
+ const minute = String(date.getUTCMinutes()).padStart(2, "0");
611
+ return `${year}-${month}-${day} ${hour}:${minute}`;
612
+ }
613
+ function extractEventText(event) {
614
+ return contentText(event.content);
615
+ }
616
+ function deriveSessionChronology(events) {
617
+ let lastInboundAt = null;
618
+ let lastOutboundAt = null;
619
+ let lastActivityAt = null;
620
+ let lastAssistantSequence = -1;
621
+ for (const event of events) {
622
+ if (event.role === "system")
623
+ continue;
624
+ const at = bestEventTimestamp(event);
625
+ lastActivityAt = at;
626
+ if (event.role === "user") {
627
+ lastInboundAt = at;
628
+ }
629
+ if (event.role === "assistant") {
630
+ lastOutboundAt = at;
631
+ lastAssistantSequence = event.sequence;
632
+ }
633
+ }
634
+ const unansweredInboundCount = events.filter((event) => event.role === "user" && event.sequence > lastAssistantSequence).length;
635
+ return {
636
+ lastInboundAt,
637
+ lastOutboundAt,
638
+ lastActivityAt,
639
+ unansweredInboundCount,
640
+ };
641
+ }
642
+ function describeCurrentSessionTiming(events, nowMs = Date.now()) {
643
+ const chronology = deriveSessionChronology(events);
644
+ const parts = [];
645
+ if (chronology.lastInboundAt) {
646
+ parts.push(`last inbound ${formatElapsed(nowMs - Date.parse(chronology.lastInboundAt))}`);
647
+ }
648
+ if (chronology.lastOutboundAt) {
649
+ parts.push(`i last replied ${formatElapsed(nowMs - Date.parse(chronology.lastOutboundAt))}`);
650
+ }
651
+ if (chronology.unansweredInboundCount > 0) {
652
+ const count = chronology.unansweredInboundCount;
653
+ parts.push(`${count} unanswered inbound message${count === 1 ? "" : "s"}`);
654
+ }
655
+ return parts.length > 0 ? `current thread: ${parts.join("; ")}` : "";
656
+ }
657
+ function migrateLegacySessionEnvelope(raw, options) {
658
+ if (!raw || typeof raw !== "object")
659
+ return null;
660
+ const legacy = raw;
661
+ const looksLegacy = legacy.version === 1
662
+ || (legacy.version == null && ("messages" in legacy || "lastUsage" in legacy || "state" in legacy));
663
+ if (!looksLegacy)
664
+ return null;
665
+ const messages = Array.isArray(legacy.messages)
666
+ ? sanitizeProviderMessages(legacy.messages)
667
+ : [];
668
+ const recordedAt = options.fileMtimeAt ?? options.recordedAt;
669
+ const events = messages.map((message, index) => buildEventFromMessage(message, index + 1, recordedAt, "migration", index, 1));
670
+ return {
671
+ version: 2,
672
+ events,
673
+ projection: {
674
+ eventIds: events.map((event) => event.id),
675
+ trimmed: false,
676
+ maxTokens: null,
677
+ contextMargin: null,
678
+ inputTokens: null,
679
+ projectedAt: recordedAt,
680
+ },
681
+ lastUsage: normalizeUsage(legacy.lastUsage),
682
+ state: normalizeContinuityState(legacy.state),
683
+ };
684
+ }
685
+ function parseSessionEnvelope(raw, options = {}) {
686
+ const recordedAt = options.recordedAt ?? new Date().toISOString();
687
+ const fileMtimeAt = options.fileMtimeAt ?? null;
688
+ const migrated = migrateLegacySessionEnvelope(raw, { recordedAt, fileMtimeAt });
689
+ if (migrated)
690
+ return migrated;
691
+ if (!raw || typeof raw !== "object")
692
+ return null;
693
+ const record = raw;
694
+ if (record.version !== 2 || !Array.isArray(record.events) || !record.projection || typeof record.projection !== "object") {
695
+ return null;
696
+ }
697
+ const rawEvents = record.events
698
+ .filter((event) => event != null && typeof event === "object")
699
+ .map((event, index) => {
700
+ const role = normalizeRole(event.role);
701
+ const time = event.time;
702
+ const relations = event.relations;
703
+ const provenance = event.provenance;
704
+ const content = sanitizeConversationContent(role, normalizeContent(event.content));
705
+ return {
706
+ id: typeof event.id === "string" ? event.id : makeEventId(index + 1),
707
+ sequence: typeof event.sequence === "number" ? event.sequence : index + 1,
708
+ role,
709
+ content,
710
+ name: typeof event.name === "string" ? event.name : null,
711
+ toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : null,
712
+ toolCalls: normalizeToolCalls(event.toolCalls),
713
+ attachments: Array.isArray(event.attachments) ? event.attachments.filter((item) => typeof item === "string") : [],
714
+ time: {
715
+ authoredAt: typeof time?.authoredAt === "string" ? time.authoredAt : null,
716
+ authoredAtSource: typeof time?.authoredAtSource === "string" ? time.authoredAtSource : "unknown",
717
+ observedAt: typeof time?.observedAt === "string" ? time.observedAt : null,
718
+ observedAtSource: typeof time?.observedAtSource === "string" ? time.observedAtSource : "unknown",
719
+ recordedAt: typeof time?.recordedAt === "string" ? time.recordedAt : recordedAt,
720
+ recordedAtSource: typeof time?.recordedAtSource === "string" ? time.recordedAtSource : "save",
721
+ },
722
+ relations: {
723
+ replyToEventId: typeof relations?.replyToEventId === "string" ? relations.replyToEventId : null,
724
+ threadRootEventId: typeof relations?.threadRootEventId === "string" ? relations.threadRootEventId : null,
725
+ references: Array.isArray(relations?.references) ? relations.references.filter((item) => typeof item === "string") : [],
726
+ toolCallId: typeof relations?.toolCallId === "string" ? relations.toolCallId : null,
727
+ supersedesEventId: typeof relations?.supersedesEventId === "string" ? relations.supersedesEventId : null,
728
+ redactsEventId: typeof relations?.redactsEventId === "string" ? relations.redactsEventId : null,
729
+ },
730
+ provenance: {
731
+ captureKind: typeof provenance?.captureKind === "string" ? provenance.captureKind : "live",
732
+ legacyVersion: typeof provenance?.legacyVersion === "number" ? provenance.legacyVersion : null,
733
+ sourceMessageIndex: typeof provenance?.sourceMessageIndex === "number" ? provenance.sourceMessageIndex : null,
734
+ },
735
+ };
736
+ });
737
+ // Self-heal duplicate event ids that may have been written by concurrent
738
+ // writers in older harness versions. Last-occurrence-wins by id (later
739
+ // entries in the persisted file are the more recent state for that id).
740
+ // We preserve the original document order otherwise, so projection.eventIds
741
+ // still resolves predictably.
742
+ const events = dedupeEventsByIdLastWins(rawEvents);
743
+ const projection = record.projection;
744
+ return {
745
+ version: 2,
746
+ events,
747
+ projection: {
748
+ eventIds: Array.isArray(projection.eventIds) ? projection.eventIds.filter((item) => typeof item === "string") : [],
749
+ trimmed: projection.trimmed === true,
750
+ maxTokens: typeof projection.maxTokens === "number" ? projection.maxTokens : null,
751
+ contextMargin: typeof projection.contextMargin === "number" ? projection.contextMargin : null,
752
+ inputTokens: typeof projection.inputTokens === "number" ? projection.inputTokens : null,
753
+ projectedAt: typeof projection.projectedAt === "string" ? projection.projectedAt : null,
754
+ },
755
+ lastUsage: normalizeUsage(record.lastUsage),
756
+ state: normalizeContinuityState(record.state),
757
+ };
758
+ }
759
+ function loadSessionEnvelopeFile(filePath) {
760
+ try {
761
+ const raw = fs.readFileSync(filePath, "utf-8");
762
+ let mtime;
763
+ try {
764
+ mtime = fs.statSync(filePath).mtime.toISOString();
765
+ }
766
+ catch {
767
+ mtime = new Date().toISOString();
768
+ }
769
+ return parseSessionEnvelope(JSON.parse(raw), {
770
+ recordedAt: mtime,
771
+ fileMtimeAt: mtime,
772
+ });
773
+ }
774
+ catch {
775
+ return null;
776
+ }
777
+ }
778
+ function messageRole(msg) {
779
+ return normalizeRole(msg.role);
780
+ }
781
+ function filterNonSystem(messages) {
782
+ return messages.filter((msg) => messageRole(msg) !== "system");
783
+ }
784
+ /**
785
+ * Compare two message arrays by their non-system messages only.
786
+ * Returns the number of matching non-system messages from the start.
787
+ * System messages (whose content changes every turn due to live world-state)
788
+ * are excluded so that prefix matching is not defeated by system prompt updates.
789
+ */
790
+ function findCommonPrefixLength(a, b) {
791
+ const aNonSys = filterNonSystem(a);
792
+ const bNonSys = filterNonSystem(b);
793
+ const max = Math.min(aNonSys.length, bNonSys.length);
794
+ for (let i = 0; i < max; i++) {
795
+ if (messageFingerprint(aNonSys[i]) !== messageFingerprint(bNonSys[i]))
796
+ return i;
797
+ }
798
+ return max;
799
+ }
800
+ function selectProjectedEventIds(currentMessages, currentEventIds, trimmedMessages) {
801
+ if (trimmedMessages.length === 0)
802
+ return [];
803
+ const trimmedFingerprints = trimmedMessages.map(messageFingerprint);
804
+ const result = [];
805
+ let needle = 0;
806
+ for (let i = 0; i < currentMessages.length && needle < trimmedFingerprints.length; i++) {
807
+ if (messageFingerprint(currentMessages[i]) !== trimmedFingerprints[needle])
808
+ continue;
809
+ result.push(currentEventIds[i]);
810
+ needle++;
811
+ }
812
+ return result;
813
+ }
814
+ function buildCanonicalSessionEnvelope(options) {
815
+ const existing = options.existing;
816
+ // Callers pass pre-sanitized messages + pre-captured ingress times.
817
+ const currentIngressTimes = options.currentIngressTimes ?? options.currentMessages.map(getIngressTime);
818
+ const previousMessages = options.previousMessages;
819
+ const currentMessages = options.currentMessages;
820
+ const trimmedMessages = options.trimmedMessages;
821
+ const previousProjectionIds = existing?.projection.eventIds.length
822
+ ? [...existing.projection.eventIds]
823
+ : existing?.events.map((event) => event.id) ?? [];
824
+ // Compare only non-system messages to find the common prefix.
825
+ // System messages change every turn (live world-state in system prompt)
826
+ // and must not defeat prefix matching of the actual conversation.
827
+ const nonSystemPrefix = findCommonPrefixLength(previousMessages, currentMessages);
828
+ // Build a lookup of non-system previous projection IDs.
829
+ const prevNonSystemIds = [];
830
+ for (let i = 0; i < previousMessages.length; i++) {
831
+ if (messageRole(previousMessages[i]) !== "system") {
832
+ prevNonSystemIds.push(previousProjectionIds[i]);
833
+ }
834
+ }
835
+ // Walk currentMessages and build currentEventIds + new events.
836
+ // Non-system messages within the prefix reuse old event IDs.
837
+ // System messages and post-prefix messages get new events.
838
+ const events = [...(existing?.events ?? [])];
839
+ const currentEventIds = [];
840
+ let nonSystemSeen = 0;
841
+ for (let i = 0; i < currentMessages.length; i++) {
842
+ const role = messageRole(currentMessages[i]);
843
+ const isSystem = role === "system";
844
+ const inPrefix = !isSystem && nonSystemSeen < nonSystemPrefix;
845
+ if (inPrefix) {
846
+ // Reuse existing event ID for this matched non-system message
847
+ currentEventIds.push(prevNonSystemIds[nonSystemSeen]);
848
+ nonSystemSeen++;
849
+ }
850
+ else if (isSystem && i < previousMessages.length
851
+ && messageRole(previousMessages[i]) === "system"
852
+ && messageFingerprint(currentMessages[i]) === messageFingerprint(previousMessages[i])) {
853
+ // System message at same position with identical content -- reuse event ID
854
+ currentEventIds.push(previousProjectionIds[i]);
855
+ }
856
+ else {
857
+ if (!isSystem)
858
+ nonSystemSeen++;
859
+ // Create a new event. Use nextEventSequence(events) instead of
860
+ // `events.length + 1` so that any gap (from pruning, archive replay,
861
+ // or self-heal dedup) cannot collide with an existing id.
862
+ const event = buildEventFromMessage(currentMessages[i], nextEventSequence(events), options.recordedAt, "live", null, null, currentIngressTimes[i]);
863
+ events.push(event);
864
+ currentEventIds.push(event.id);
865
+ }
866
+ }
867
+ const projectionEventIds = selectProjectedEventIds(currentMessages, currentEventIds, trimmedMessages);
868
+ // Prune events: only keep events whose IDs are in the projection.
869
+ // Events not in projection are returned as evicted for archiving.
870
+ const projectionIdSet = new Set(projectionEventIds);
871
+ const prunedEvents = events.filter((event) => projectionIdSet.has(event.id));
872
+ const evictedEvents = events.filter((event) => !projectionIdSet.has(event.id));
873
+ return {
874
+ envelope: {
875
+ version: 2,
876
+ events: prunedEvents,
877
+ projection: {
878
+ eventIds: projectionEventIds,
879
+ trimmed: projectionEventIds.length < currentEventIds.length,
880
+ maxTokens: options.projectionBasis.maxTokens,
881
+ contextMargin: options.projectionBasis.contextMargin,
882
+ inputTokens: options.projectionBasis.inputTokens,
883
+ projectedAt: options.recordedAt,
884
+ },
885
+ lastUsage: normalizeUsage(options.lastUsage),
886
+ state: normalizeContinuityState(options.state),
887
+ },
888
+ evictedEvents,
889
+ };
890
+ }
891
+ /**
892
+ * Load full event history from both the pruned envelope and the NDJSON archive.
893
+ * Returns all events deduplicated by id and sorted by sequence.
894
+ * Corrupted archive lines are silently skipped.
895
+ */
896
+ function loadFullEventHistory(sessPath) {
897
+ const envelope = loadSessionEnvelopeFile(sessPath);
898
+ if (!envelope)
899
+ return [];
900
+ const envelopeEvents = envelope.events;
901
+ const archivePath = sessPath.replace(/\.json$/, ".archive.ndjson");
902
+ let archiveEvents = [];
903
+ try {
904
+ const raw = fs.readFileSync(archivePath, "utf-8");
905
+ const lines = raw.split("\n");
906
+ for (const line of lines) {
907
+ const trimmed = line.trim();
908
+ if (trimmed.length === 0)
909
+ continue;
910
+ try {
911
+ const event = JSON.parse(trimmed);
912
+ if (event && typeof event.id === "string" && typeof event.sequence === "number") {
913
+ archiveEvents.push(event);
914
+ }
915
+ }
916
+ catch {
917
+ // Skip corrupted lines
918
+ }
919
+ }
920
+ }
921
+ catch {
922
+ // Archive file doesn't exist or can't be read -- that's fine
923
+ }
924
+ // Merge, deduplicate by id, sort by sequence
925
+ const seen = new Set();
926
+ const merged = [];
927
+ for (const event of [...archiveEvents, ...envelopeEvents]) {
928
+ if (seen.has(event.id))
929
+ continue;
930
+ seen.add(event.id);
931
+ merged.push(event);
932
+ }
933
+ merged.sort((a, b) => a.sequence - b.sequence);
934
+ return merged;
935
+ }
936
+ /**
937
+ * Append evicted events to an NDJSON archive file.
938
+ * The archive path is derived from the session path by replacing .json with .archive.ndjson.
939
+ * Each event is written as a single JSON line. The file is appended to, not overwritten.
940
+ * Failures are logged and swallowed -- archive write must never crash the persist path.
941
+ */
942
+ function appendEvictedToArchive(sessPath, evictedEvents) {
943
+ if (evictedEvents.length === 0)
944
+ return;
945
+ const archivePath = sessPath.replace(/\.json$/, ".archive.ndjson");
946
+ try {
947
+ const ndjson = evictedEvents.map((event) => JSON.stringify(event)).join("\n") + "\n";
948
+ fs.appendFileSync(archivePath, ndjson);
949
+ }
950
+ catch (err) {
951
+ (0, runtime_1.emitNervesEvent)({
952
+ level: "warn",
953
+ component: "heart",
954
+ event: "heart.archive_write_error",
955
+ message: "failed to write evicted events to archive",
956
+ meta: {
957
+ archivePath,
958
+ eventCount: evictedEvents.length,
959
+ /* v8 ignore next -- defensive: Node fs always throws Error instances @preserve */
960
+ error: err instanceof Error ? err.message : String(err),
961
+ },
962
+ });
963
+ }
964
+ }
965
+ function appendSyntheticAssistantEvent(envelope, content, recordedAt) {
966
+ // Use nextEventSequence(events) instead of `events.length + 1` so any gap
967
+ // (from pruning, archive replay, or self-heal dedup) cannot collide with
968
+ // an existing event id. Same fix pattern as line 1046.
969
+ const sequence = nextEventSequence(envelope.events);
970
+ const event = buildEventFromMessage({ role: "assistant", content }, sequence, recordedAt, "synthetic", null, null);
971
+ return {
972
+ ...envelope,
973
+ events: [...envelope.events, event],
974
+ projection: {
975
+ ...envelope.projection,
976
+ eventIds: [...envelope.projection.eventIds, event.id],
977
+ projectedAt: recordedAt,
978
+ trimmed: false,
979
+ },
980
+ };
981
+ }