@ouro.bot/cli 0.1.0-alpha.37 → 0.1.0-alpha.370

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 (329) hide show
  1. package/README.md +106 -14
  2. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
  3. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
  4. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  5. package/changelog.json +2222 -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 +832 -0
  16. package/dist/heart/agent-entry.js +37 -2
  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 +378 -0
  28. package/dist/heart/bridges/manager.js +358 -0
  29. package/dist/heart/bridges/state-machine.js +135 -0
  30. package/dist/heart/bridges/store.js +123 -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 +107 -61
  35. package/dist/heart/core.js +803 -259
  36. package/dist/heart/cross-chat-delivery.js +131 -0
  37. package/dist/heart/daemon/agent-config-check.js +382 -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 +205 -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 +538 -0
  44. package/dist/heart/daemon/cli-exec.js +3081 -0
  45. package/dist/heart/daemon/cli-help.js +312 -0
  46. package/dist/heart/daemon/cli-parse.js +1023 -0
  47. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  48. package/dist/heart/daemon/cli-render.js +560 -0
  49. package/dist/heart/daemon/cli-types.js +8 -0
  50. package/dist/heart/daemon/daemon-cli.js +29 -1171
  51. package/dist/heart/daemon/daemon-entry.js +356 -3
  52. package/dist/heart/daemon/daemon-health.js +141 -0
  53. package/dist/heart/daemon/daemon-runtime-sync.js +157 -12
  54. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  55. package/dist/heart/daemon/daemon.js +757 -58
  56. package/dist/heart/daemon/doctor-types.js +8 -0
  57. package/dist/heart/daemon/doctor.js +445 -0
  58. package/dist/heart/daemon/health-monitor.js +79 -1
  59. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  60. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  61. package/dist/heart/daemon/http-health-probe.js +80 -0
  62. package/dist/heart/daemon/inner-status.js +89 -0
  63. package/dist/heart/daemon/interactive-repair.js +148 -0
  64. package/dist/heart/daemon/launchd.js +46 -9
  65. package/dist/heart/daemon/log-tailer.js +82 -12
  66. package/dist/heart/daemon/logs-prune.js +105 -0
  67. package/dist/heart/daemon/message-router.js +17 -8
  68. package/dist/heart/daemon/os-cron-deps.js +134 -0
  69. package/dist/heart/daemon/ouro-bot-entry.js +1 -1
  70. package/dist/heart/daemon/process-manager.js +201 -0
  71. package/dist/heart/daemon/provider-discovery.js +113 -0
  72. package/dist/heart/daemon/pulse.js +475 -0
  73. package/dist/heart/daemon/run-hooks.js +2 -0
  74. package/dist/heart/daemon/runtime-logging.js +67 -16
  75. package/dist/heart/daemon/runtime-metadata.js +101 -0
  76. package/dist/heart/daemon/runtime-mode.js +67 -0
  77. package/dist/heart/daemon/safe-mode.js +161 -0
  78. package/dist/heart/daemon/sense-manager.js +72 -3
  79. package/dist/heart/daemon/session-id-resolver.js +131 -0
  80. package/dist/heart/daemon/skill-management-installer.js +94 -0
  81. package/dist/heart/daemon/socket-client.js +307 -0
  82. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  83. package/dist/heart/daemon/startup-tui.js +237 -0
  84. package/dist/heart/daemon/task-scheduler.js +3 -25
  85. package/dist/heart/daemon/thoughts.js +510 -0
  86. package/dist/heart/daemon/up-progress.js +135 -0
  87. package/dist/heart/delegation.js +62 -0
  88. package/dist/heart/habits/habit-migration.js +181 -0
  89. package/dist/heart/habits/habit-parser.js +140 -0
  90. package/dist/heart/habits/habit-scheduler.js +371 -0
  91. package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -126
  92. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  93. package/dist/heart/{daemon → hatch}/specialist-prompt.js +11 -8
  94. package/dist/heart/{daemon → hatch}/specialist-tools.js +77 -11
  95. package/dist/heart/identity.js +154 -59
  96. package/dist/heart/kept-notes.js +357 -0
  97. package/dist/heart/kicks.js +2 -20
  98. package/dist/heart/machine-identity.js +161 -0
  99. package/dist/heart/mcp/mcp-server.js +653 -0
  100. package/dist/heart/migrate-config.js +100 -0
  101. package/dist/heart/model-capabilities.js +59 -0
  102. package/dist/heart/outlook/outlook-http-hooks.js +64 -0
  103. package/dist/heart/outlook/outlook-http-response.js +7 -0
  104. package/dist/heart/outlook/outlook-http-routes.js +232 -0
  105. package/dist/heart/outlook/outlook-http-static.js +99 -0
  106. package/dist/heart/outlook/outlook-http-transport.js +116 -0
  107. package/dist/heart/outlook/outlook-http.js +99 -0
  108. package/dist/heart/outlook/outlook-read.js +28 -0
  109. package/dist/heart/outlook/outlook-types.js +27 -0
  110. package/dist/heart/outlook/outlook-view.js +195 -0
  111. package/dist/heart/outlook/readers/agent-machine.js +359 -0
  112. package/dist/heart/outlook/readers/continuity-readers.js +332 -0
  113. package/dist/heart/outlook/readers/runtime-readers.js +660 -0
  114. package/dist/heart/outlook/readers/sessions.js +232 -0
  115. package/dist/heart/outlook/readers/shared.js +111 -0
  116. package/dist/heart/platform.js +81 -0
  117. package/dist/heart/progress-story.js +42 -0
  118. package/dist/heart/provider-attempt.js +133 -0
  119. package/dist/heart/provider-binding-resolver.js +239 -0
  120. package/dist/heart/provider-credentials.js +379 -0
  121. package/dist/heart/provider-failover.js +266 -0
  122. package/dist/heart/provider-models.js +81 -0
  123. package/dist/heart/provider-ping.js +237 -0
  124. package/dist/heart/provider-state.js +216 -0
  125. package/dist/heart/provider-visibility.js +180 -0
  126. package/dist/heart/providers/anthropic-token.js +131 -0
  127. package/dist/heart/providers/anthropic.js +193 -55
  128. package/dist/heart/providers/azure.js +103 -12
  129. package/dist/heart/providers/error-classification.js +63 -0
  130. package/dist/heart/providers/github-copilot.js +145 -0
  131. package/dist/heart/providers/minimax-vlm.js +189 -0
  132. package/dist/heart/providers/minimax.js +29 -7
  133. package/dist/heart/providers/openai-codex.js +39 -29
  134. package/dist/heart/session-activity.js +190 -0
  135. package/dist/heart/session-events.js +855 -0
  136. package/dist/heart/session-transcript.js +167 -0
  137. package/dist/heart/start-of-turn-packet.js +345 -0
  138. package/dist/heart/streaming.js +36 -27
  139. package/dist/heart/sync.js +332 -0
  140. package/dist/heart/target-resolution.js +127 -0
  141. package/dist/heart/tempo.js +93 -0
  142. package/dist/heart/temporal-view.js +41 -0
  143. package/dist/heart/tool-activity-callbacks.js +36 -0
  144. package/dist/heart/tool-description.js +135 -0
  145. package/dist/heart/tool-friction.js +55 -0
  146. package/dist/heart/tool-loop.js +200 -0
  147. package/dist/heart/turn-context.js +362 -0
  148. package/dist/heart/turn-coordinator.js +28 -0
  149. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +1 -1
  150. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  151. package/dist/heart/versioning/ouro-path-installer.js +301 -0
  152. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  153. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  154. package/dist/heart/{daemon → versioning}/update-checker.js +12 -2
  155. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  156. package/dist/mind/bundle-manifest.js +7 -1
  157. package/dist/mind/context.js +141 -94
  158. package/dist/mind/diary-integrity.js +60 -0
  159. package/dist/mind/{memory.js → diary.js} +84 -96
  160. package/dist/mind/embedding-provider.js +60 -0
  161. package/dist/mind/file-state.js +179 -0
  162. package/dist/mind/first-impressions.js +14 -1
  163. package/dist/mind/friends/channel.js +56 -0
  164. package/dist/mind/friends/group-context.js +144 -0
  165. package/dist/mind/friends/resolver.js +38 -1
  166. package/dist/mind/friends/store-file.js +58 -3
  167. package/dist/mind/friends/trust-explanation.js +74 -0
  168. package/dist/mind/friends/types.js +9 -1
  169. package/dist/mind/journal-index.js +161 -0
  170. package/dist/mind/note-search.js +268 -0
  171. package/dist/mind/obligation-steering.js +221 -0
  172. package/dist/mind/pending.js +74 -7
  173. package/dist/mind/prompt-refresh.js +3 -2
  174. package/dist/mind/prompt.js +1030 -118
  175. package/dist/mind/provenance-trust.js +26 -0
  176. package/dist/mind/scrutiny.js +173 -0
  177. package/dist/mind/token-estimate.js +8 -12
  178. package/dist/nerves/cli-logging.js +7 -1
  179. package/dist/nerves/coverage/audit-rules.js +15 -6
  180. package/dist/nerves/coverage/audit.js +28 -2
  181. package/dist/nerves/coverage/cli.js +1 -1
  182. package/dist/nerves/coverage/file-completeness.js +83 -5
  183. package/dist/nerves/coverage/run-artifacts.js +1 -1
  184. package/dist/nerves/event-buffer.js +111 -0
  185. package/dist/nerves/index.js +224 -4
  186. package/dist/nerves/observation.js +20 -0
  187. package/dist/nerves/redact.js +79 -0
  188. package/dist/nerves/runtime.js +5 -1
  189. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  190. package/dist/outlook-ui/assets/index-xTdv64BV.js +61 -0
  191. package/dist/outlook-ui/index.html +15 -0
  192. package/dist/repertoire/ado-client.js +15 -56
  193. package/dist/repertoire/ado-semantic.js +11 -10
  194. package/dist/repertoire/api-client.js +97 -0
  195. package/dist/repertoire/bitwarden-store.js +365 -0
  196. package/dist/repertoire/bundle-templates.js +72 -0
  197. package/dist/repertoire/bw-installer.js +79 -0
  198. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  199. package/dist/repertoire/coding/context-pack.js +330 -0
  200. package/dist/repertoire/coding/feedback.js +197 -30
  201. package/dist/repertoire/coding/manager.js +158 -9
  202. package/dist/repertoire/coding/spawner.js +55 -9
  203. package/dist/repertoire/coding/tools.js +170 -7
  204. package/dist/repertoire/commerce-errors.js +109 -0
  205. package/dist/repertoire/commerce-self-test.js +156 -0
  206. package/dist/repertoire/credential-access.js +107 -0
  207. package/dist/repertoire/duffel-client.js +185 -0
  208. package/dist/repertoire/github-client.js +14 -55
  209. package/dist/repertoire/graph-client.js +11 -52
  210. package/dist/repertoire/guardrails.js +375 -0
  211. package/dist/repertoire/mcp-client.js +255 -0
  212. package/dist/repertoire/mcp-manager.js +305 -0
  213. package/dist/repertoire/mcp-tools.js +63 -0
  214. package/dist/repertoire/shell-sessions.js +133 -0
  215. package/dist/repertoire/skills.js +15 -24
  216. package/dist/repertoire/stripe-client.js +131 -0
  217. package/dist/repertoire/tasks/board.js +43 -5
  218. package/dist/repertoire/tasks/fix.js +182 -0
  219. package/dist/repertoire/tasks/index.js +28 -10
  220. package/dist/repertoire/tasks/lifecycle.js +2 -2
  221. package/dist/repertoire/tasks/parser.js +3 -2
  222. package/dist/repertoire/tasks/scanner.js +194 -37
  223. package/dist/repertoire/tasks/transitions.js +16 -79
  224. package/dist/repertoire/tool-results.js +29 -0
  225. package/dist/repertoire/tools-attachments.js +317 -0
  226. package/dist/repertoire/tools-base.js +45 -771
  227. package/dist/repertoire/tools-bluebubbles.js +1 -0
  228. package/dist/repertoire/tools-bridge.js +141 -0
  229. package/dist/repertoire/tools-bundle.js +984 -0
  230. package/dist/repertoire/tools-config.js +185 -0
  231. package/dist/repertoire/tools-continuity.js +248 -0
  232. package/dist/repertoire/tools-credential.js +182 -0
  233. package/dist/repertoire/tools-files.js +342 -0
  234. package/dist/repertoire/tools-flight.js +224 -0
  235. package/dist/repertoire/tools-flow.js +105 -0
  236. package/dist/repertoire/tools-github.js +1 -7
  237. package/dist/repertoire/tools-notes.js +376 -0
  238. package/dist/repertoire/tools-session.js +739 -0
  239. package/dist/repertoire/tools-shell.js +120 -0
  240. package/dist/repertoire/tools-stripe.js +180 -0
  241. package/dist/repertoire/tools-surface.js +243 -0
  242. package/dist/repertoire/tools-teams.js +12 -62
  243. package/dist/repertoire/tools-travel.js +125 -0
  244. package/dist/repertoire/tools-user-profile.js +144 -0
  245. package/dist/repertoire/tools-vault.js +40 -0
  246. package/dist/repertoire/tools.js +144 -138
  247. package/dist/repertoire/travel-api-client.js +360 -0
  248. package/dist/repertoire/user-profile.js +118 -0
  249. package/dist/repertoire/vault-setup.js +241 -0
  250. package/dist/repertoire/vault-unlock.js +359 -0
  251. package/dist/scripts/claude-code-hook.js +41 -0
  252. package/dist/scripts/claude-code-stop-hook.js +47 -0
  253. package/dist/senses/attention-queue.js +116 -0
  254. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  255. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  256. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +260 -9
  257. package/dist/senses/bluebubbles/entry.js +13 -0
  258. package/dist/senses/bluebubbles/inbound-log.js +113 -0
  259. package/dist/senses/bluebubbles/index.js +1620 -0
  260. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
  261. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +43 -12
  262. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
  263. package/dist/senses/bluebubbles/replay.js +129 -0
  264. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  265. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  266. package/dist/senses/cli/bracketed-paste.js +82 -0
  267. package/dist/senses/cli/image-paste.js +287 -0
  268. package/dist/senses/cli/image-ref-navigation.js +75 -0
  269. package/dist/senses/cli/ink-app.js +156 -0
  270. package/dist/senses/cli/inline-diff.js +64 -0
  271. package/dist/senses/cli/input-keys.js +174 -0
  272. package/dist/senses/cli/kill-ring.js +86 -0
  273. package/dist/senses/cli/message-list.js +51 -0
  274. package/dist/senses/cli/ouro-tui.js +605 -0
  275. package/dist/senses/cli/spinner-imperative.js +135 -0
  276. package/dist/senses/cli/spinner.js +101 -0
  277. package/dist/senses/cli/status-line.js +60 -0
  278. package/dist/senses/cli/streaming-markdown.js +526 -0
  279. package/dist/senses/cli/tool-display.js +83 -0
  280. package/dist/senses/cli/tool-render.js +85 -0
  281. package/dist/senses/cli/tui-store.js +240 -0
  282. package/dist/senses/cli/virtual-list.js +35 -0
  283. package/dist/senses/cli-entry.js +1 -1
  284. package/dist/senses/cli-layout.js +187 -0
  285. package/dist/senses/cli.js +588 -250
  286. package/dist/senses/commands.js +66 -3
  287. package/dist/senses/continuity.js +94 -0
  288. package/dist/senses/habit-turn-message.js +108 -0
  289. package/dist/senses/inner-dialog-worker.js +112 -19
  290. package/dist/senses/inner-dialog.js +636 -86
  291. package/dist/senses/pipeline.js +602 -0
  292. package/dist/senses/proactive-content-guard.js +51 -0
  293. package/dist/senses/shared-turn.js +205 -0
  294. package/dist/senses/surface-tool.js +68 -0
  295. package/dist/senses/teams.js +693 -160
  296. package/dist/senses/trust-gate.js +112 -2
  297. package/package.json +29 -7
  298. package/skills/agent-commerce.md +106 -0
  299. package/skills/browser-navigation.md +110 -0
  300. package/skills/commerce-setup-guide.md +116 -0
  301. package/skills/commerce-setup.md +84 -0
  302. package/skills/configure-dev-tools.md +101 -0
  303. package/skills/travel-planning.md +138 -0
  304. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  305. package/dist/heart/daemon/subagent-installer.js +0 -134
  306. package/dist/mind/associative-recall.js +0 -197
  307. package/dist/senses/bluebubbles-entry.js +0 -11
  308. package/dist/senses/bluebubbles.js +0 -558
  309. package/dist/senses/debug-activity.js +0 -127
  310. package/subagents/README.md +0 -60
  311. package/subagents/work-doer.md +0 -235
  312. package/subagents/work-merger.md +0 -618
  313. package/subagents/work-planner.md +0 -382
  314. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  315. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  316. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  317. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  318. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  319. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  320. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  321. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  322. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  323. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  324. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  325. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  326. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  327. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  328. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  329. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -33,15 +33,31 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.downloadBlueBubblesAttachment = exports.VLM_TEXT_WRAPPERS = void 0;
