@ouro.bot/cli 0.1.0-alpha.25 → 0.1.0-alpha.250

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 (258) 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 +1507 -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 +447 -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 +145 -0
  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 +557 -0
  28. package/dist/heart/daemon/cli-exec.js +1875 -0
  29. package/dist/heart/daemon/cli-parse.js +759 -0
  30. package/dist/heart/daemon/cli-render.js +464 -0
  31. package/dist/heart/daemon/cli-types.js +8 -0
  32. package/dist/heart/daemon/daemon-cli.js +29 -1179
  33. package/dist/heart/daemon/daemon-entry.js +202 -4
  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 +123 -0
  37. package/dist/heart/daemon/daemon.js +491 -20
  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 +39 -8
  42. package/dist/heart/daemon/log-tailer.js +4 -3
  43. package/dist/heart/daemon/message-router.js +17 -8
  44. package/dist/heart/daemon/os-cron-deps.js +134 -0
  45. package/dist/heart/daemon/ouro-bot-entry.js +1 -1
  46. package/dist/heart/daemon/process-manager.js +123 -0
  47. package/dist/heart/daemon/run-hooks.js +2 -0
  48. package/dist/heart/daemon/runtime-logging.js +58 -15
  49. package/dist/heart/daemon/runtime-metadata.js +101 -0
  50. package/dist/heart/daemon/runtime-mode.js +67 -0
  51. package/dist/heart/daemon/safe-mode.js +161 -0
  52. package/dist/heart/daemon/sense-manager.js +72 -3
  53. package/dist/heart/daemon/session-id-resolver.js +131 -0
  54. package/dist/heart/daemon/skill-management-installer.js +94 -0
  55. package/dist/heart/daemon/socket-client.js +202 -0
  56. package/dist/heart/daemon/task-scheduler.js +3 -25
  57. package/dist/heart/daemon/thoughts.js +507 -0
  58. package/dist/heart/delegation.js +62 -0
  59. package/dist/heart/habits/habit-migration.js +181 -0
  60. package/dist/heart/habits/habit-parser.js +119 -0
  61. package/dist/heart/habits/habit-scheduler.js +369 -0
  62. package/dist/heart/{daemon → hatch}/hatch-flow.js +30 -120
  63. package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
  64. package/dist/heart/{daemon → hatch}/specialist-prompt.js +10 -7
  65. package/dist/heart/{daemon → hatch}/specialist-tools.js +49 -3
  66. package/dist/heart/identity.js +157 -55
  67. package/dist/heart/kicks.js +2 -20
  68. package/dist/heart/mcp/mcp-server.js +651 -0
  69. package/dist/heart/migrate-config.js +127 -0
  70. package/dist/heart/model-capabilities.js +48 -0
  71. package/dist/heart/outlook/outlook-http.js +439 -0
  72. package/dist/heart/outlook/outlook-read.js +1618 -0
  73. package/dist/heart/outlook/outlook-render.js +1032 -0
  74. package/dist/heart/outlook/outlook-types.js +7 -0
  75. package/dist/heart/outlook/outlook-view.js +194 -0
  76. package/dist/heart/progress-story.js +42 -0
  77. package/dist/heart/provider-failover.js +88 -0
  78. package/dist/heart/provider-ping.js +162 -0
  79. package/dist/heart/providers/anthropic-token.js +163 -0
  80. package/dist/heart/providers/anthropic.js +169 -46
  81. package/dist/heart/providers/azure.js +98 -11
  82. package/dist/heart/providers/error-classification.js +63 -0
  83. package/dist/heart/providers/github-copilot.js +136 -0
  84. package/dist/heart/providers/minimax.js +15 -4
  85. package/dist/heart/providers/openai-codex.js +33 -22
  86. package/dist/heart/session-activity.js +172 -0
  87. package/dist/heart/session-recall.js +216 -0
  88. package/dist/heart/start-of-turn-packet.js +330 -0
  89. package/dist/heart/streaming.js +36 -27
  90. package/dist/heart/sync.js +277 -0
  91. package/dist/heart/target-resolution.js +127 -0
  92. package/dist/heart/tempo.js +93 -0
  93. package/dist/heart/temporal-view.js +41 -0
  94. package/dist/heart/tool-activity-callbacks.js +36 -0
  95. package/dist/heart/tool-description.js +135 -0
  96. package/dist/heart/tool-loop.js +200 -0
  97. package/dist/heart/turn-context.js +358 -0
  98. package/dist/heart/turn-coordinator.js +28 -0
  99. package/dist/heart/versioning/ouro-bot-global-installer.js +128 -0
  100. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  101. package/dist/heart/{daemon → versioning}/ouro-path-installer.js +78 -35
  102. package/dist/heart/versioning/ouro-version-manager.js +171 -0
  103. package/dist/heart/{daemon → versioning}/staged-restart.js +30 -6
  104. package/dist/heart/{daemon → versioning}/update-checker.js +9 -1
  105. package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +41 -3
  106. package/dist/mind/associative-recall.js +126 -66
  107. package/dist/mind/bundle-manifest.js +8 -1
  108. package/dist/mind/context.js +125 -14
  109. package/dist/mind/{memory.js → diary.js} +68 -96
  110. package/dist/mind/embedding-provider.js +60 -0
  111. package/dist/mind/file-state.js +179 -0
  112. package/dist/mind/first-impressions.js +16 -2
  113. package/dist/mind/friends/channel.js +56 -0
  114. package/dist/mind/friends/group-context.js +144 -0
  115. package/dist/mind/friends/resolver.js +37 -0
  116. package/dist/mind/friends/store-file.js +58 -3
  117. package/dist/mind/friends/trust-explanation.js +74 -0
  118. package/dist/mind/friends/types.js +8 -0
  119. package/dist/mind/journal-index.js +161 -0
  120. package/dist/mind/obligation-steering.js +221 -0
  121. package/dist/mind/pending.js +76 -9
  122. package/dist/mind/phrases.js +1 -0
  123. package/dist/mind/prompt.js +762 -109
  124. package/dist/mind/scrutiny.js +173 -0
  125. package/dist/mind/token-estimate.js +8 -12
  126. package/dist/nerves/coverage/audit.js +1 -1
  127. package/dist/nerves/coverage/file-completeness.js +50 -5
  128. package/dist/nerves/coverage/run-artifacts.js +1 -1
  129. package/dist/nerves/event-buffer.js +111 -0
  130. package/dist/nerves/index.js +64 -4
  131. package/dist/nerves/observation.js +20 -0
  132. package/dist/nerves/runtime.js +5 -1
  133. package/dist/outlook-ui/assets/index-Ck8agNeO.js +61 -0
  134. package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
  135. package/dist/outlook-ui/index.html +15 -0
  136. package/dist/repertoire/ado-client.js +15 -56
  137. package/dist/repertoire/ado-semantic.js +11 -10
  138. package/dist/repertoire/api-client.js +97 -0
  139. package/dist/repertoire/bitwarden-store.js +319 -0
  140. package/dist/repertoire/bw-installer.js +79 -0
  141. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  142. package/dist/repertoire/coding/context-pack.js +330 -0
  143. package/dist/repertoire/coding/feedback.js +197 -30
  144. package/dist/repertoire/coding/manager.js +149 -2
  145. package/dist/repertoire/coding/spawner.js +55 -9
  146. package/dist/repertoire/coding/tools.js +170 -7
  147. package/dist/repertoire/commerce-errors.js +109 -0
  148. package/dist/repertoire/commerce-self-test.js +156 -0
  149. package/dist/repertoire/credential-access.js +515 -0
  150. package/dist/repertoire/duffel-client.js +185 -0
  151. package/dist/repertoire/github-client.js +14 -55
  152. package/dist/repertoire/graph-client.js +11 -52
  153. package/dist/repertoire/guardrails.js +369 -0
  154. package/dist/repertoire/mcp-client.js +255 -0
  155. package/dist/repertoire/mcp-manager.js +303 -0
  156. package/dist/repertoire/mcp-tools.js +63 -0
  157. package/dist/repertoire/shell-sessions.js +133 -0
  158. package/dist/repertoire/skills.js +14 -23
  159. package/dist/repertoire/stripe-client.js +131 -0
  160. package/dist/repertoire/tasks/board.js +43 -5
  161. package/dist/repertoire/tasks/fix.js +182 -0
  162. package/dist/repertoire/tasks/index.js +28 -10
  163. package/dist/repertoire/tasks/lifecycle.js +2 -2
  164. package/dist/repertoire/tasks/parser.js +3 -2
  165. package/dist/repertoire/tasks/scanner.js +194 -37
  166. package/dist/repertoire/tasks/transitions.js +11 -28
  167. package/dist/repertoire/tools-base.js +43 -771
  168. package/dist/repertoire/tools-bluebubbles.js +94 -0
  169. package/dist/repertoire/tools-bridge.js +141 -0
  170. package/dist/repertoire/tools-config.js +185 -0
  171. package/dist/repertoire/tools-continuity.js +248 -0
  172. package/dist/repertoire/tools-credential.js +182 -0
  173. package/dist/repertoire/tools-files.js +342 -0
  174. package/dist/repertoire/tools-flight.js +224 -0
  175. package/dist/repertoire/tools-flow.js +62 -0
  176. package/dist/repertoire/tools-github.js +1 -7
  177. package/dist/repertoire/tools-memory.js +347 -0
  178. package/dist/repertoire/tools-session.js +739 -0
  179. package/dist/repertoire/tools-shell.js +120 -0
  180. package/dist/repertoire/tools-stripe.js +180 -0
  181. package/dist/repertoire/tools-surface.js +243 -0
  182. package/dist/repertoire/tools-teams.js +12 -62
  183. package/dist/repertoire/tools-travel.js +125 -0
  184. package/dist/repertoire/tools-user-profile.js +144 -0
  185. package/dist/repertoire/tools-vault.js +110 -0
  186. package/dist/repertoire/tools.js +147 -130
  187. package/dist/repertoire/travel-api-client.js +360 -0
  188. package/dist/repertoire/user-profile.js +118 -0
  189. package/dist/repertoire/vault-setup.js +241 -0
  190. package/dist/scripts/claude-code-hook.js +41 -0
  191. package/dist/scripts/claude-code-stop-hook.js +47 -0
  192. package/dist/senses/attention-queue.js +97 -0
  193. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +109 -10
  194. package/dist/senses/bluebubbles/entry.js +13 -0
  195. package/dist/senses/bluebubbles/inbound-log.js +109 -0
  196. package/dist/senses/bluebubbles/index.js +1374 -0
  197. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +119 -24
  198. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +13 -5
  199. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
  200. package/dist/senses/bluebubbles/runtime-state.js +109 -0
  201. package/dist/senses/bluebubbles/session-cleanup.js +72 -0
  202. package/dist/senses/cli/ink-app.js +156 -0
  203. package/dist/senses/cli/inline-diff.js +64 -0
  204. package/dist/senses/cli/message-list.js +51 -0
  205. package/dist/senses/cli/ouro-tui.js +393 -0
  206. package/dist/senses/cli/spinner-imperative.js +135 -0
  207. package/dist/senses/cli/spinner.js +101 -0
  208. package/dist/senses/cli/status-line.js +60 -0
  209. package/dist/senses/cli/streaming-markdown.js +526 -0
  210. package/dist/senses/cli/tool-display.js +83 -0
  211. package/dist/senses/cli/tool-render.js +85 -0
  212. package/dist/senses/cli/tui-store.js +236 -0
  213. package/dist/senses/cli/virtual-list.js +35 -0
  214. package/dist/senses/cli-entry.js +1 -1
  215. package/dist/senses/cli-layout.js +187 -0
  216. package/dist/senses/cli.js +550 -246
  217. package/dist/senses/commands.js +65 -1
  218. package/dist/senses/continuity.js +94 -0
  219. package/dist/senses/habit-turn-message.js +108 -0
  220. package/dist/senses/inner-dialog-worker.js +112 -19
  221. package/dist/senses/inner-dialog.js +593 -85
  222. package/dist/senses/pipeline.js +552 -0
  223. package/dist/senses/shared-turn.js +199 -0
  224. package/dist/senses/surface-tool.js +68 -0
  225. package/dist/senses/teams.js +664 -165
  226. package/dist/senses/trust-gate.js +112 -2
  227. package/package.json +14 -7
  228. package/skills/agent-commerce.md +106 -0
  229. package/skills/browser-navigation.md +110 -0
  230. package/skills/commerce-setup-guide.md +116 -0
  231. package/skills/commerce-setup.md +84 -0
  232. package/skills/configure-dev-tools.md +81 -0
  233. package/skills/travel-planning.md +138 -0
  234. package/dist/heart/daemon/subagent-installer.js +0 -134
  235. package/dist/senses/bluebubbles-entry.js +0 -11
  236. package/dist/senses/bluebubbles.js +0 -421
  237. package/dist/senses/debug-activity.js +0 -107
  238. package/subagents/README.md +0 -73
  239. package/subagents/work-doer.md +0 -235
  240. package/subagents/work-merger.md +0 -618
  241. package/subagents/work-planner.md +0 -382
  242. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  243. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  244. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  245. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  246. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  247. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  248. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  249. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  250. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  251. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  252. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  253. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
  254. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  255. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  256. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  257. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  258. /package/dist/heart/{daemon → versioning}/update-hooks.js +0 -0
