@ouro.bot/cli 0.1.0-alpha.28 → 0.1.0-alpha.280

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 (267) hide show
  1. package/README.md +187 -191
  2. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
  4. package/changelog.json +1696 -0
  5. package/dist/arc/attention-types.js +8 -0
  6. package/dist/arc/cares.js +140 -0
  7. package/dist/arc/episodes.js +117 -0
  8. package/dist/arc/intentions.js +133 -0
  9. package/dist/arc/json-store.js +117 -0
  10. package/dist/arc/obligations.js +237 -0
  11. package/dist/arc/presence.js +185 -0
  12. package/dist/heart/active-work.js +832 -0
  13. package/dist/heart/agent-entry.js +37 -2
  14. package/dist/heart/auth/auth-flow.js +456 -0
  15. package/dist/heart/bridges/manager.js +358 -0
  16. package/dist/heart/bridges/state-machine.js +135 -0
  17. package/dist/heart/bridges/store.js +123 -0
  18. package/dist/heart/commitments.js +111 -0
  19. package/dist/heart/config-registry.js +304 -0
  20. package/dist/heart/config.js +63 -30
  21. package/dist/heart/core.js +644 -196
  22. package/dist/heart/cross-chat-delivery.js +131 -0
  23. package/dist/heart/daemon/agent-config-check.js +149 -0
  24. package/dist/heart/daemon/agent-discovery.js +79 -3
  25. package/dist/heart/daemon/agent-service.js +360 -0
  26. package/dist/heart/daemon/cadence.js +70 -0
  27. package/dist/heart/daemon/cli-defaults.js +574 -0
  28. package/dist/heart/daemon/cli-exec.js +1932 -0
  29. package/dist/heart/daemon/cli-parse.js +762 -0
  30. package/dist/heart/daemon/cli-render.js +503 -0
  31. package/dist/heart/daemon/cli-types.js +8 -0
  32. package/dist/heart/daemon/daemon-cli.js +29 -1155
  33. package/dist/heart/daemon/daemon-entry.js +234 -3
  34. package/dist/heart/daemon/daemon-health.js +136 -0
  35. package/dist/heart/daemon/daemon-runtime-sync.js +153 -12
  36. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  37. package/dist/heart/daemon/daemon.js +702 -57
  38. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  39. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  40. package/dist/heart/daemon/inner-status.js +89 -0
  41. package/dist/heart/daemon/launchd.js +46 -9
  42. package/dist/heart/daemon/log-tailer.js +82 -12
  43. package/dist/heart/daemon/logs-prune.js +105 -0
  44. package/dist/heart/daemon/message-router.js +17 -8
  45. package/dist/heart/daemon/os-cron-deps.js +134 -0
  46. package/dist/heart/daemon/ouro-bot-entry.js +1 -1
  47. package/dist/heart/daemon/process-manager.js +180 -0
  48. package/dist/heart/daemon/pulse.js +463 -0
  49. package/dist/heart/daemon/run-hooks.js +2 -0
  50. package/dist/heart/daemon/runtime-logging.js +67 -16
  51. package/dist/heart/daemon/runtime-metadata.js +101 -0
  52. package/dist/heart/daemon/runtime-mode.js +67 -0
  53. package/dist/heart/daemon/safe-mode.js +161 -0
  54. package/dist/heart/daemon/sense-manager.js +72 -3
  55. package/dist/heart/daemon/session-id-resolver.js +131 -0
  56. package/dist/heart/daemon/skill-management-installer.js +94 -0
  57. package/dist/heart/daemon/socket-client.js +307 -0
  58. package/dist/heart/daemon/task-scheduler.js +3 -25
  59. package/dist/heart/daemon/thoughts.js +510 -0
  60. package/dist/heart/delegation.js +62 -0
  61. package/dist/heart/habits/habit-migration.js +181 -0
  62. package/dist/heart/habits/habit-parser.js +119 -0
  63. package/dist/heart/habits/habit-scheduler.js +371 -0
  64. package/dist/heart/{daemon → hatch}/hatch-flow.js +30 -120
  65. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  66. package/dist/heart/{daemon → hatch}/specialist-prompt.js +10 -7
  67. package/dist/heart/{daemon → hatch}/specialist-tools.js +49 -3
  68. package/dist/heart/identity.js +163 -60
  69. package/dist/heart/kicks.js +2 -20
  70. package/dist/heart/mcp/mcp-server.js +653 -0
  71. package/dist/heart/migrate-config.js +127 -0
  72. package/dist/heart/model-capabilities.js +59 -0
  73. package/dist/heart/outlook/outlook-http.js +439 -0
  74. package/dist/heart/outlook/outlook-read.js +1618 -0
  75. package/dist/heart/outlook/outlook-render.js +1032 -0
  76. package/dist/heart/outlook/outlook-types.js +7 -0
  77. package/dist/heart/outlook/outlook-view.js +194 -0
  78. package/dist/heart/progress-story.js +42 -0
  79. package/dist/heart/provider-failover.js +88 -0
  80. package/dist/heart/provider-ping.js +162 -0
  81. package/dist/heart/providers/anthropic-token.js +163 -0
  82. package/dist/heart/providers/anthropic.js +169 -46
  83. package/dist/heart/providers/azure.js +98 -11
  84. package/dist/heart/providers/error-classification.js +63 -0
  85. package/dist/heart/providers/github-copilot.js +136 -0
  86. package/dist/heart/providers/minimax-vlm.js +189 -0
  87. package/dist/heart/providers/minimax.js +23 -5
  88. package/dist/heart/providers/openai-codex.js +33 -22
  89. package/dist/heart/session-activity.js +172 -0
  90. package/dist/heart/session-recall.js +216 -0
  91. package/dist/heart/start-of-turn-packet.js +330 -0
  92. package/dist/heart/streaming.js +36 -27
  93. package/dist/heart/sync.js +277 -0
  94. package/dist/heart/target-resolution.js +127 -0
  95. package/dist/heart/tempo.js +93 -0
  96. package/dist/heart/temporal-view.js +41 -0
  97. package/dist/heart/tool-activity-callbacks.js +36 -0
  98. package/dist/heart/tool-description.js +135 -0
  99. package/dist/heart/tool-loop.js +200 -0
  100. package/dist/heart/turn-context.js +358 -0
  101. package/dist/heart/turn-coordinator.js +28 -0
  102. package/dist/heart/versioning/ouro-bot-global-installer.js +128 -0
  103. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  104. package/dist/heart/{daemon → versioning}/ouro-path-installer.js +78 -35
  105. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  106. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  107. package/dist/heart/{daemon → versioning}/update-checker.js +12 -2
  108. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  109. package/dist/mind/associative-recall.js +126 -66
  110. package/dist/mind/bundle-manifest.js +8 -1
  111. package/dist/mind/context.js +125 -14
  112. package/dist/mind/{memory.js → diary.js} +68 -96
  113. package/dist/mind/embedding-provider.js +60 -0
  114. package/dist/mind/file-state.js +179 -0
  115. package/dist/mind/first-impressions.js +14 -1
  116. package/dist/mind/friends/channel.js +56 -0
  117. package/dist/mind/friends/group-context.js +144 -0
  118. package/dist/mind/friends/resolver.js +37 -0
  119. package/dist/mind/friends/store-file.js +58 -3
  120. package/dist/mind/friends/trust-explanation.js +74 -0
  121. package/dist/mind/friends/types.js +8 -0
  122. package/dist/mind/journal-index.js +161 -0
  123. package/dist/mind/obligation-steering.js +221 -0
  124. package/dist/mind/pending.js +76 -9
  125. package/dist/mind/prompt.js +874 -109
  126. package/dist/mind/scrutiny.js +173 -0
  127. package/dist/mind/token-estimate.js +8 -12
  128. package/dist/nerves/cli-logging.js +7 -1
  129. package/dist/nerves/coverage/audit.js +1 -1
  130. package/dist/nerves/coverage/file-completeness.js +50 -5
  131. package/dist/nerves/coverage/run-artifacts.js +1 -1
  132. package/dist/nerves/event-buffer.js +111 -0
  133. package/dist/nerves/index.js +220 -4
  134. package/dist/nerves/observation.js +20 -0
  135. package/dist/nerves/runtime.js +5 -1
  136. package/dist/outlook-ui/assets/index-Ck8agNeO.js +61 -0
  137. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  138. package/dist/outlook-ui/index.html +15 -0
  139. package/dist/repertoire/ado-client.js +15 -56
  140. package/dist/repertoire/ado-semantic.js +11 -10
  141. package/dist/repertoire/api-client.js +97 -0
  142. package/dist/repertoire/bitwarden-store.js +319 -0
  143. package/dist/repertoire/bw-installer.js +79 -0
  144. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  145. package/dist/repertoire/coding/context-pack.js +330 -0
  146. package/dist/repertoire/coding/feedback.js +197 -30
  147. package/dist/repertoire/coding/manager.js +149 -2
  148. package/dist/repertoire/coding/spawner.js +55 -9
  149. package/dist/repertoire/coding/tools.js +170 -7
  150. package/dist/repertoire/commerce-errors.js +109 -0
  151. package/dist/repertoire/commerce-self-test.js +156 -0
  152. package/dist/repertoire/credential-access.js +515 -0
  153. package/dist/repertoire/duffel-client.js +185 -0
  154. package/dist/repertoire/github-client.js +14 -55
  155. package/dist/repertoire/graph-client.js +11 -52
  156. package/dist/repertoire/guardrails.js +369 -0
  157. package/dist/repertoire/mcp-client.js +255 -0
  158. package/dist/repertoire/mcp-manager.js +305 -0
  159. package/dist/repertoire/mcp-tools.js +63 -0
  160. package/dist/repertoire/shell-sessions.js +133 -0
  161. package/dist/repertoire/skills.js +14 -23
  162. package/dist/repertoire/stripe-client.js +131 -0
  163. package/dist/repertoire/tasks/board.js +43 -5
  164. package/dist/repertoire/tasks/fix.js +182 -0
  165. package/dist/repertoire/tasks/index.js +28 -10
  166. package/dist/repertoire/tasks/lifecycle.js +2 -2
  167. package/dist/repertoire/tasks/parser.js +3 -2
  168. package/dist/repertoire/tasks/scanner.js +194 -37
  169. package/dist/repertoire/tasks/transitions.js +11 -28
  170. package/dist/repertoire/tools-base.js +43 -771
  171. package/dist/repertoire/tools-bluebubbles.js +201 -0
  172. package/dist/repertoire/tools-bridge.js +141 -0
  173. package/dist/repertoire/tools-config.js +185 -0
  174. package/dist/repertoire/tools-continuity.js +248 -0
  175. package/dist/repertoire/tools-credential.js +182 -0
  176. package/dist/repertoire/tools-files.js +342 -0
  177. package/dist/repertoire/tools-flight.js +224 -0
  178. package/dist/repertoire/tools-flow.js +62 -0
  179. package/dist/repertoire/tools-github.js +1 -7
  180. package/dist/repertoire/tools-memory.js +347 -0
  181. package/dist/repertoire/tools-session.js +739 -0
  182. package/dist/repertoire/tools-shell.js +120 -0
  183. package/dist/repertoire/tools-stripe.js +180 -0
  184. package/dist/repertoire/tools-surface.js +243 -0
  185. package/dist/repertoire/tools-teams.js +12 -62
  186. package/dist/repertoire/tools-travel.js +125 -0
  187. package/dist/repertoire/tools-user-profile.js +144 -0
  188. package/dist/repertoire/tools-vault.js +110 -0
  189. package/dist/repertoire/tools.js +155 -130
  190. package/dist/repertoire/travel-api-client.js +360 -0
  191. package/dist/repertoire/user-profile.js +118 -0
  192. package/dist/repertoire/vault-setup.js +241 -0
  193. package/dist/scripts/claude-code-hook.js +41 -0
  194. package/dist/scripts/claude-code-stop-hook.js +47 -0
  195. package/dist/senses/attention-queue.js +97 -0
  196. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  197. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +143 -9
  198. package/dist/senses/bluebubbles/entry.js +13 -0
  199. package/dist/senses/bluebubbles/inbound-log.js +110 -0
  200. package/dist/senses/bluebubbles/index.js +1410 -0
  201. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +140 -23
  202. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +20 -5
  203. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
  204. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  205. package/dist/senses/bluebubbles/session-cleanup.js +72 -0
  206. package/dist/senses/cli/bracketed-paste.js +82 -0
  207. package/dist/senses/cli/image-paste.js +211 -0
  208. package/dist/senses/cli/image-ref-navigation.js +75 -0
  209. package/dist/senses/cli/ink-app.js +156 -0
  210. package/dist/senses/cli/inline-diff.js +64 -0
  211. package/dist/senses/cli/input-keys.js +174 -0
  212. package/dist/senses/cli/kill-ring.js +86 -0
  213. package/dist/senses/cli/message-list.js +51 -0
  214. package/dist/senses/cli/ouro-tui.js +595 -0
  215. package/dist/senses/cli/spinner-imperative.js +135 -0
  216. package/dist/senses/cli/spinner.js +101 -0
  217. package/dist/senses/cli/status-line.js +60 -0
  218. package/dist/senses/cli/streaming-markdown.js +526 -0
  219. package/dist/senses/cli/tool-display.js +83 -0
  220. package/dist/senses/cli/tool-render.js +85 -0
  221. package/dist/senses/cli/tui-store.js +240 -0
  222. package/dist/senses/cli/virtual-list.js +35 -0
  223. package/dist/senses/cli-entry.js +1 -1
  224. package/dist/senses/cli-layout.js +187 -0
  225. package/dist/senses/cli.js +569 -246
  226. package/dist/senses/commands.js +65 -1
  227. package/dist/senses/continuity.js +94 -0
  228. package/dist/senses/habit-turn-message.js +108 -0
  229. package/dist/senses/inner-dialog-worker.js +112 -19
  230. package/dist/senses/inner-dialog.js +593 -85
  231. package/dist/senses/pipeline.js +552 -0
  232. package/dist/senses/shared-turn.js +199 -0
  233. package/dist/senses/surface-tool.js +68 -0
  234. package/dist/senses/teams.js +664 -165
  235. package/dist/senses/trust-gate.js +112 -2
  236. package/package.json +14 -7
  237. package/skills/agent-commerce.md +106 -0
  238. package/skills/browser-navigation.md +110 -0
  239. package/skills/commerce-setup-guide.md +116 -0
  240. package/skills/commerce-setup.md +84 -0
  241. package/skills/configure-dev-tools.md +81 -0
  242. package/skills/travel-planning.md +138 -0
  243. package/dist/heart/daemon/subagent-installer.js +0 -134
  244. package/dist/senses/bluebubbles-entry.js +0 -11
  245. package/dist/senses/bluebubbles.js +0 -421
  246. package/dist/senses/debug-activity.js +0 -108
  247. package/subagents/README.md +0 -73
  248. package/subagents/work-doer.md +0 -235
  249. package/subagents/work-merger.md +0 -618
  250. package/subagents/work-planner.md +0 -382
  251. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  252. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  253. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  254. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  255. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  256. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  257. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  258. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  259. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  260. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  261. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  262. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  263. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  264. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  265. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  266. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  267. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -36,7 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.trimMessages = trimMessages;