36
37
  exports.hydrateBlueBubblesAttachments = hydrateBlueBubblesAttachments;
37
38
  const node_child_process_1 = require("node:child_process");
38
39
  const fs = __importStar(require("node:fs/promises"));
39
40
  const os = __importStar(require("node:os"));
40
41
  const path = __importStar(require("node:path"));
41
- const runtime_1 = require("../nerves/runtime");
42
- const MAX_ATTACHMENT_BYTES = 8 * 1024 * 1024;
42
+ const runtime_1 = require("../../nerves/runtime");
43
+ const identity_1 = require("../../heart/identity");
44
+ const model_capabilities_1 = require("../../heart/model-capabilities");
45
+ const image_normalize_1 = require("../../heart/attachments/image-normalize");
46
+ const store_1 = require("../../heart/attachments/store");
47
+ const bluebubbles_1 = require("../../heart/attachments/sources/bluebubbles");
48
+ const attachment_download_1 = require("./attachment-download");
49
+ // Pin the exact wrapper strings so tests and code read from one source.
50
+ exports.VLM_TEXT_WRAPPERS = {
51
+ description: (desc) => `[image description: ${desc}]`,
52
+ failure: (reason) => `[image description failed: ${reason}]`,
53
+ };
54
+ // D3 prompt template. Included here (not templated out) because the contract
55
+ // is load-bearing — see planning doc section D3 and AX-4.
56
+ function buildVlmPrompt(userText) {
57
+ const body = userText ?? "";
58
+ return `User message: "${body}"\n\nDescribe this image in detail, focusing on anything relevant to what the user said above.\nInclude any text visible in the image verbatim.`;
59
+ }
43
60
  const AUDIO_EXTENSIONS = new Set([".mp3", ".wav", ".m4a", ".caf", ".ogg"]);
