@ouro.bot/cli 0.1.0-alpha.18 → 0.1.0-alpha.181

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 (185) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +3 -2
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +3 -3
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/README.md +187 -191
  5. package/changelog.json +1219 -0
  6. package/dist/heart/active-work.js +633 -0
  7. package/dist/heart/bridges/manager.js +358 -0
  8. package/dist/heart/bridges/state-machine.js +135 -0
  9. package/dist/heart/bridges/store.js +123 -0
  10. package/dist/heart/commitments.js +105 -0
  11. package/dist/heart/config.js +40 -30
  12. package/dist/heart/core.js +620 -124
  13. package/dist/heart/cross-chat-delivery.js +146 -0
  14. package/dist/heart/daemon/agent-discovery.js +81 -0
  15. package/dist/heart/daemon/agent-service.js +360 -0
  16. package/dist/heart/daemon/auth-flow.js +477 -0
  17. package/dist/heart/daemon/cadence.js +70 -0
  18. package/dist/heart/daemon/daemon-cli.js +2476 -206
  19. package/dist/heart/daemon/daemon-entry.js +157 -4
  20. package/dist/heart/daemon/daemon-health.js +136 -0
  21. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  22. package/dist/heart/daemon/daemon-tombstone.js +123 -0
  23. package/dist/heart/daemon/daemon.js +449 -17
  24. package/dist/heart/daemon/habit-migration.js +181 -0
  25. package/dist/heart/daemon/habit-parser.js +119 -0
  26. package/dist/heart/daemon/habit-scheduler.js +369 -0
  27. package/dist/heart/daemon/hatch-animation.js +10 -3
  28. package/dist/heart/daemon/hatch-flow.js +30 -111
  29. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  30. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  31. package/dist/heart/daemon/inner-status.js +89 -0
  32. package/dist/heart/daemon/launchd.js +165 -0
  33. package/dist/heart/daemon/log-tailer.js +4 -3
  34. package/dist/heart/daemon/mcp-server.js +591 -0
  35. package/dist/heart/daemon/message-router.js +17 -8
  36. package/dist/heart/daemon/os-cron-deps.js +134 -0
  37. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  38. package/dist/heart/daemon/ouro-path-installer.js +78 -34
  39. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  40. package/dist/heart/daemon/outlook-http.js +399 -0
  41. package/dist/heart/daemon/outlook-read.js +1291 -0
  42. package/dist/heart/daemon/outlook-render.js +1032 -0
  43. package/dist/heart/daemon/outlook-types.js +7 -0
  44. package/dist/heart/daemon/outlook-view.js +194 -0
  45. package/dist/heart/daemon/process-manager.js +75 -0
  46. package/dist/heart/daemon/run-hooks.js +39 -0
  47. package/dist/heart/daemon/runtime-logging.js +58 -15
  48. package/dist/heart/daemon/runtime-metadata.js +219 -0
  49. package/dist/heart/daemon/runtime-mode.js +67 -0
  50. package/dist/heart/daemon/safe-mode.js +161 -0
  51. package/dist/heart/daemon/sense-manager.js +50 -2
  52. package/dist/heart/daemon/session-id-resolver.js +131 -0
  53. package/dist/heart/daemon/skill-management-installer.js +94 -0
  54. package/dist/heart/daemon/socket-client.js +202 -0
  55. package/dist/heart/daemon/specialist-orchestrator.js +2 -2
  56. package/dist/heart/daemon/specialist-prompt.js +13 -9
  57. package/dist/heart/daemon/specialist-tools.js +78 -9
  58. package/dist/heart/daemon/staged-restart.js +114 -0
  59. package/dist/heart/daemon/task-scheduler.js +3 -25
  60. package/dist/heart/daemon/thoughts.js +507 -0
  61. package/dist/heart/daemon/update-checker.js +111 -0
  62. package/dist/heart/daemon/update-hooks.js +138 -0
  63. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  64. package/dist/heart/delegation.js +62 -0
  65. package/dist/heart/identity.js +136 -55
  66. package/dist/heart/kicks.js +2 -20
  67. package/dist/heart/migrate-config.js +127 -0
  68. package/dist/heart/model-capabilities.js +48 -0
  69. package/dist/heart/obligations.js +197 -0
  70. package/dist/heart/progress-story.js +42 -0
  71. package/dist/heart/provider-failover.js +88 -0
  72. package/dist/heart/provider-ping.js +172 -0
  73. package/dist/heart/providers/anthropic-token.js +163 -0
  74. package/dist/heart/providers/anthropic.js +196 -36
  75. package/dist/heart/providers/azure.js +116 -10
  76. package/dist/heart/providers/github-copilot.js +157 -0
  77. package/dist/heart/providers/minimax.js +33 -3
  78. package/dist/heart/providers/openai-codex.js +51 -16
  79. package/dist/heart/session-activity.js +172 -0
  80. package/dist/heart/session-recall.js +216 -0
  81. package/dist/heart/streaming.js +62 -32
  82. package/dist/heart/target-resolution.js +127 -0
  83. package/dist/heart/tool-activity-callbacks.js +36 -0
  84. package/dist/heart/tool-description.js +135 -0
  85. package/dist/heart/tool-loop.js +200 -0
  86. package/dist/heart/turn-coordinator.js +28 -0
  87. package/dist/mind/associative-recall.js +122 -32
  88. package/dist/mind/bundle-manifest.js +14 -0
  89. package/dist/mind/context.js +125 -14
  90. package/dist/mind/{memory.js → diary.js} +70 -56
  91. package/dist/mind/file-state.js +179 -0
  92. package/dist/mind/first-impressions.js +16 -2
  93. package/dist/mind/friends/channel.js +56 -0
  94. package/dist/mind/friends/group-context.js +144 -0
  95. package/dist/mind/friends/store-file.js +19 -0
  96. package/dist/mind/friends/trust-explanation.js +74 -0
  97. package/dist/mind/friends/types.js +8 -0
  98. package/dist/mind/journal-index.js +161 -0
  99. package/dist/mind/obligation-steering.js +221 -0
  100. package/dist/mind/obligations.js +134 -0
  101. package/dist/mind/pending.js +76 -9
  102. package/dist/mind/phrases.js +1 -0
  103. package/dist/mind/prompt.js +721 -102
  104. package/dist/mind/scrutiny.js +173 -0
  105. package/dist/mind/token-estimate.js +8 -12
  106. package/dist/nerves/cli-logging.js +15 -2
  107. package/dist/nerves/coverage/audit.js +1 -1
  108. package/dist/nerves/coverage/run-artifacts.js +1 -1
  109. package/dist/nerves/event-buffer.js +111 -0
  110. package/dist/nerves/index.js +63 -3
  111. package/dist/nerves/observation.js +20 -0
  112. package/dist/nerves/runtime.js +5 -1
  113. package/dist/outlook-ui/assets/index-DN6vVYPm.css +1 -0
  114. package/dist/outlook-ui/assets/index-M1l1tDTX.js +61 -0
  115. package/dist/outlook-ui/index.html +15 -0
  116. package/dist/repertoire/ado-semantic.js +11 -5
  117. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  118. package/dist/repertoire/coding/context-pack.js +254 -0
  119. package/dist/repertoire/coding/feedback.js +197 -30
  120. package/dist/repertoire/coding/manager.js +149 -2
  121. package/dist/repertoire/coding/spawner.js +55 -9
  122. package/dist/repertoire/coding/tools.js +170 -7
  123. package/dist/repertoire/guardrails.js +291 -0
  124. package/dist/repertoire/mcp-client.js +254 -0
  125. package/dist/repertoire/mcp-manager.js +198 -0
  126. package/dist/repertoire/shell-sessions.js +133 -0
  127. package/dist/repertoire/skills.js +14 -23
  128. package/dist/repertoire/tasks/board.js +43 -5
  129. package/dist/repertoire/tasks/fix.js +182 -0
  130. package/dist/repertoire/tasks/index.js +28 -10
  131. package/dist/repertoire/tasks/lifecycle.js +2 -2
  132. package/dist/repertoire/tasks/parser.js +3 -2
  133. package/dist/repertoire/tasks/scanner.js +194 -37
  134. package/dist/repertoire/tasks/transitions.js +11 -28
  135. package/dist/repertoire/tools-base.js +1111 -327
  136. package/dist/repertoire/tools-bluebubbles.js +94 -0
  137. package/dist/repertoire/tools-github.js +1 -6
  138. package/dist/repertoire/tools-teams.js +12 -59
  139. package/dist/repertoire/tools.js +318 -124
  140. package/dist/scripts/claude-code-hook.js +41 -0
  141. package/dist/scripts/claude-code-stop-hook.js +47 -0
  142. package/dist/senses/attention-queue.js +97 -0
  143. package/dist/senses/bluebubbles-client.js +102 -3
  144. package/dist/senses/bluebubbles-entry.js +2 -0
  145. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  146. package/dist/senses/bluebubbles-media.js +118 -23
  147. package/dist/senses/bluebubbles-model.js +12 -4
  148. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  149. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  150. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  151. package/dist/senses/bluebubbles.js +1008 -102
  152. package/dist/senses/cli/adapter.js +274 -0
  153. package/dist/senses/cli/ink-app.js +156 -0
  154. package/dist/senses/cli/inline-diff.js +64 -0
  155. package/dist/senses/cli/message-list.js +51 -0
  156. package/dist/senses/cli/ouro-tui.js +323 -0
  157. package/dist/senses/cli/spinner-imperative.js +135 -0
  158. package/dist/senses/cli/spinner.js +101 -0
  159. package/dist/senses/cli/status-line.js +60 -0
  160. package/dist/senses/cli/streaming-markdown.js +525 -0
  161. package/dist/senses/cli/tool-display.js +83 -0
  162. package/dist/senses/cli/tool-render.js +85 -0
  163. package/dist/senses/cli/tui-store.js +198 -0
  164. package/dist/senses/cli/virtual-list.js +35 -0
  165. package/dist/senses/cli-entry.js +1 -1
  166. package/dist/senses/cli-layout.js +187 -0
  167. package/dist/senses/cli.js +593 -240
  168. package/dist/senses/commands.js +38 -1
  169. package/dist/senses/continuity.js +94 -0
  170. package/dist/senses/habit-turn-message.js +108 -0
  171. package/dist/senses/inner-dialog-worker.js +112 -19
  172. package/dist/senses/inner-dialog.js +592 -84
  173. package/dist/senses/pipeline.js +545 -0
  174. package/dist/senses/shared-turn.js +196 -0
  175. package/dist/senses/surface-tool.js +95 -0
  176. package/dist/senses/teams.js +667 -120
  177. package/dist/senses/trust-gate.js +112 -2
  178. package/package.json +16 -5
  179. package/skills/configure-dev-tools.md +81 -0
  180. package/subagents/README.md +4 -70
  181. package/dist/heart/daemon/subagent-installer.js +0 -134
  182. package/dist/senses/debug-activity.js +0 -107
  183. package/subagents/work-doer.md +0 -235
  184. package/subagents/work-merger.md +0 -618
  185. package/subagents/work-planner.md +0 -382
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAttentionQueue = buildAttentionQueue;
4
+ exports.dequeueAttentionItem = dequeueAttentionItem;
5
+ exports.attentionQueueEmpty = attentionQueueEmpty;
6
+ exports.buildAttentionQueueSummary = buildAttentionQueueSummary;
7
+ const runtime_1 = require("../nerves/runtime");
8
+ // ── Queue construction ───────────────────────────────────────────
9
+ function generateItemId() {
10
+ return Math.random().toString(36).slice(2, 10);
11
+ }
12
+ function originKey(friendId, channel, key) {
13
+ return `${friendId}/${channel}/${key}`;
14
+ }
15
+ function buildAttentionQueue(input) {
16
+ const { drainedPending, outstandingObligations, friendNameResolver } = input;
17
+ const seen = new Set();
18
+ const items = [];
19
+ // Source 1: drained pending messages with delegatedFrom (current-turn delegations)
20
+ for (const msg of drainedPending) {
21
+ if (!msg.delegatedFrom)
22
+ continue;
23
+ const { friendId, channel, key, bridgeId } = msg.delegatedFrom;
24
+ const oKey = originKey(friendId, channel, key);
25
+ seen.add(oKey);
26
+ const resolvedName = friendNameResolver(friendId);
27
+ items.push({
28
+ id: msg.obligationId ?? generateItemId(),
29
+ friendId,
30
+ friendName: resolvedName ?? friendId,
31
+ channel,
32
+ key,
33
+ ...(bridgeId ? { bridgeId } : {}),
34
+ delegatedContent: msg.content,
35
+ ...(msg.obligationId ? { obligationId: msg.obligationId } : {}),
36
+ source: "drained",
37
+ timestamp: msg.timestamp,
38
+ });
39
+ }
40
+ // Source 2: outstanding obligations (crash recovery)
41
+ for (const obligation of outstandingObligations) {
42
+ const { friendId, channel, key, bridgeId } = obligation.origin;
43
+ const oKey = originKey(friendId, channel, key);
44
+ if (seen.has(oKey))
45
+ continue; // deduplicate: prefer drained version
46
+ seen.add(oKey);
47
+ const resolvedName = friendNameResolver(friendId);
48
+ items.push({
49
+ id: obligation.id,
50
+ friendId,
51
+ friendName: resolvedName ?? friendId,
52
+ channel,
53
+ key,
54
+ ...(bridgeId ? { bridgeId } : {}),
55
+ delegatedContent: obligation.delegatedContent,
56
+ obligationId: obligation.id,
57
+ source: "obligation-recovery",
58
+ timestamp: obligation.createdAt,
59
+ });
60
+ }
61
+ // Sort FIFO (oldest first)
62
+ items.sort((a, b) => a.timestamp - b.timestamp);
63
+ (0, runtime_1.emitNervesEvent)({
64
+ event: "senses.attention_queue_built",
65
+ component: "senses",
66
+ message: `attention queue built with ${items.length} item(s)`,
67
+ meta: {
68
+ drainedCount: items.filter((i) => i.source === "drained").length,
69
+ recoveredCount: items.filter((i) => i.source === "obligation-recovery").length,
70
+ },
71
+ });
72
+ return items;
73
+ }
74
+ // ── Queue operations ─────────────────────────────────────────────
75
+ function dequeueAttentionItem(queue, id) {
76
+ const index = queue.findIndex((item) => item.id === id);
77
+ if (index === -1)
78
+ return null;
79
+ return queue.splice(index, 1)[0];
80
+ }
81
+ function attentionQueueEmpty(queue) {
82
+ return queue.length === 0;
83
+ }
84
+ // ── Queue visibility ─────────────────────────────────────────────
85
+ const CONTENT_PREVIEW_MAX = 80;
86
+ function buildAttentionQueueSummary(queue) {
87
+ if (queue.length === 0)
88
+ return "";
89
+ const lines = ["you're holding:"];
90
+ for (const item of queue) {
91
+ const preview = item.delegatedContent.length > CONTENT_PREVIEW_MAX
92
+ ? `${item.delegatedContent.slice(0, CONTENT_PREVIEW_MAX - 3)}...`
93
+ : item.delegatedContent;
94
+ lines.push(`- [${item.id}] ${item.friendName} asked: "${preview}"`);
95
+ }
96
+ return lines.join("\n");
97
+ }
@@ -170,6 +170,12 @@ function applyRepairNotice(event, notice) {
170
170
  repairNotice: notice,
171
171
  };