37
37
  exports.validateSessionMessages = validateSessionMessages;
38
38
  exports.repairSessionMessages = repairSessionMessages;
39
+ exports.migrateToolNames = migrateToolNames;
39
40
  exports.saveSession = saveSession;
41
+ exports.appendSyntheticAssistantMessage = appendSyntheticAssistantMessage;
40
42
  exports.loadSession = loadSession;
41
43
  exports.postTurn = postTurn;
42
44
  exports.deleteSession = deleteSession;
@@ -50,17 +52,17 @@ function buildTrimmableBlocks(messages) {
50
52
  let i = 0;
51
53
  while (i < messages.length) {
52
54
  const msg = messages[i];
53
- if (msg?.role === "system") {
55
+ if (msg.role === "system") {
54
56
  i++;
55
57
  continue;
56
58
  }
57
59
  // Tool coherence block: assistant message with tool_calls + immediately following tool results
58
- if (msg?.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
60
+ if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
59
61
  const indices = [i];
60
62
  i++;
61
63
  while (i < messages.length) {
62
64
  const next = messages[i];
63
- if (next?.role !== "tool")
65
+ if (next.role !== "tool")
64
66
  break;
65
67
  indices.push(i);
66
68
  i++;
@@ -78,13 +80,13 @@ function buildTrimmableBlocks(messages) {
78
80
  function getSystemMessageIndices(messages) {
79
81
  const indices = [];
80
82
  for (let i = 0; i < messages.length; i++) {
81
- if (messages[i]?.role === "system")
83
+ if (messages[i].role === "system")
82
84
  indices.push(i);
83
85
  }
84
86
  return indices;
85
87
  }
86
88
  function buildTrimmedMessages(messages, kept) {
87
- return messages.filter((m, idx) => m?.role === "system" || kept.has(idx));
89
+ return messages.filter((m, idx) => m.role === "system" || kept.has(idx));
88
90
  }
89
91
  function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
90
92
  const targetTokens = Math.floor(maxTokens * (1 - contextMargin / 100));
@@ -132,7 +134,7 @@ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
132
134
  let remaining = actualTokenCount;
133
135
  const kept = new Set();
134
136
  for (let i = 0; i < messages.length; i++) {
135
- if (messages[i]?.role !== "system")
137
+ if (messages[i].role !== "system")
136
138
  kept.add(i);
137
139
  }
138
140
  // Drop oldest blocks until we fall under target.
@@ -146,7 +148,7 @@ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
146
148
  let trimmed = buildTrimmedMessages(messages, kept);
147
149
  // If we're still above budget after dropping everything trimmable, preserve system only.
148
150
  if (remaining > targetTokens) {
149
- trimmed = messages.filter((m) => m?.role === "system");
151
+ trimmed = messages.filter((m) => m.role === "system");
150
152
  }
151
153
  const estimatedAfter = (0, token_estimate_1.estimateTokensForMessages)(trimmed);
152
154
  (0, runtime_1.emitNervesEvent)({
@@ -219,7 +221,7 @@ function repairSessionMessages(messages) {
219
221
  result.push(msg);
220
222
  }
221
223
  (0, runtime_1.emitNervesEvent)({
222
- level: "warn",
224
+ level: "info",
223
225
  event: "mind.session_invariant_repair",
224
226
  component: "mind",
225
227
  message: "repaired session invariant violations",
@@ -227,11 +229,79 @@ function repairSessionMessages(messages) {
227
229
  });
228
230
  return result;
229
231
  }
230
- function saveSession(filePath, messages, lastUsage) {
232
+ function stripOrphanedToolResults(messages) {
233
+ const validCallIds = new Set();
234
+ for (const msg of messages) {
235
+ if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls))
236
+ continue;
237
+ for (const toolCall of msg.tool_calls)
238
+ validCallIds.add(toolCall.id);
239
+ }
240
+ let removed = 0;
241
+ const repaired = messages.filter((msg) => {
242
+ if (msg.role !== "tool")
243
+ return true;
244
+ const keep = validCallIds.has(msg.tool_call_id);
245
+ if (!keep)
246
+ removed++;
247
+ return keep;
248
+ });
249
+ if (removed > 0) {
250
+ (0, runtime_1.emitNervesEvent)({
251
+ level: "info",
252
+ event: "mind.session_orphan_tool_result_repair",
253
+ component: "mind",
254
+ message: "removed orphaned tool results from session history",
255
+ meta: { removed },
256
+ });
257
+ }
258
+ return repaired;
259
+ }
260
+ // Tool renames that have shipped. Old names in session history confuse the
261
+ // model into calling tools that no longer exist. Applied on session load so
262
+ // the transcript uses the current vocabulary.
263
+ const TOOL_NAME_MIGRATIONS = {
264
+ final_answer: "settle",
265
+ no_response: "observe",
266
+ go_inward: "ponder",
267
+ descend: "ponder",
268
+ memory_save: "diary_write",
269
+ memory_search: "recall",
270
+ };
271
+ function migrateToolNames(messages) {
272
+ let migrated = 0;
273
+ const result = messages.map((msg) => {
274
+ if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0)
275
+ return msg;
276
+ let changed = false;
277
+ const updatedCalls = msg.tool_calls.map((tc) => {
278
+ if (tc.type !== "function")
279
+ return tc;
280
+ const newName = TOOL_NAME_MIGRATIONS[tc.function.name];
281
+ if (!newName)
282
+ return tc;
283
+ changed = true;
284
+ migrated++;
285
+ return { ...tc, function: { ...tc.function, name: newName } };
286
+ });
287
+ return changed ? { ...msg, tool_calls: updatedCalls } : msg;
288
+ });
289
+ if (migrated > 0) {
290
+ (0, runtime_1.emitNervesEvent)({
291
+ level: "info",
292
+ event: "mind.session_tool_name_migration",
293
+ component: "mind",
294
+ message: "migrated deprecated tool names in session history",
295
+ meta: { migrated },
296
+ });
297
+ }
298
+ return result;
299
+ }
300
+ function saveSession(filePath, messages, lastUsage, state) {
231
301
  const violations = validateSessionMessages(messages);
232
302
  if (violations.length > 0) {
233
303
  (0, runtime_1.emitNervesEvent)({
234
- level: "warn",
304
+ level: "info",
235
305
  event: "mind.session_invariant_violation",
236
306
  component: "mind",
237
307
  message: "session invariant violated on save",
@@ -239,12 +309,41 @@ function saveSession(filePath, messages, lastUsage) {
239
309
  });
240
310
  messages = repairSessionMessages(messages);
241
311
  }
312
+ messages = stripOrphanedToolResults(messages);
242
313
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
243
314
  const envelope = { version: 1, messages };
244
315
  if (lastUsage)
245
316
  envelope.lastUsage = lastUsage;
317
+ if (state?.mustResolveBeforeHandoff === true || typeof state?.lastFriendActivityAt === "string") {
318
+ envelope.state = {
319
+ ...(state?.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
320
+ ...(typeof state?.lastFriendActivityAt === "string" ? { lastFriendActivityAt: state.lastFriendActivityAt } : {}),
321
+ };
322
+ }
246
323
  fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
247
324
  }
325
+ function appendSyntheticAssistantMessage(filePath, content) {
326
+ try {
327
+ if (!fs.existsSync(filePath))
328
+ return false;
329
+ const raw = fs.readFileSync(filePath, "utf-8");
330
+ const data = JSON.parse(raw);
331
+ if (data.version !== 1 || !Array.isArray(data.messages))
332
+ return false;
333
+ data.messages.push({ role: "assistant", content });
334
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
335
+ (0, runtime_1.emitNervesEvent)({
336
+ component: "mind",
337
+ event: "mind.session_synthetic_message_appended",
338
+ message: "appended synthetic assistant message to session",
339
+ meta: { path: filePath, contentLength: content.length },
340
+ });
341
+ return true;
342
+ }
343
+ catch {
344
+ return false;
345
+ }
346
+ }
248
347
  function loadSession(filePath) {
249
348
  try {
250
349
  const raw = fs.readFileSync(filePath, "utf-8");
@@ -255,7 +354,7 @@ function loadSession(filePath) {
255
354
  const violations = validateSessionMessages(messages);
256
355
  if (violations.length > 0) {
257
356
  (0, runtime_1.emitNervesEvent)({
258
- level: "warn",
357
+ level: "info",
259
358
  event: "mind.session_invariant_violation",
260
359
  component: "mind",
261
360
  message: "session invariant violated on load",
@@ -263,13 +362,25 @@ function loadSession(filePath) {
263
362
  });
264
363
  messages = repairSessionMessages(messages);
265
364
  }
266
- return { messages, lastUsage: data.lastUsage };
365
+ messages = stripOrphanedToolResults(messages);
366
+ messages = migrateToolNames(messages);
367
+ const rawState = data?.state && typeof data.state === "object" && data.state !== null
368
+ ? data.state
369
+ : undefined;
370
+ const state = rawState && (rawState.mustResolveBeforeHandoff === true
371
+ || typeof rawState.lastFriendActivityAt === "string")
372
+ ? {
373
+ ...(rawState.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
374
+ ...(typeof rawState.lastFriendActivityAt === "string" ? { lastFriendActivityAt: rawState.lastFriendActivityAt } : {}),
375
+ }
376
+ : undefined;
377
+ return { messages, lastUsage: data.lastUsage, state };
267
378
  }
268
379
  catch {
269
380
  return null;
270
381
  }
271
382
  }
272
- function postTurn(messages, sessPath, usage, hooks) {
383
+ function postTurn(messages, sessPath, usage, hooks, state) {
273
384
  if (hooks?.beforeTrim) {
274
385
  try {
275
386
  hooks.beforeTrim([...messages]);
@@ -289,7 +400,7 @@ function postTurn(messages, sessPath, usage, hooks) {
289
400
  const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
290
401
  const trimmed = trimMessages(messages, maxTokens, contextMargin, usage?.input_tokens);
291
402
  messages.splice(0, messages.length, ...trimmed);
292
- saveSession(sessPath, messages, usage);
403
+ saveSession(sessPath, messages, usage, state);
293
404
  }
294
405
  function deleteSession(filePath) {
295
406
  try {
@@ -33,52 +33,24 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.__memoryTestUtils = void 0;
37
- exports.ensureMemoryStorePaths = ensureMemoryStorePaths;
38
- exports.appendFactsWithDedup = appendFactsWithDedup;
39
- exports.readMemoryFacts = readMemoryFacts;
40
- exports.saveMemoryFact = saveMemoryFact;
36
+ exports.ensureDiaryStorePaths = ensureDiaryStorePaths;
37
+ exports.appendEntriesWithDedup = appendEntriesWithDedup;
38
+ exports.resolveDiaryRoot = resolveDiaryRoot;
39
+ exports.readDiaryEntries = readDiaryEntries;
40
+ exports.saveDiaryEntry = saveDiaryEntry;
41
41
  exports.backfillEmbeddings = backfillEmbeddings;
42
- exports.searchMemoryFacts = searchMemoryFacts;
42
+ exports.searchDiaryEntries = searchDiaryEntries;
43
43
  const fs = __importStar(require("fs"));
44
44
  const path = __importStar(require("path"));
45
45
  const crypto_1 = require("crypto");
46
- const config_1 = require("../heart/config");
47
46
  const identity_1 = require("../heart/identity");
48
47
  const runtime_1 = require("../nerves/runtime");
48
+ const associative_recall_1 = require("./associative-recall");
49
+ const embedding_provider_1 = require("./embedding-provider");
49
50
  const DEDUP_THRESHOLD = 0.6;
51
+ const SEMANTIC_DEDUP_THRESHOLD = 0.95;
50
52
  const ENTITY_TOKEN = /[a-z0-9]+/g;
51
- const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
52
- class OpenAIEmbeddingProvider {
53
- apiKey;
54
- model;
55
- constructor(apiKey, model = DEFAULT_EMBEDDING_MODEL) {
56
- this.apiKey = apiKey;
57
- this.model = model;
58
- }
59
- async embed(texts) {
60
- const response = await fetch("https://api.openai.com/v1/embeddings", {
61
- method: "POST",
62
- headers: {
63
- Authorization: `Bearer ${this.apiKey}`,
64
- "Content-Type": "application/json",
65
- },
66
- body: JSON.stringify({
67
- model: this.model,
68
- input: texts,
69
- }),
70
- });
71
- if (!response.ok) {
72
- throw new Error(`embedding request failed: ${response.status} ${response.statusText}`);
73
- }
74
- const payload = (await response.json());
75
- if (!payload.data || payload.data.length !== texts.length) {
76
- throw new Error("embedding response missing expected vectors");
77
- }
78
- return payload.data.map((entry) => entry.embedding);
79
- }
80
- }
81
- function ensureMemoryStorePaths(rootDir) {
53
+ function ensureDiaryStorePaths(rootDir) {
82
54
  const factsPath = path.join(rootDir, "facts.jsonl");
83
55
  const entitiesPath = path.join(rootDir, "entities.json");
84
56
  const dailyDir = path.join(rootDir, "daily");
@@ -90,8 +62,8 @@ function ensureMemoryStorePaths(rootDir) {
90
62
  fs.writeFileSync(entitiesPath, "{}\n", "utf8");
91
63
  (0, runtime_1.emitNervesEvent)({
92
64
  component: "mind",
93
- event: "mind.memory_paths_ready",
94
- message: "memory store paths ready",
65
+ event: "mind.diary_paths_ready",
66
+ message: "diary store paths ready",
95
67
  meta: { rootDir },
96
68
  });
97
69
  return { rootDir, factsPath, entitiesPath, dailyDir };
@@ -114,15 +86,22 @@ function overlapScore(left, right) {
114
86
  }
115
87
  return common / Math.min(leftWords.size, rightWords.size);
116
88
  }
117
- function readExistingFacts(factsPath) {
89
+ function readExistingEntries(factsPath) {
118
90
  if (!fs.existsSync(factsPath))
119
91
  return [];
120
92
  const raw = fs.readFileSync(factsPath, "utf8").trim();
121
93
  if (!raw)
122
94
  return [];
123
- return raw
124
- .split("\n")
125
- .map((line) => JSON.parse(line));
95
+ const facts = [];
96
+ for (const line of raw.split("\n")) {
97
+ try {
98
+ facts.push(JSON.parse(line));
99
+ }
100
+ catch {
101
+ // Skip corrupt lines (e.g. partial write from a crash).
102
+ }
103
+ }
104
+ return facts;
126
105
  }
127
106
  function readEntityIndex(entitiesPath) {
128
107
  if (!fs.existsSync(entitiesPath))
@@ -170,13 +149,24 @@ function appendDailyFact(dailyDir, fact) {
170
149
  const dayPath = path.join(dailyDir, `${day}.jsonl`);
171
150
  fs.appendFileSync(dayPath, `${JSON.stringify(fact)}\n`, "utf8");
172
151
  }
173
- function appendFactsWithDedup(stores, incoming) {
174
- const existing = readExistingFacts(stores.factsPath);
152
+ function appendEntriesWithDedup(stores, incoming, options) {
153
+ const existing = readExistingEntries(stores.factsPath);
175
154
  const all = [...existing];
176
155
  let added = 0;
177
156
  let skipped = 0;
157
+ const semanticThreshold = options?.semanticThreshold;
178
158
  for (const fact of incoming) {
179
- const duplicate = all.some((prior) => overlapScore(prior.text, fact.text) > DEDUP_THRESHOLD);
159
+ const duplicate = all.some((prior) => {
160
+ if (overlapScore(prior.text, fact.text) > DEDUP_THRESHOLD)
161
+ return true;
162
+ if (semanticThreshold !== undefined &&
163
+ Array.isArray(fact.embedding) && fact.embedding.length > 0 &&
164
+ Array.isArray(prior.embedding) && prior.embedding.length > 0 &&
165
+ fact.embedding.length === prior.embedding.length) {
166
+ return (0, associative_recall_1.cosineSimilarity)(fact.embedding, prior.embedding) > semanticThreshold;
167
+ }
168
+ return false;
169
+ });
180
170
  if (duplicate) {
181
171
  skipped++;
182
172
  continue;
@@ -189,44 +179,20 @@ function appendFactsWithDedup(stores, incoming) {
189
179
  }
190
180
  (0, runtime_1.emitNervesEvent)({
191
181
  component: "mind",
192
- event: "mind.memory_write",
193
- message: "memory write completed",
182
+ event: "mind.diary_write",
183
+ message: "diary write completed",
194
184
  meta: { added, skipped },
195
185
  });
196
186
  return { added, skipped };
197
187
  }
198
- function cosineSimilarity(left, right) {
199
- if (left.length === 0 || right.length === 0 || left.length !== right.length)
200
- return 0;
201
- let dot = 0;
202
- let leftNorm = 0;
203
- let rightNorm = 0;
204
- for (let i = 0; i < left.length; i += 1) {
205
- dot += left[i] * right[i];
206
- leftNorm += left[i] * left[i];
207
- rightNorm += right[i] * right[i];
208
- }
209
- if (leftNorm === 0 || rightNorm === 0)
210
- return 0;
211
- return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
212
- }
213
- exports.__memoryTestUtils = {
214
- cosineSimilarity,
215
- };
216
- function createDefaultEmbeddingProvider() {
217
- const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
218
- if (!apiKey)
219
- return null;
220
- return new OpenAIEmbeddingProvider(apiKey);
221
- }
222
188
  async function buildEmbedding(text, embeddingProvider) {
223
- const provider = embeddingProvider ?? createDefaultEmbeddingProvider();
189
+ const provider = embeddingProvider ?? (0, embedding_provider_1.createDefaultEmbeddingProvider)();
224
190
  if (!provider) {
225
191
  (0, runtime_1.emitNervesEvent)({
226
192
  level: "warn",
227
193
  component: "mind",
228
- event: "mind.memory_embedding_unavailable",
229
- message: "embedding provider unavailable for memory write",
194
+ event: "mind.diary_embedding_unavailable",
195
+ message: "embedding provider unavailable for diary write",
230
196
  meta: { reason: "missing_openai_embeddings_key" },
231
197
  });
232
198
  return [];
@@ -239,8 +205,8 @@ async function buildEmbedding(text, embeddingProvider) {
239
205
  (0, runtime_1.emitNervesEvent)({
240
206
  level: "warn",
241
207
  component: "mind",
242
- event: "mind.memory_embedding_unavailable",
243
- message: "embedding provider unavailable for memory write",
208
+ event: "mind.diary_embedding_unavailable",
209
+ message: "embedding provider unavailable for diary write",
244
210
  meta: {
245
211
  reason: error instanceof Error ? error.message : String(error),
246
212
  },
@@ -248,13 +214,19 @@ async function buildEmbedding(text, embeddingProvider) {
248
214
  return [];
249
215
  }
250
216
  }
251
- function readMemoryFacts(memoryRoot = path.join((0, identity_1.getAgentRoot)(), "psyche", "memory")) {
252
- return readExistingFacts(path.join(memoryRoot, "facts.jsonl"));
217
+ function resolveDiaryRoot(explicitRoot) {
218
+ if (explicitRoot)
219
+ return explicitRoot;
220
+ const agentRoot = (0, identity_1.getAgentRoot)();
221
+ return path.join(agentRoot, "diary");
222
+ }
223
+ function readDiaryEntries(diaryRoot) {
224
+ return readExistingEntries(path.join(resolveDiaryRoot(diaryRoot), "facts.jsonl"));
253
225
  }
254
- async function saveMemoryFact(options) {
226
+ async function saveDiaryEntry(options) {
255
227
  const text = options.text.trim();
256
- const memoryRoot = options.memoryRoot ?? path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
257
- const stores = ensureMemoryStorePaths(memoryRoot);
228
+ const diaryRoot = resolveDiaryRoot(options.diaryRoot);
229
+ const stores = ensureDiaryStorePaths(diaryRoot);
258
230
  const embedding = await buildEmbedding(text, options.embeddingProvider);
259
231
  const fact = {
260
232
  id: options.idFactory ? options.idFactory() : (0, crypto_1.randomUUID)(),
@@ -264,23 +236,23 @@ async function saveMemoryFact(options) {
264
236
  createdAt: (options.now ?? (() => new Date()))().toISOString(),
265
237
  embedding,
266
238
  };
267
- return appendFactsWithDedup(stores, [fact]);
239
+ return appendEntriesWithDedup(stores, [fact], { semanticThreshold: SEMANTIC_DEDUP_THRESHOLD });
268
240
  }
269
241
  async function backfillEmbeddings(options) {
270
- const memoryRoot = options?.memoryRoot ?? path.join((0, identity_1.getAgentRoot)(), "psyche", "memory");
271
- const factsPath = path.join(memoryRoot, "facts.jsonl");
242
+ const diaryRoot = resolveDiaryRoot(options?.diaryRoot);
243
+ const factsPath = path.join(diaryRoot, "facts.jsonl");
272
244
  if (!fs.existsSync(factsPath))
273
245
  return { total: 0, backfilled: 0, failed: 0 };
274
- const facts = readExistingFacts(factsPath);
246
+ const facts = readExistingEntries(factsPath);
275
247
  const needsEmbedding = facts.filter((f) => !Array.isArray(f.embedding) || f.embedding.length === 0);
276
248
  if (needsEmbedding.length === 0)
277
249
  return { total: facts.length, backfilled: 0, failed: 0 };
278
- const provider = options?.embeddingProvider ?? createDefaultEmbeddingProvider();
250
+ const provider = options?.embeddingProvider ?? (0, embedding_provider_1.createDefaultEmbeddingProvider)();
279
251
  if (!provider) {
280
252
  (0, runtime_1.emitNervesEvent)({
281
253
  level: "warn",
282
254
  component: "mind",
283
- event: "mind.memory_backfill_skipped",
255
+ event: "mind.diary_backfill_skipped",
284
256
  message: "embedding provider unavailable for backfill",
285
257
  meta: { needsEmbedding: needsEmbedding.length },
286
258
  });
@@ -306,7 +278,7 @@ async function backfillEmbeddings(options) {
306
278
  (0, runtime_1.emitNervesEvent)({
307
279
  level: "warn",
308
280
  component: "mind",
309
- event: "mind.memory_backfill_batch_error",
281
+ event: "mind.diary_backfill_batch_error",
310
282
  message: "embedding backfill batch failed",
311
283
  meta: {
312
284
  batchStart: i,
@@ -321,7 +293,7 @@ async function backfillEmbeddings(options) {
321
293
  fs.writeFileSync(factsPath, lines, "utf8");
322
294
  (0, runtime_1.emitNervesEvent)({
323
295
  component: "mind",
324
- event: "mind.memory_backfill_complete",
296
+ event: "mind.diary_backfill_complete",
325
297
  message: "embedding backfill completed",
326
298
  meta: { total: facts.length, backfilled, failed },
327
299
  });
@@ -341,7 +313,7 @@ function uniqueFacts(facts) {
341
313
  }
342
314
  return unique;
343
315
  }
344
- async function searchMemoryFacts(query, facts, embeddingProvider) {
316
+ async function searchDiaryEntries(query, facts, embeddingProvider) {
345
317
  const trimmed = query.trim();
346
318
  if (!trimmed)
347
319
  return [];
@@ -351,7 +323,7 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
351
323
  if (embeddedFacts.length === 0) {
352
324
  return substringFallback();
353
325
  }
354
- const provider = embeddingProvider ?? createDefaultEmbeddingProvider();
326
+ const provider = embeddingProvider ?? (0, embedding_provider_1.createDefaultEmbeddingProvider)();
355
327
  if (!provider) {
356
328
  return substringFallback();
357
329
  }
@@ -365,7 +337,7 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
365
337
  .filter((fact) => fact.embedding.length === queryEmbedding.length)
366
338
  .map((fact) => ({
367
339
  fact,
368
- score: cosineSimilarity(queryEmbedding, fact.embedding),
340
+ score: (0, associative_recall_1.cosineSimilarity)(queryEmbedding, fact.embedding),
369
341
  }))
370
342
  .filter((entry) => entry.score > 0)
371
343
  .sort((left, right) => right.score - left.score)
@@ -377,8 +349,8 @@ async function searchMemoryFacts(query, facts, embeddingProvider) {
377
349
  (0, runtime_1.emitNervesEvent)({
378
350
  level: "warn",
379
351
  component: "mind",
380
- event: "mind.memory_embedding_unavailable",
381
- message: "embedding provider unavailable for memory search",
352
+ event: "mind.diary_embedding_unavailable",
353
+ message: "embedding provider unavailable for diary search",
382
354
  meta: {
383
355
  reason: error instanceof Error ? error.message : String(error),
384
356
  },
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * Shared OpenAI embedding provider.
4
+ *
5
+ * Both diary.ts and associative-recall.ts need to call the OpenAI embeddings
6
+ * API. This module provides the shared implementation so neither duplicates
7
+ * the fetch logic.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.OpenAIEmbeddingProvider = void 0;
11
+ exports.createDefaultEmbeddingProvider = createDefaultEmbeddingProvider;
12
+ const config_1 = require("../heart/config");
13
+ const runtime_1 = require("../nerves/runtime");
14
+ const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
15
+ class OpenAIEmbeddingProvider {
16
+ apiKey;
17
+ model;
18
+ constructor(apiKey, model = DEFAULT_EMBEDDING_MODEL) {
19
+ this.apiKey = apiKey;
20
+ this.model = model;
21
+ }
22
+ async embed(texts) {
23
+ const response = await fetch("https://api.openai.com/v1/embeddings", {
24
+ method: "POST",
25
+ headers: {
26
+ Authorization: `Bearer ${this.apiKey}`,
27
+ "Content-Type": "application/json",
28
+ },
29
+ body: JSON.stringify({
30
+ model: this.model,
31
+ input: texts,
32
+ }),
33
+ });
34
+ if (!response.ok) {
35
+ throw new Error(`embedding request failed: ${response.status} ${response.statusText}`);
36
+ }
37
+ const payload = (await response.json());
38
+ if (!payload.data || payload.data.length !== texts.length) {
39
+ throw new Error("embedding response missing expected vectors");
40
+ }
41
+ return payload.data.map((entry) => entry.embedding);
42
+ }
43
+ }
44
+ exports.OpenAIEmbeddingProvider = OpenAIEmbeddingProvider;
45
+ /**
46
+ * Create a default embedding provider from the configured API key.
47
+ * Returns null if no key is configured.
48
+ */
49
+ function createDefaultEmbeddingProvider() {
50
+ const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
51
+ if (!apiKey)
52
+ return null;
53
+ (0, runtime_1.emitNervesEvent)({
54
+ component: "mind",
55
+ event: "mind.embedding_provider_created",
56
+ message: "default embedding provider created",
57
+ meta: { model: DEFAULT_EMBEDDING_MODEL },
58
+ });
59
+ return new OpenAIEmbeddingProvider(apiKey);
60
+ }