44
- const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".heic", ".heif"]);
45
61
  const AUDIO_EXTENSION_BY_CONTENT_TYPE = {
46
62
  "audio/wav": ".wav",
47
63
  "audio/x-wav": ".wav",
@@ -65,31 +81,19 @@ const AUDIO_INPUT_FORMAT_BY_EXTENSION = {
65
81
  const WHISPER_CPP_FORMULA = "whisper-cpp";
66
82
  const WHISPER_CPP_MODEL_NAME = "ggml-base.en.bin";
67
83
  const WHISPER_CPP_MODEL_URL = `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/${WHISPER_CPP_MODEL_NAME}`;
68
- const WHISPER_CPP_TOOLS_DIR = path.join(os.homedir(), ".agentstate", "tools", "whisper-cpp");
69
- const WHISPER_CPP_MODELS_DIR = path.join(WHISPER_CPP_TOOLS_DIR, "models");
70
- const WHISPER_CPP_MODEL_PATH = path.join(WHISPER_CPP_MODELS_DIR, WHISPER_CPP_MODEL_NAME);
71
- function buildBlueBubblesApiUrl(baseUrl, endpoint, password) {
72
- const root = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
73
- const url = new URL(endpoint.replace(/^\//, ""), root);
74
- url.searchParams.set("password", password);
75
- return url.toString();
84
+ // Lazy getAgentToolsRoot() requires identity to be resolved, which isn't
85
+ // true at module-load time in most unit test contexts. Tests that only touch
86
+ // the image/VLM path shouldn't break because the audio path asked for a
87
+ // tools root they don't need.
88
+ function whisperCppPaths() {
89
+ const toolsDir = path.join((0, identity_1.getAgentToolsRoot)(), "whisper-cpp");
90
+ const modelsDir = path.join(toolsDir, "models");
91
+ const modelPath = path.join(modelsDir, WHISPER_CPP_MODEL_NAME);
92
+ return { toolsDir, modelsDir, modelPath };
76
93
  }
77
94
  function describeAttachment(attachment) {
78
95
  return attachment.transferName?.trim() || attachment.guid?.trim() || "attachment";
79
96
  }
80
- function inferContentType(attachment, responseType) {
81
- const normalizedResponseType = responseType?.split(";")[0]?.trim().toLowerCase();
82
- if (normalizedResponseType) {
83
- return normalizedResponseType;
84
- }
85
- return attachment.mimeType?.trim().toLowerCase() || undefined;
86
- }
87
- function isImageAttachment(attachment, contentType) {
88
- if (contentType?.startsWith("image/"))
89
- return true;
90
- const extension = path.extname(attachment.transferName ?? "").toLowerCase();
91
- return IMAGE_EXTENSIONS.has(extension);
92
- }
93
97
  function isAudioAttachment(attachment, contentType) {
94
98
  if (contentType?.startsWith("audio/"))
95
99
  return true;
@@ -169,12 +173,13 @@ async function resolveWhisperCppBinary(timeoutMs) {
169
173
  return candidate;
170
174
  }
171
175
  async function ensureWhisperCppModel(timeoutMs, fetchImpl) {
176
+ const { modelsDir, modelPath } = whisperCppPaths();
172
177
  try {
173
- await fs.access(WHISPER_CPP_MODEL_PATH);
174
- return WHISPER_CPP_MODEL_PATH;
178
+ await fs.access(modelPath);
179
+ return modelPath;
175
180
  }
176
181
  catch {
177
- await fs.mkdir(WHISPER_CPP_MODELS_DIR, { recursive: true });
182
+ await fs.mkdir(modelsDir, { recursive: true });
178
183
  const response = await fetchImpl(WHISPER_CPP_MODEL_URL, {
179
184
  method: "GET",
180
185
  signal: AbortSignal.timeout(Math.max(timeoutMs, 300_000)),
@@ -182,8 +187,8 @@ async function ensureWhisperCppModel(timeoutMs, fetchImpl) {
182
187
  if (!response.ok) {
183
188
  throw new Error(`failed to download whisper.cpp model: HTTP ${response.status}`);
184
189
  }
185
- await fs.writeFile(WHISPER_CPP_MODEL_PATH, Buffer.from(await response.arrayBuffer()));
186
- return WHISPER_CPP_MODEL_PATH;
190
+ await fs.writeFile(modelPath, Buffer.from(await response.arrayBuffer()));
191
+ return modelPath;
187
192
  }
188
193
  }
189
194
  async function convertAudioForWhisperCpp(sourcePath, outputPath, timeoutMs) {
@@ -235,59 +240,105 @@ async function transcribeAudioWithWhisperCpp(params, modelFetchImpl = fetch) {
235
240
  await fs.rm(workDir, { recursive: true, force: true }).catch(() => undefined);
236
241
  }
237
242
  }
238
- async function downloadAttachment(attachment, config, channelConfig, fetchImpl) {
239
- const guid = attachment.guid?.trim();
240
- if (!guid) {
241
- throw new Error("attachment guid missing");
242
- }
243
- if (typeof attachment.totalBytes === "number" && attachment.totalBytes > MAX_ATTACHMENT_BYTES) {
244
- throw new Error(`attachment exceeds ${MAX_ATTACHMENT_BYTES} byte limit`);
245
- }
246
- const url = buildBlueBubblesApiUrl(config.serverUrl, `/api/v1/attachment/${encodeURIComponent(guid)}/download`, config.password);
247
- const response = await fetchImpl(url, {
248
- method: "GET",
249
- signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
250
- });
251
- if (!response.ok) {
252
- throw new Error(`HTTP ${response.status}`);
253
- }
254
- const buffer = Buffer.from(await response.arrayBuffer());
255
- if (buffer.length > MAX_ATTACHMENT_BYTES) {
256
- throw new Error(`attachment exceeds ${MAX_ATTACHMENT_BYTES} byte limit`);
257
- }
258
- return {
259
- buffer,
260
- contentType: inferContentType(attachment, response.headers.get("content-type")),
261
- };
262
- }
243
+ var attachment_download_2 = require("./attachment-download");
244
+ Object.defineProperty(exports, "downloadBlueBubblesAttachment", { enumerable: true, get: function () { return attachment_download_2.downloadBlueBubblesAttachment; } });
263
245
  async function hydrateBlueBubblesAttachments(attachments, config, channelConfig, deps = {}) {
264
- (0, runtime_1.emitNervesEvent)({
265
- component: "senses",
266
- event: "senses.bluebubbles_media_hydrate",
267
- message: "hydrating bluebubbles attachments",
268
- meta: {
269
- attachmentCount: attachments.length,
270
- preferAudioInput: deps.preferAudioInput ?? false,
271
- },
272
- });
273
246
  const fetchImpl = deps.fetchImpl ?? fetch;
274
247
  const modelFetchImpl = deps.modelFetchImpl ?? fetch;
275
248
  const transcribeAudio = deps.transcribeAudio ?? ((params) => transcribeAudioWithWhisperCpp(params, modelFetchImpl));
276
249
  const preferAudioInput = deps.preferAudioInput ?? false;
250
+ const chatModel = deps.chatModel;
251
+ const visionCapable = chatModel ? (0, model_capabilities_1.getModelCapabilities)(chatModel).vision === true : true;
252
+ const agentName = (0, identity_1.getAgentName)();
253
+ const agentRoot = (0, identity_1.getAgentRoot)(agentName);
277
254
  const inputParts = [];
278
255
  const transcriptAdditions = [];
279
256
  const notices = [];
280
257
  for (const attachment of attachments) {
281
258
  const name = describeAttachment(attachment);
259
+ const initialRecord = attachment.guid?.trim()
260
+ ? (0, store_1.cacheRecentAttachment)(agentName, (0, bluebubbles_1.buildBlueBubblesAttachmentRecord)(attachment), agentRoot)
261
+ : null;
282
262
  try {
283
- const downloaded = await downloadAttachment(attachment, config, channelConfig, fetchImpl);
263
+ const downloaded = await (0, attachment_download_1.downloadBlueBubblesAttachment)(attachment, config, channelConfig, fetchImpl);
284
264
  const base64 = downloaded.buffer.toString("base64");
285
- if (isImageAttachment(attachment, downloaded.contentType)) {
286
- inputParts.push({
287
- type: "image_url",
288
- image_url: {
289
- url: `data:${downloaded.contentType ?? "application/octet-stream"};base64,${base64}`,
290
- detail: "auto",
265
+ const byteCount = downloaded.buffer.length;
266
+ const persistedRecord = initialRecord && initialRecord.source === "bluebubbles"
267
+ ? await (0, bluebubbles_1.persistBlueBubblesAttachmentSource)(agentName, initialRecord, {
268
+ buffer: downloaded.buffer,
269
+ mimeType: downloaded.contentType,
270
+ byteCount,
271
+ }, agentRoot)
272
+ : null;
273
+ if ((0, attachment_download_1.isBlueBubblesImageAttachment)(attachment, downloaded.contentType)) {
274
+ const mimeType = downloaded.contentType ?? "application/octet-stream";
275
+ if (visionCapable) {
276
+ inputParts.push({
277
+ type: "image_url",
278
+ image_url: {
279
+ url: `data:${mimeType};base64,${base64}`,
280
+ detail: "auto",
281
+ },
282
+ });
283
+ (0, runtime_1.emitNervesEvent)({
284
+ level: "warn",
285
+ component: "senses",
286
+ event: "senses.bluebubbles_media_hydrate",
287
+ message: "bluebubbles media hydrate",
288
+ meta: {
289
+ attachmentGuid: attachment.guid,
290
+ mimeType,
291
+ byteCount,
292
+ hydrationPath: "native-passthrough",
293
+ },
294
+ });
295
+ continue;
296
+ }
297
+ try {
298
+ if (!deps.vlmDescribe) {
299
+ throw new Error("no VLM describer configured — wire a vlmDescribe dep or configure a vision-capable chat model");
300
+ }
301
+ if (!persistedRecord?.sourceData.localPath) {
302
+ throw new Error("image attachment could not be persisted for normalization");
303
+ }
304
+ const normalized = await (0, image_normalize_1.normalizeImageForVision)({
305
+ attachment: persistedRecord,
306
+ sourcePath: persistedRecord.sourceData.localPath,
307
+ agentName,
308
+ agentRoot,
309
+ });
310
+ const normalizedBuffer = await fs.readFile(normalized.path);
311
+ const normalizedMime = normalized.mimeType ?? persistedRecord.mimeType ?? "image/jpeg";
312
+ const dataUrl = `data:${normalizedMime};base64,${normalizedBuffer.toString("base64")}`;
313
+ const description = await deps.vlmDescribe({
314
+ prompt: buildVlmPrompt(deps.userText),
315
+ imageDataUrl: dataUrl,
316
+ attachmentGuid: attachment.guid,
317
+ mimeType: normalizedMime,
318
+ chatModel,
319
+ });
320
+ inputParts.push({
321
+ type: "text",
322
+ text: exports.VLM_TEXT_WRAPPERS.description(description),
323
+ });
324
+ }
325
+ catch (vlmError) {
326
+ const reason = vlmError instanceof Error ? vlmError.message : String(vlmError);
327
+ inputParts.push({
328
+ type: "text",
329
+ text: exports.VLM_TEXT_WRAPPERS.failure(reason),
330
+ });
331
+ }
332
+ (0, runtime_1.emitNervesEvent)({
333
+ level: "warn",
334
+ component: "senses",
335
+ event: "senses.bluebubbles_media_hydrate",
336
+ message: "bluebubbles media hydrate",
337
+ meta: {
338
+ attachmentGuid: attachment.guid,
339
+ mimeType: persistedRecord?.mimeType ?? mimeType,
340
+ byteCount,
341
+ hydrationPath: "vlm-describe",
291
342
  },
292
343
  });
293
344
  continue;
@@ -1,7 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BlueBubblesIgnoredEventError = void 0;
3
4
  exports.normalizeBlueBubblesEvent = normalizeBlueBubblesEvent;
4
- const runtime_1 = require("../nerves/runtime");
5
+ const runtime_1 = require("../../nerves/runtime");
6
+ const render_1 = require("../../heart/attachments/render");
7
+ const bluebubbles_1 = require("../../heart/attachments/sources/bluebubbles");
8
+ const IGNORABLE_GUIDLESS_EVENT_TYPES = new Set([
9
+ "chat-read-status-changed",
10
+ ]);
11
+ class BlueBubblesIgnoredEventError extends Error {
12
+ eventType;
13
+ constructor(eventType, message) {
14
+ super(message);
15
+ this.name = "BlueBubblesIgnoredEventError";
16
+ this.eventType = eventType;
17
+ }
18
+ }
19
+ exports.BlueBubblesIgnoredEventError = BlueBubblesIgnoredEventError;
5
20
  function asRecord(value) {
6
21
  return value && typeof value === "object" && !Array.isArray(value)
7
22
  ? value
@@ -52,6 +67,15 @@ function buildChatRef(data, threadOriginatorGuid) {
52
67
  const sessionKey = chatGuid?.trim()
53
68
  ? `chat:${chatGuid.trim()}`
54
69
  : `chat_identifier:${(chatIdentifier ?? "unknown").trim()}`;
70
+ // Extract participant handles from chat.participants (when available from BB API)
71
+ const rawParticipants = Array.isArray(chat?.participants) ? chat.participants : [];
72
+ const participantHandles = rawParticipants
73
+ .map((p) => {
74
+ const rec = asRecord(p);
75
+ const addr = readString(rec, "address") ?? readString(rec, "id");
76
+ return addr ? normalizeHandle(addr) : "";
77
+ })
78
+ .filter(Boolean);
55
79
  return {
56
80
  chatGuid: chatGuid?.trim() || undefined,
57
81
  chatIdentifier: chatIdentifier?.trim() || undefined,
@@ -61,6 +85,7 @@ function buildChatRef(data, threadOriginatorGuid) {
61
85
  sendTarget: chatGuid?.trim()
62
86
  ? { kind: "chat_guid", value: chatGuid.trim() }
63
87
  : { kind: "chat_identifier", value: (chatIdentifier ?? "unknown").trim() },
88
+ participantHandles,
64
89
  };
65
90
  }
66
91
  function extractSender(data, chat) {
@@ -97,18 +122,14 @@ function extractAttachments(data) {
97
122
  function formatAttachmentText(attachments) {
98
123
  if (attachments.length === 0)
99
124
  return "";
125
+ const renderable = attachments
126
+ .filter((attachment) => typeof attachment.guid === "string" && attachment.guid.trim().length > 0)
127
+ .map((attachment) => (0, bluebubbles_1.buildBlueBubblesAttachmentRecord)(attachment));
128
+ if (renderable.length > 0) {
129
+ return (0, render_1.renderAttachmentBlock)(renderable);
130
+ }
100
131
  const [first] = attachments;
101
- const mime = first.mimeType ?? "";
102
- const label = mime.startsWith("image/")
103
- ? "image attachment"
104
- : mime.startsWith("audio/")
105
- ? "audio attachment"
106
- : "attachment";
107
- const name = first.transferName ? `: ${first.transferName}` : "";
108
- const dimensions = typeof first.width === "number" && typeof first.height === "number" && first.width > 0 && first.height > 0
109
- ? ` (${first.width}x${first.height})`
110
- : "";
111
- return `[${label}${name}${dimensions}]`;
132
+ return `[attachment: ${first.transferName?.trim() || "unknown"}]`;
112
133
  }
113
134
  function formatMessageText(data, attachments) {
114
135
  const text = readString(data, "text")?.trim() ?? "";
@@ -117,6 +138,13 @@ function formatMessageText(data, attachments) {
117
138
  if (balloonBundleId === "com.apple.messages.URLBalloonProvider") {
118
139
  return `${text}\n[link preview attached]`;
119
140
  }
141
+ // B2 fix: when text and attachments both exist, append the attachment
142
+ // marker so downstream senses can see the guid/filename. Previously the
143
+ // marker was dropped whenever text was non-empty, which hid images from
144
+ // the agent when the user captioned a screenshot.
145
+ if (attachments.length > 0) {
146
+ return `${text}\n${formatAttachmentText(attachments)}`;
147
+ }
120
148
  return text;
121
149
  }
122
150
  return formatAttachmentText(attachments);
@@ -188,6 +216,9 @@ function normalizeBlueBubblesEvent(payload) {
188
216
  message: "ignored bluebubbles payload without guid",
189
217
  meta: { eventType },
190
218
  });
219
+ if (IGNORABLE_GUIDLESS_EVENT_TYPES.has(eventType)) {
220
+ throw new BlueBubblesIgnoredEventError(eventType, `Ignored BlueBubbles event '${eventType}' without data.guid`);
221
+ }
191
222
  throw new Error("BlueBubbles payload is missing data.guid");
192
223
  }
193
224
  const threadOriginatorGuid = readString(data, "threadOriginatorGuid")?.trim() || undefined;
@@ -35,15 +35,14 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.getBlueBubblesMutationLogPath = getBlueBubblesMutationLogPath;
37
37
  exports.recordBlueBubblesMutation = recordBlueBubblesMutation;
38
+ exports.listBlueBubblesRecoveryCandidates = listBlueBubblesRecoveryCandidates;
38
39
  const fs = __importStar(require("node:fs"));
39
40
  const path = __importStar(require("node:path"));
40
- const runtime_1 = require("../nerves/runtime");
41
- const identity_1 = require("../heart/identity");
42
- function sanitizeKey(key) {
43
- return key.replace(/[/:]/g, "_");
44
- }
41
+ const runtime_1 = require("../../nerves/runtime");
42
+ const identity_1 = require("../../heart/identity");
43
+ const config_1 = require("../../heart/config");
45
44
  function getBlueBubblesMutationLogPath(agentName, sessionKey) {
46
- return path.join((0, identity_1.getAgentRoot)(agentName), "state", "senses", "bluebubbles", "mutations", `${sanitizeKey(sessionKey)}.ndjson`);
45
+ return path.join((0, identity_1.getAgentRoot)(agentName), "state", "senses", "bluebubbles", "mutations", `${(0, config_1.sanitizeKey)(sessionKey)}.ndjson`);
47
46
  }
48
47
  function recordBlueBubblesMutation(agentName, event) {
49
48
  const filePath = getBlueBubblesMutationLogPath(agentName, event.chat.sessionKey);
@@ -74,3 +73,44 @@ function recordBlueBubblesMutation(agentName, event) {
74
73
  });
75
74
  return filePath;
76
75
  }
76
+ function listBlueBubblesRecoveryCandidates(agentName) {
77
+ const rootDir = path.join((0, identity_1.getAgentRoot)(agentName), "state", "senses", "bluebubbles", "mutations");
78
+ let files;
79
+ try {
80
+ files = fs.readdirSync(rootDir);
81
+ }
82
+ catch {
83
+ return [];
84
+ }
85
+ const deduped = new Map();
86
+ for (const file of files.filter((entry) => entry.endsWith(".ndjson")).sort()) {
87
+ const filePath = path.join(rootDir, file);
88
+ let raw = "";
89
+ try {
90
+ raw = fs.readFileSync(filePath, "utf-8");
91
+ }
92
+ catch {
93
+ continue;
94
+ }
95
+ for (const line of raw.split("\n")) {
96
+ const trimmed = line.trim();
97
+ if (!trimmed)
98
+ continue;
99
+ try {
100
+ const entry = JSON.parse(trimmed);
101
+ if (typeof entry.messageGuid !== "string"
102
+ || !entry.messageGuid.trim()
103
+ || entry.fromMe
104
+ || entry.shouldNotifyAgent
105
+ || (entry.mutationType !== "read" && entry.mutationType !== "delivery")) {
106
+ continue;
107
+ }
108
+ deduped.set(entry.messageGuid, entry);
109
+ }
110
+ catch {
111
+ // ignore malformed recovery candidates
112
+ }
113
+ }
114
+ }
115
+ return [...deduped.values()].sort((left, right) => left.recordedAt.localeCompare(right.recordedAt));
116
+ }
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.replayBlueBubblesMessage = replayBlueBubblesMessage;
4
+ exports.formatBlueBubblesReplayText = formatBlueBubblesReplayText;
5
+ const render_1 = require("../../heart/attachments/render");
6
+ const bluebubbles_1 = require("../../heart/attachments/sources/bluebubbles");
7
+ const identity_1 = require("../../heart/identity");
8
+ const runtime_1 = require("../../nerves/runtime");
9
+ const client_1 = require("./client");
10
+ const model_1 = require("./model");
11
+ function buildReplayHint(probeEventType, event) {
12
+ if (probeEventType === "updated-message"
13
+ && event.kind === "mutation"
14
+ && (event.mutationType === "read" || event.mutationType === "delivery")) {
15
+ return "replay resolved to a state-only mutation; rerun with --event-type new-message to inspect the original message payload.";
16
+ }
17
+ return undefined;
18
+ }
19
+ async function replayBlueBubblesMessage(params, deps = {}) {
20
+ const agentName = params.agentName.trim();
21
+ const messageGuid = params.messageGuid.trim();
22
+ const eventType = params.eventType ?? "new-message";
23
+ if (!agentName) {
24
+ throw new Error("bluebubbles replay requires agentName");
25
+ }
26
+ if (!messageGuid) {
27
+ throw new Error("bluebubbles replay requires messageGuid");
28
+ }
29
+ const setReplayAgentName = deps.setAgentName ?? identity_1.setAgentName;
30
+ const resetReplayIdentity = deps.resetIdentity ?? identity_1.resetIdentity;
31
+ const normalizeEvent = deps.normalizeEvent ?? model_1.normalizeBlueBubblesEvent;
32
+ (0, runtime_1.emitNervesEvent)({
33
+ component: "senses",
34
+ event: "senses.bluebubbles_replay_start",
35
+ message: "starting bluebubbles historical replay",
36
+ meta: {
37
+ agentName,
38
+ messageGuid,
39
+ eventType,
40
+ },
41
+ });
42
+ setReplayAgentName(agentName);
43
+ try {
44
+ const client = deps.createClient ? deps.createClient() : (0, client_1.createBlueBubblesClient)();
45
+ const probe = normalizeEvent({
46
+ type: eventType,
47
+ data: {
48
+ guid: messageGuid,
49
+ hasPayloadData: true,
50
+ },
51
+ });
52
+ const event = await client.repairEvent(probe);
53
+ const attachmentRecords = event.kind === "message"
54
+ ? event.attachments
55
+ .filter((attachment) => typeof attachment.guid === "string" && attachment.guid.trim().length > 0)
56
+ .map((attachment) => (0, bluebubbles_1.buildBlueBubblesAttachmentRecord)(attachment))
57
+ : [];
58
+ const result = {
59
+ probe: {
60
+ agentName,
61
+ messageGuid,
62
+ eventType,
63
+ },
64
+ event,
65
+ attachmentIds: attachmentRecords.map((attachment) => attachment.id),
66
+ attachmentBlock: (0, render_1.renderAttachmentBlock)(attachmentRecords),
67
+ ...(buildReplayHint(eventType, event) ? { hint: buildReplayHint(eventType, event) } : {}),
68
+ };
69
+ (0, runtime_1.emitNervesEvent)({
70
+ component: "senses",
71
+ event: "senses.bluebubbles_replay_end",
72
+ message: "completed bluebubbles historical replay",
73
+ meta: {
74
+ agentName,
75
+ messageGuid,
76
+ eventType,
77
+ kind: event.kind,
78
+ attachmentCount: attachmentRecords.length,
79
+ },
80
+ });
81
+ return result;
82
+ }
83
+ catch (error) {
84
+ (0, runtime_1.emitNervesEvent)({
85
+ level: "warn",
86
+ component: "senses",
87
+ event: "senses.bluebubbles_replay_error",
88
+ message: "bluebubbles historical replay failed",
89
+ meta: {
90
+ agentName,
91
+ messageGuid,
92
+ eventType,
93
+ reason: error instanceof Error ? error.message : String(error),
94
+ },
95
+ });
96
+ throw error;
97
+ }
98
+ finally {
99
+ resetReplayIdentity();
100
+ }
101
+ }
102
+ function formatBlueBubblesReplayText(result) {
103
+ const lines = [
104
+ `probe: ${result.probe.eventType}`,
105
+ `agent: ${result.probe.agentName}`,
106
+ `message_guid: ${result.probe.messageGuid}`,
107
+ `result_kind: ${result.event.kind}`,
108
+ `session: ${result.event.chat.sessionKey}`,
109
+ ];
110
+ if (result.event.kind === "mutation") {
111
+ lines.push(`mutation_type: ${result.event.mutationType}`);
112
+ }
113
+ if (result.event.kind === "message" && result.event.inputPartsForAgent?.length) {
114
+ lines.push(`input_parts_for_agent: ${result.event.inputPartsForAgent.length}`);
115
+ }
116
+ if (result.event.repairNotice?.trim()) {
117
+ lines.push(`repair_notice: ${result.event.repairNotice.trim()}`);
118
+ }
119
+ if (result.attachmentBlock && !result.event.textForAgent.includes(result.attachmentBlock)) {
120
+ lines.push(result.attachmentBlock);
121
+ }
122
+ lines.push("[text_for_agent]");
123
+ lines.push(result.event.textForAgent || "(empty)");
124
+ if (result.hint) {
125
+ lines.push("[hint]");
126
+ lines.push(result.hint);
127
+ }
128
+ return lines.join("\n");
129
+ }
@@ -0,0 +1,109 @@
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.getBlueBubblesRuntimeStatePath = getBlueBubblesRuntimeStatePath;
37
+ exports.readBlueBubblesRuntimeState = readBlueBubblesRuntimeState;
38
+ exports.writeBlueBubblesRuntimeState = writeBlueBubblesRuntimeState;
39
+ const fs = __importStar(require("node:fs"));
40
+ const path = __importStar(require("node:path"));
41
+ const identity_1 = require("../../heart/identity");
42
+ const runtime_1 = require("../../nerves/runtime");
43
+ const DEFAULT_RUNTIME_STATE = {
44
+ upstreamStatus: "unknown",
45
+ detail: "startup health probe pending",
46
+ pendingRecoveryCount: 0,
47
+ };
48
+ function getBlueBubblesRuntimeStatePath(agentName, agentRoot = (0, identity_1.getAgentRoot)(agentName)) {
49
+ return path.join(agentRoot, "state", "senses", "bluebubbles", "runtime.json");
50
+ }
51
+ function readBlueBubblesRuntimeState(agentName, agentRoot) {
52
+ const filePath = getBlueBubblesRuntimeStatePath(agentName, agentRoot);
53
+ try {
54
+ const raw = fs.readFileSync(filePath, "utf-8");
55
+ const parsed = JSON.parse(raw);
56
+ return {
57
+ upstreamStatus: parsed.upstreamStatus === "ok" || parsed.upstreamStatus === "error"
58
+ ? parsed.upstreamStatus
59
+ : "unknown",
60
+ detail: typeof parsed.detail === "string" && parsed.detail.trim()
61
+ ? parsed.detail
62
+ : DEFAULT_RUNTIME_STATE.detail,
63
+ lastCheckedAt: typeof parsed.lastCheckedAt === "string" ? parsed.lastCheckedAt : undefined,
64
+ pendingRecoveryCount: typeof parsed.pendingRecoveryCount === "number" && Number.isFinite(parsed.pendingRecoveryCount)
65
+ ? parsed.pendingRecoveryCount
66
+ : 0,
67
+ lastRecoveredAt: typeof parsed.lastRecoveredAt === "string" ? parsed.lastRecoveredAt : undefined,
68
+ lastRecoveredMessageGuid: typeof parsed.lastRecoveredMessageGuid === "string"
69
+ ? parsed.lastRecoveredMessageGuid
70
+ : undefined,
71
+ };
72
+ }
73
+ catch {
74
+ return { ...DEFAULT_RUNTIME_STATE };
75
+ }
76
+ }
77
+ function writeBlueBubblesRuntimeState(agentName, state, agentRoot) {
78
+ const filePath = getBlueBubblesRuntimeStatePath(agentName, agentRoot);
79
+ try {
80
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
81
+ fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
82
+ }
83
+ catch (error) {
84
+ (0, runtime_1.emitNervesEvent)({
85
+ level: "warn",
86
+ component: "senses",
87
+ event: "senses.bluebubbles_runtime_state_error",
88
+ message: "failed to write bluebubbles runtime state",
89
+ meta: {
90
+ agentName,
91
+ upstreamStatus: state.upstreamStatus,
92
+ reason: error instanceof Error ? error.message : String(error),
93
+ },
94
+ });
95
+ return filePath;
96
+ }
97
+ (0, runtime_1.emitNervesEvent)({
98
+ component: "senses",
99
+ event: "senses.bluebubbles_runtime_state_written",
100
+ message: "wrote bluebubbles runtime state",
101
+ meta: {
102
+ agentName,
103
+ upstreamStatus: state.upstreamStatus,
104
+ pendingRecoveryCount: state.pendingRecoveryCount,
105
+ path: filePath,
106
+ },
107
+ });
108
+ return filePath;
109
+ }
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.findObsoleteBlueBubblesThreadSessions = findObsoleteBlueBubblesThreadSessions;
37
37
  const fs = __importStar(require("node:fs"));
38
38
  const path = __importStar(require("node:path"));
39
- const runtime_1 = require("../nerves/runtime");
39
+ const runtime_1 = require("../../nerves/runtime");
40
40
  function findObsoleteBlueBubblesThreadSessions(trunkSessionPath) {
41
41
  const normalized = trunkSessionPath.trim();
42
42
  if (!normalized.endsWith(".json"))