172
172
  }
173
+ function hasRecoverableMessageContent(event) {
174
+ return event.kind === "message"
175
+ && (event.textForAgent.trim().length > 0
176
+ || event.attachments.length > 0
177
+ || event.hasPayloadData);
178
+ }
173
179
  function hydrateTextForAgent(event, rawData) {
174
180
  if (event.kind !== "message") {
175
181
  return { ...event, requiresRepair: false };
@@ -196,7 +202,8 @@ function extractRepairData(payload) {
196
202
  return asRecord(record?.data) ?? record;
197
203
  }
198
204
  function providerSupportsAudioInput(provider) {
199
- return provider === "azure" || provider === "openai-codex";
205
+ void provider;
206
+ return false;
200
207
  }
201
208
  async function resolveChatGuid(chat, config, channelConfig) {
202
209
  return chat.chatGuid
@@ -322,6 +329,40 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
322
329
  throw new Error(`BlueBubbles read failed (${response.status}): ${errorText || "unknown"}`);
323
330
  }
324
331
  },
332
+ async checkHealth() {
333
+ const url = buildBlueBubblesApiUrl(config.serverUrl, "/api/v1/message/count", config.password);
334
+ (0, runtime_1.emitNervesEvent)({
335
+ component: "senses",
336
+ event: "senses.bluebubbles_healthcheck_start",
337
+ message: "probing bluebubbles upstream health",
338
+ meta: { serverUrl: config.serverUrl },
339
+ });
340
+ const response = await fetch(url, {
341
+ method: "GET",
342
+ signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
343
+ });
344
+ if (!response.ok) {
345
+ const errorText = await response.text().catch(() => "");
346
+ (0, runtime_1.emitNervesEvent)({
347
+ level: "warn",
348
+ component: "senses",
349
+ event: "senses.bluebubbles_healthcheck_error",
350
+ message: "bluebubbles upstream health probe failed",
351
+ meta: {
352
+ serverUrl: config.serverUrl,
353
+ status: response.status,
354
+ reason: errorText || "unknown",
355
+ },
356
+ });
357
+ throw new Error(`BlueBubbles upstream health check failed (${response.status}): ${errorText || "unknown"}`);
358
+ }
359
+ (0, runtime_1.emitNervesEvent)({
360
+ component: "senses",
361
+ event: "senses.bluebubbles_healthcheck_end",
362
+ message: "bluebubbles upstream health probe succeeded",
363
+ meta: { serverUrl: config.serverUrl },
364
+ });
365
+ },
325
366
  async repairEvent(event) {
326
367
  if (!event.requiresRepair) {
327
368
  (0, runtime_1.emitNervesEvent)({
@@ -386,12 +427,21 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
386
427
  type: event.eventType,
387
428
  data,
388
429
  });
389
- let hydrated = hydrateTextForAgent(normalized, data);
430
+ const recoveredMessage = event.kind === "mutation"
431
+ && !event.shouldNotifyAgent
432
+ ? (0, bluebubbles_model_1.normalizeBlueBubblesEvent)({
433
+ type: "new-message",
434
+ data,
435
+ })
436
+ : null;
437
+ let hydrated = recoveredMessage && hasRecoverableMessageContent(recoveredMessage)
438
+ ? hydrateTextForAgent(recoveredMessage, data)
439
+ : hydrateTextForAgent(normalized, data);
390
440
  if (hydrated.kind === "message" &&
391
441
  hydrated.balloonBundleId !== "com.apple.messages.URLBalloonProvider" &&
392
442
  hydrated.attachments.length > 0) {
393
443
  const media = await (0, bluebubbles_media_1.hydrateBlueBubblesAttachments)(hydrated.attachments, config, channelConfig, {
394
- preferAudioInput: providerSupportsAudioInput((0, identity_1.loadAgentConfig)().provider),
444
+ preferAudioInput: providerSupportsAudioInput((0, identity_1.loadAgentConfig)().humanFacing.provider),
395
445
  });
396
446
  const transcriptSuffix = media.transcriptAdditions.map((entry) => `[${entry}]`).join("\n");
397
447
  const noticeSuffix = media.notices.map((entry) => `[${entry}]`).join("\n");
@@ -410,6 +460,7 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
410
460
  kind: hydrated.kind,
411
461
  messageGuid: hydrated.messageGuid,
412
462
  repairedFrom: event.kind,
463
+ promotedFromMutation: event.kind === "mutation" && hydrated.kind === "message",
413
464
  },
414
465
  });
415
466
  return hydrated;
@@ -429,5 +480,53 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
429
480
  return applyRepairNotice(event, `BlueBubbles repair failed: ${reason}`);
430
481
  }