@@ -38,7 +38,8 @@ const node_child_process_1 = require("node:child_process");
38
38
  const fs = __importStar(require("node:fs/promises"));
39
39
  const os = __importStar(require("node:os"));
40
40
  const path = __importStar(require("node:path"));
41
- const runtime_1 = require("../nerves/runtime");
41
+ const runtime_1 = require("../../nerves/runtime");
42
+ const identity_1 = require("../../heart/identity");
42
43
  const MAX_ATTACHMENT_BYTES = 8 * 1024 * 1024;
43
44
  const AUDIO_EXTENSIONS = new Set([".mp3", ".wav", ".m4a", ".caf", ".ogg"]);
44
45
  const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".heic", ".heif"]);
@@ -62,6 +63,12 @@ const AUDIO_INPUT_FORMAT_BY_EXTENSION = {
62
63
  ".wav": "wav",
63
64
  ".mp3": "mp3",
64
65
  };
66
+ const WHISPER_CPP_FORMULA = "whisper-cpp";
67
+ const WHISPER_CPP_MODEL_NAME = "ggml-base.en.bin";
68
+ const WHISPER_CPP_MODEL_URL = `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/${WHISPER_CPP_MODEL_NAME}`;
69
+ const WHISPER_CPP_TOOLS_DIR = path.join((0, identity_1.getAgentToolsRoot)(), "whisper-cpp");
70
+ const WHISPER_CPP_MODELS_DIR = path.join(WHISPER_CPP_TOOLS_DIR, "models");
71
+ const WHISPER_CPP_MODEL_PATH = path.join(WHISPER_CPP_MODELS_DIR, WHISPER_CPP_MODEL_NAME);
65
72
  function buildBlueBubblesApiUrl(baseUrl, endpoint, password) {
66
73
  const root = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
67
74
  const url = new URL(endpoint.replace(/^\//, ""), root);
@@ -107,36 +114,123 @@ function audioFormatForInput(contentType, attachment) {
107
114
  const extension = path.extname(attachment?.transferName ?? "").toLowerCase();
108
115
  return AUDIO_INPUT_FORMAT_BY_CONTENT_TYPE[contentType ?? ""] ?? AUDIO_INPUT_FORMAT_BY_EXTENSION[extension];
109
116
  }
110
- async function transcribeAudioWithWhisper(params) {
117
+ async function execFileText(file, args, timeout) {
118
+ return await new Promise((resolve, reject) => {
119
+ (0, node_child_process_1.execFile)(file, args, { timeout }, (error, stdout = "", stderr = "") => {
120
+ if (error) {
121
+ const detail = stderr.trim() || stdout.trim() || error.message;
122
+ reject(new Error(detail));
123
+ return;
124
+ }
125
+ resolve(stdout);
126
+ });
127
+ });
128
+ }
129
+ async function pathExists(targetPath) {
130
+ try {
131
+ await fs.access(targetPath);
132
+ return true;
133
+ }
134
+ catch {
135
+ return false;
136
+ }
137
+ }
138
+ async function resolveWhisperCppBinary(timeoutMs) {
139
+ try {
140
+ const existing = (await execFileText("which", ["whisper-cli"], timeoutMs)).trim();
141
+ if (existing) {
142
+ return existing;
143
+ }
144
+ }
145
+ catch {
146
+ // fall through to managed install
147
+ }
148
+ let prefix = "";
149
+ try {
150
+ prefix = (await execFileText("brew", ["--prefix", WHISPER_CPP_FORMULA], timeoutMs)).trim();
151
+ if (prefix) {
152
+ const candidate = path.join(prefix, "bin", "whisper-cli");
153
+ if (await pathExists(candidate)) {
154
+ return candidate;
155
+ }
156
+ }
157
+ }
158
+ catch {
159
+ // fall through to managed install
160
+ }
161
+ await execFileText("brew", ["install", WHISPER_CPP_FORMULA], Math.max(timeoutMs, 300_000));
162
+ prefix = (await execFileText("brew", ["--prefix", WHISPER_CPP_FORMULA], timeoutMs)).trim();
163
+ if (!prefix) {
164
+ throw new Error("whisper.cpp installed but brew did not return a usable prefix");
165
+ }
166
+ const candidate = path.join(prefix, "bin", "whisper-cli");
167
+ if (!await pathExists(candidate)) {
168
+ throw new Error("whisper.cpp installed but whisper-cli binary is missing");
169
+ }
170
+ return candidate;
171
+ }
172
+ async function ensureWhisperCppModel(timeoutMs, fetchImpl) {
173
+ try {
174
+ await fs.access(WHISPER_CPP_MODEL_PATH);
175
+ return WHISPER_CPP_MODEL_PATH;
176
+ }
177
+ catch {
178
+ await fs.mkdir(WHISPER_CPP_MODELS_DIR, { recursive: true });
179
+ const response = await fetchImpl(WHISPER_CPP_MODEL_URL, {
180
+ method: "GET",
181
+ signal: AbortSignal.timeout(Math.max(timeoutMs, 300_000)),
182
+ });
183
+ if (!response.ok) {
184
+ throw new Error(`failed to download whisper.cpp model: HTTP ${response.status}`);
185
+ }
186
+ await fs.writeFile(WHISPER_CPP_MODEL_PATH, Buffer.from(await response.arrayBuffer()));
187
+ return WHISPER_CPP_MODEL_PATH;
188
+ }
189
+ }
190
+ async function convertAudioForWhisperCpp(sourcePath, outputPath, timeoutMs) {
191
+ try {
192
+ await execFileText("ffmpeg", ["-y", "-i", sourcePath, "-ar", "16000", "-ac", "1", "-c:a", "pcm_s16le", outputPath], Math.max(timeoutMs, 120_000));
193
+ return;
194
+ }
195
+ catch (ffmpegError) {
196
+ try {
197
+ await execFileText("afconvert", ["-f", "WAVE", "-d", "LEI16@16000", "-c", "1", sourcePath, outputPath], Math.max(timeoutMs, 120_000));
198
+ return;
199
+ }
200
+ catch (afconvertError) {
201
+ const ffmpegReason = ffmpegError.message;
202
+ const afconvertReason = afconvertError.message;
203
+ throw new Error(`failed to prepare audio for whisper.cpp (ffmpeg: ${ffmpegReason}; afconvert: ${afconvertReason})`);
204
+ }
205
+ }
206
+ }
207
+ async function transcribeAudioWithWhisperCpp(params, modelFetchImpl = fetch) {
111
208
  const workDir = await fs.mkdtemp(path.join(os.tmpdir(), "ouro-bb-audio-"));
112
209
  const filename = sanitizeFilename(describeAttachment(params.attachment));
113
210
  const extension = fileExtensionForAudio(params.attachment, params.contentType);
114
211
  const audioPath = path.join(workDir, `${path.parse(filename).name}${extension}`);
212
+ const wavPath = path.join(workDir, `${path.parse(audioPath).name}.wav`);
213
+ const outputBase = path.join(workDir, path.parse(audioPath).name);
115
214
  try {
116
215
  await fs.writeFile(audioPath, params.buffer);
117
- await new Promise((resolve, reject) => {
118
- (0, node_child_process_1.execFile)("whisper", [
119
- audioPath,
120
- "--model",
121
- "turbo",
122
- "--output_dir",
123
- workDir,
124
- "--output_format",
125
- "json",
126
- "--verbose",
127
- "False",
128
- ], { timeout: Math.max(params.timeoutMs, 120000) }, (error) => {
129
- if (error) {
130
- reject(error);
131
- return;
132
- }
133
- resolve();
134
- });
135
- });
136
- const transcriptPath = path.join(workDir, `${path.parse(audioPath).name}.json`);
216
+ const whisperCliPath = await resolveWhisperCppBinary(params.timeoutMs);
217
+ const modelPath = await ensureWhisperCppModel(params.timeoutMs, modelFetchImpl);
218
+ await convertAudioForWhisperCpp(audioPath, wavPath, params.timeoutMs);
219
+ await execFileText(whisperCliPath, ["-m", modelPath, "-f", wavPath, "-oj", "-of", outputBase], Math.max(params.timeoutMs, 120_000));
220
+ const transcriptPath = `${outputBase}.json`;
137
221
  const raw = await fs.readFile(transcriptPath, "utf8");
138
222
  const parsed = JSON.parse(raw);
139
- return typeof parsed.text === "string" ? parsed.text.trim() : "";
223
+ if (typeof parsed.text === "string") {
224
+ return parsed.text.trim();
225
+ }
226
+ if (Array.isArray(parsed.transcription)) {
227
+ return parsed.transcription
228
+ .map((entry) => (typeof entry?.text === "string" ? entry.text.trim() : ""))
229
+ .filter(Boolean)
230
+ .join(" ")
231
+ .trim();
232
+ }
233
+ return "";
140
234
  }
141
235
  finally {
142
236
  await fs.rm(workDir, { recursive: true, force: true }).catch(() => undefined);
@@ -178,7 +272,8 @@ async function hydrateBlueBubblesAttachments(attachments, config, channelConfig,
178
272
  },
179
273
  });
180
274
  const fetchImpl = deps.fetchImpl ?? fetch;
181
- const transcribeAudio = deps.transcribeAudio ?? transcribeAudioWithWhisper;
275
+ const modelFetchImpl = deps.modelFetchImpl ?? fetch;
276
+ const transcribeAudio = deps.transcribeAudio ?? ((params) => transcribeAudioWithWhisperCpp(params, modelFetchImpl));
182
277
  const preferAudioInput = deps.preferAudioInput ?? false;
183
278
  const inputParts = [];
184
279
  const transcriptAdditions = [];
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.normalizeBlueBubblesEvent = normalizeBlueBubblesEvent;
4
- const runtime_1 = require("../nerves/runtime");
4
+ const runtime_1 = require("../../nerves/runtime");
5
5
  function asRecord(value) {
6
6
  return value && typeof value === "object" && !Array.isArray(value)
7
7
  ? value
@@ -39,6 +39,7 @@ function extractChatIdentifierFromGuid(chatGuid) {
39
39
  return parts.length >= 3 ? parts[2]?.trim() || undefined : undefined;
40
40
  }
41
41
  function buildChatRef(data, threadOriginatorGuid) {
42
+ void threadOriginatorGuid;
42
43
  const chats = Array.isArray(data.chats) ? data.chats : [];
43
44
  const chat = asRecord(chats[0]) ?? null;
44
45
  const chatGuid = readString(chat, "guid");
@@ -48,12 +49,18 @@ function buildChatRef(data, threadOriginatorGuid) {
48
49
  const displayName = readString(chat, "displayName")?.trim() || undefined;
49
50
  const style = readNumber(chat, "style");
50
51
  const isGroup = style === 43 || (chatGuid?.includes(";+;") ?? false) || Boolean(displayName);
51
- const baseKey = chatGuid?.trim()
52
+ const sessionKey = chatGuid?.trim()
52
53
  ? `chat:${chatGuid.trim()}`
53
54
  : `chat_identifier:${(chatIdentifier ?? "unknown").trim()}`;
54
- const sessionKey = threadOriginatorGuid?.trim()
55
- ? `${baseKey}:thread:${threadOriginatorGuid.trim()}`
56
- : baseKey;
55
+ // Extract participant handles from chat.participants (when available from BB API)
56
+ const rawParticipants = Array.isArray(chat?.participants) ? chat.participants : [];
57
+ const participantHandles = rawParticipants
58
+ .map((p) => {
59
+ const rec = asRecord(p);
60
+ const addr = readString(rec, "address") ?? readString(rec, "id");
61
+ return addr ? normalizeHandle(addr) : "";
62
+ })
63
+ .filter(Boolean);
57
64
  return {
58
65
  chatGuid: chatGuid?.trim() || undefined,
59
66
  chatIdentifier: chatIdentifier?.trim() || undefined,
@@ -63,6 +70,7 @@ function buildChatRef(data, threadOriginatorGuid) {
63
70
  sendTarget: chatGuid?.trim()
64
71
  ? { kind: "chat_guid", value: chatGuid.trim() }
65
72
  : { kind: "chat_identifier", value: (chatIdentifier ?? "unknown").trim() },
73
+ participantHandles,
66
74
  };
67
75
  }
68
76
  function extractSender(data, chat) {
@@ -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
- const os = __importStar(require("node:os"));
40
40
  const path = __importStar(require("node:path"));
41
- const runtime_1 = require("../nerves/runtime");
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(os.homedir(), ".agentstate", agentName, "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,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
+ }
@@ -0,0 +1,72 @@
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.findObsoleteBlueBubblesThreadSessions = findObsoleteBlueBubblesThreadSessions;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const runtime_1 = require("../../nerves/runtime");
40
+ function findObsoleteBlueBubblesThreadSessions(trunkSessionPath) {
41
+ const normalized = trunkSessionPath.trim();
42
+ if (!normalized.endsWith(".json"))
43
+ return [];
44
+ const trunkName = path.basename(normalized);
45
+ if (trunkName.includes("_thread_"))
46
+ return [];
47
+ if (!fs.existsSync(normalized))
48
+ return [];
49
+ const dir = path.dirname(normalized);
50
+ const prefix = trunkName.slice(0, -".json".length);
51
+ const threadLaneFiles = [];
52
+ for (const entry of fs.readdirSync(dir)) {
53
+ if (!entry.endsWith(".json"))
54
+ continue;
55
+ if (!entry.startsWith(`${prefix}_thread_`))
56
+ continue;
57
+ threadLaneFiles.push(path.join(dir, entry));
58
+ }
59
+ if (threadLaneFiles.length > 0) {
60
+ (0, runtime_1.emitNervesEvent)({
61
+ level: "warn",
62
+ component: "senses",
63
+ event: "senses.bluebubbles_thread_lane_artifacts_detected",
64
+ message: "detected obsolete bluebubbles thread-lane sessions",
65
+ meta: {
66
+ sessionPath: normalized,
67
+ artifactCount: threadLaneFiles.length,
68
+ },
69
+ });
70
+ }
71
+ return threadLaneFiles;
72
+ }
@@ -0,0 +1,156 @@
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.InkApp = InkApp;
37
+ const jsx_runtime_1 = require("react/jsx-runtime");
38
+ const react_1 = __importStar(require("react"));
39
+ const ink_1 = require("ink");
40
+ const streaming_markdown_1 = require("./streaming-markdown");
41
+ /**
42
+ * Ouroboros CLI TUI — Ink application shell.
43
+ *
44
+ * Replaces the imperative readline-based CLI with a React/Ink component tree.
45
+ * Provides: message list, input area, spinner, tool result display.
46
+ *
47
+ * Design: Ouroboros-themed (green serpent), NOT Claude/Anthropic design language.
48
+ * Copy-paste integrity: no padding characters. Visual hierarchy via color/bold/dim only.
49
+ */
50
+ // Ouroboros brand palette
51
+ const OURO_GREEN = "#2ecc40";
52
+ const OURO_TEAL = "#4ec9b0";
53
+ const OURO_DIM = true;
54
+ // Snake-themed spinner frames: serpent eating its own tail
55
+ const SNAKE_FRAMES = [
56
+ "\u{1F40D}\u2003", // snake emoji
57
+ "\u25E0\u2003", // upper half circle
58
+ "\u25D4\u2003", // circle with upper right quadrant
59
+ "\u25D1\u2003", // circle with right half
60
+ "\u25D5\u2003", // circle with all but upper left quadrant
61
+ "\u25E1\u2003", // lower half circle
62
+ "\u{1F40D}\u2003", // snake emoji again
63
+ "\u25CB\u2003", // white circle (tail eaten)
64
+ ];
65
+ function UserMessage({ content }) {
66
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO_TEAL, bold: true, children: ") " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: content })] }));
67
+ }
68
+ function AssistantMessage({ content }) {
69
+ return ((0, jsx_runtime_1.jsx)(ink_1.Box, { children: (0, jsx_runtime_1.jsx)(streaming_markdown_1.StreamingMarkdown, { text: content }) }));
70
+ }
71
+ function ToolCallDisplay({ toolCall }) {
72
+ let argSummary = "";
73
+ try {
74
+ const args = JSON.parse(toolCall.function.arguments);
75
+ const first = Object.values(args)[0];
76
+ if (typeof first === "string")
77
+ argSummary = first.length > 60 ? first.slice(0, 57) + "..." : first;
78
+ }
79
+ catch {
80
+ argSummary = toolCall.function.arguments.slice(0, 60);
81
+ }
82
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO_GREEN, bold: true, children: toolCall.function.name }), argSummary ? (0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [" ", argSummary] }) : null] }));
83
+ }
84
+ function ToolResultDisplay({ result }) {
85
+ const color = result.success ? OURO_GREEN : "#e74c3c";
86
+ const icon = result.success ? "\u2713" : "\u2717";
87
+ const summary = result.result.length > 120
88
+ ? result.result.slice(0, 117) + "..."
89
+ : result.result;
90
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { color: color, children: [icon, " ", result.name] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: true, children: [" ", summary.replace(/\n/g, " ")] })] }));
91
+ }
92
+ function SpinnerComponent({ text, frame }) {
93
+ const char = SNAKE_FRAMES[frame % SNAKE_FRAMES.length];
94
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO_GREEN, children: char }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { dimColor: OURO_DIM, children: [text, "..."] })] }));
95
+ }
96
+ function InputArea({ onSubmit, }) {
97
+ const [input, setInput] = (0, react_1.useState)("");
98
+ // Ref tracks the latest input value so useInput callback always sees current state
99
+ const inputRef = (0, react_1.useRef)("");
100
+ (0, ink_1.useInput)((inputChar, key) => {
101
+ if (key.return) {
102
+ if (inputRef.current.trim() && onSubmit) {
103
+ onSubmit(inputRef.current);
104
+ }
105
+ inputRef.current = "";
106
+ setInput("");
107
+ return;
108
+ }
109
+ if (key.backspace || key.delete) {
110
+ inputRef.current = inputRef.current.slice(0, -1);
111
+ setInput(inputRef.current);
112
+ return;
113
+ }
114
+ if (!key.ctrl && !key.meta && inputChar) {
115
+ inputRef.current += inputChar;
116
+ setInput(inputRef.current);
117
+ }
118
+ });
119
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { color: OURO_TEAL, bold: true, children: ") " }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: input })] }));
120
+ }
121
+ function MessageList({ messages, toolResults, }) {
122
+ const toolResultMap = new Map();
123
+ if (toolResults) {
124
+ for (const tr of toolResults) {
125
+ toolResultMap.set(tr.toolCallId, tr);
126
+ }
127
+ }
128
+ return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: messages.map((msg, i) => {
129
+ if (msg.role === "user" && msg.content) {
130
+ return (0, jsx_runtime_1.jsx)(UserMessage, { content: msg.content }, i);
131
+ }
132
+ if (msg.role === "assistant") {
133
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [msg.content ? (0, jsx_runtime_1.jsx)(AssistantMessage, { content: msg.content }) : null, msg.tool_calls?.map(tc => {
134
+ const tr = toolResultMap.get(tc.id);
135
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ToolCallDisplay, { toolCall: tc }), tr ? (0, jsx_runtime_1.jsx)(ToolResultDisplay, { result: tr }) : null] }, tc.id));
136
+ })] }, i));
137
+ }
138
+ return null;
139
+ }) }));
140
+ }
141
+ function InkApp({ messages, onSubmit, loading, spinnerText, toolResults, }) {
142
+ const [spinnerFrame, setSpinnerFrame] = (0, react_1.useState)(0);
143
+ // Animate spinner
144
+ react_1.default.useEffect(() => {
145
+ if (!loading)
146
+ return;
147
+ const iv = setInterval(() => {
148
+ setSpinnerFrame(f => (f + 1) % SNAKE_FRAMES.length);
149
+ }, 120);
150
+ return () => clearInterval(iv);
151
+ }, [loading]);
152
+ const handleSubmit = (0, react_1.useCallback)((text) => {
153
+ onSubmit?.(text);
154
+ }, [onSubmit]);
155
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(MessageList, { messages: messages, toolResults: toolResults }), loading && spinnerText ? ((0, jsx_runtime_1.jsx)(SpinnerComponent, { text: spinnerText, frame: spinnerFrame })) : null, (0, jsx_runtime_1.jsx)(InputArea, { onSubmit: handleSubmit })] }));
156
+ }