431
482
  },
483
+ async getMessageText(messageGuid) {
484
+ const url = buildRepairUrl(config.serverUrl, messageGuid, config.password);
485
+ try {
486
+ const response = await fetch(url, {
487
+ method: "GET",
488
+ signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
489
+ });
490
+ if (!response.ok) {
491
+ (0, runtime_1.emitNervesEvent)({
492
+ level: "warn",
493
+ component: "senses",
494
+ event: "senses.bluebubbles_get_message_text_error",
495
+ message: "failed to fetch message text",
496
+ meta: { messageGuid, status: response.status },
497
+ });
498
+ return null;
499
+ }
500
+ const payload = await parseJsonBody(response);
501
+ const data = extractRepairData(payload);
502
+ if (!data || typeof data.text !== "string") {
503
+ (0, runtime_1.emitNervesEvent)({
504
+ level: "warn",
505
+ component: "senses",
506
+ event: "senses.bluebubbles_get_message_text_error",
507
+ message: "message payload missing text field",
508
+ meta: { messageGuid, hasData: !!data, textType: data ? typeof data.text : "n/a" },
509
+ });
510
+ return null;
511
+ }
512
+ (0, runtime_1.emitNervesEvent)({
513
+ component: "senses",
514
+ event: "senses.bluebubbles_get_message_text",
515
+ message: "fetched message text by guid",
516
+ meta: { messageGuid },
517
+ });
518
+ return data.text.trim() || null;
519
+ }
520
+ catch (error) {
521
+ (0, runtime_1.emitNervesEvent)({
522
+ level: "warn",
523
+ component: "senses",
524
+ event: "senses.bluebubbles_get_message_text_error",
525
+ message: "exception fetching message text",
526
+ meta: { messageGuid, reason: error instanceof Error ? error.message : String(error) },
527
+ });
528
+ return null;
529
+ }
530
+ },
432
531
  };
433
532
  }
@@ -8,4 +8,6 @@ if (!process.argv.includes("--agent")) {
8
8
  process.exit(1);
9
9
  }
10
10
  const bluebubbles_1 = require("./bluebubbles");
11
+ const runtime_logging_1 = require("../heart/daemon/runtime-logging");
12
+ (0, runtime_logging_1.configureDaemonRuntimeLogger)("bluebubbles");
11
13
  (0, bluebubbles_1.startBlueBubblesApp)();
@@ -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.getBlueBubblesInboundLogPath = getBlueBubblesInboundLogPath;
37
+ exports.hasRecordedBlueBubblesInbound = hasRecordedBlueBubblesInbound;
38
+ exports.recordBlueBubblesInbound = recordBlueBubblesInbound;
39
+ const fs = __importStar(require("node:fs"));
40
+ const path = __importStar(require("node:path"));
41
+ const config_1 = require("../heart/config");
42
+ const identity_1 = require("../heart/identity");
43
+ const runtime_1 = require("../nerves/runtime");
44
+ function getBlueBubblesInboundLogPath(agentName, sessionKey) {
45
+ return path.join((0, identity_1.getAgentRoot)(agentName), "state", "senses", "bluebubbles", "inbound", `${(0, config_1.sanitizeKey)(sessionKey)}.ndjson`);
46
+ }
47
+ function readEntries(filePath) {
48
+ try {
49
+ const raw = fs.readFileSync(filePath, "utf-8");
50
+ return raw
51
+ .split("\n")
52
+ .map((line) => line.trim())
53
+ .filter(Boolean)
54
+ .map((line) => JSON.parse(line))
55
+ .filter((entry) => typeof entry.messageGuid === "string" && typeof entry.sessionKey === "string");
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ }
61
+ function hasRecordedBlueBubblesInbound(agentName, sessionKey, messageGuid) {
62
+ if (!messageGuid.trim())
63
+ return false;
64
+ const filePath = getBlueBubblesInboundLogPath(agentName, sessionKey);
65
+ return readEntries(filePath).some((entry) => entry.messageGuid === messageGuid);
66
+ }
67
+ function recordBlueBubblesInbound(agentName, event, source) {
68
+ const filePath = getBlueBubblesInboundLogPath(agentName, event.chat.sessionKey);
69
+ try {
70
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
71
+ fs.appendFileSync(filePath, JSON.stringify({
72
+ recordedAt: new Date(event.timestamp).toISOString(),
73
+ messageGuid: event.messageGuid,
74
+ chatGuid: event.chat.chatGuid ?? null,
75
+ chatIdentifier: event.chat.chatIdentifier ?? null,
76
+ sessionKey: event.chat.sessionKey,
77
+ textForAgent: event.textForAgent,
78
+ source,
79
+ }) + "\n", "utf-8");
80
+ }
81
+ catch (error) {
82
+ (0, runtime_1.emitNervesEvent)({
83
+ level: "warn",
84
+ component: "senses",
85
+ event: "senses.bluebubbles_inbound_log_error",
86
+ message: "failed to record bluebubbles inbound sidecar log",
87
+ meta: {
88
+ agentName,
89
+ messageGuid: event.messageGuid,
90
+ sessionKey: event.chat.sessionKey,
91
+ reason: error instanceof Error ? error.message : String(error),
92
+ },
93
+ });
94
+ return filePath;
95
+ }
96
+ (0, runtime_1.emitNervesEvent)({
97
+ component: "senses",
98
+ event: "senses.bluebubbles_inbound_logged",
99
+ message: "recorded bluebubbles inbound message to sidecar log",
100
+ meta: {
101
+ agentName,
102
+ messageGuid: event.messageGuid,
103
+ sessionKey: event.chat.sessionKey,
104
+ source,
105
+ path: filePath,
106
+ },
107
+ });
108
+ return filePath;
109
+ }
@@ -39,6 +39,7 @@ const fs = __importStar(require("node:fs/promises"));
39
39
  const os = __importStar(require("node:os"));
40
40
  const path = __importStar(require("node:path"));
41
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 = [];
@@ -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
41
  const runtime_1 = require("../nerves/runtime");
42
- function sanitizeKey(key) {
43
- return key.replace(/[/:]/g, "_");
44
- }
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
+ }