@ouro.bot/cli 0.1.0-alpha.5 → 0.1.0-alpha.50

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 (102) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +70 -9
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/README.md +117 -188
  5. package/assets/ouroboros.png +0 -0
  6. package/changelog.json +242 -0
  7. package/dist/heart/active-work.js +157 -0
  8. package/dist/heart/bridges/manager.js +358 -0
  9. package/dist/heart/bridges/state-machine.js +135 -0
  10. package/dist/heart/bridges/store.js +123 -0
  11. package/dist/heart/config.js +81 -8
  12. package/dist/heart/core.js +145 -50
  13. package/dist/heart/daemon/agent-discovery.js +81 -0
  14. package/dist/heart/daemon/daemon-cli.js +1099 -164
  15. package/dist/heart/daemon/daemon-entry.js +14 -5
  16. package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
  17. package/dist/heart/daemon/daemon.js +184 -9
  18. package/dist/heart/daemon/hatch-animation.js +10 -3
  19. package/dist/heart/daemon/hatch-flow.js +3 -20
  20. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  21. package/dist/heart/daemon/launchd.js +151 -0
  22. package/dist/heart/daemon/message-router.js +15 -6
  23. package/dist/heart/daemon/ouro-bot-entry.js +0 -0
  24. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  25. package/dist/heart/daemon/ouro-entry.js +0 -0
  26. package/dist/heart/daemon/ouro-path-installer.js +178 -0
  27. package/dist/heart/daemon/ouro-uti.js +11 -2
  28. package/dist/heart/daemon/process-manager.js +1 -1
  29. package/dist/heart/daemon/run-hooks.js +37 -0
  30. package/dist/heart/daemon/runtime-metadata.js +118 -0
  31. package/dist/heart/daemon/sense-manager.js +290 -0
  32. package/dist/heart/daemon/socket-client.js +202 -0
  33. package/dist/heart/daemon/specialist-orchestrator.js +53 -84
  34. package/dist/heart/daemon/specialist-prompt.js +64 -5
  35. package/dist/heart/daemon/specialist-tools.js +213 -58
  36. package/dist/heart/daemon/staged-restart.js +114 -0
  37. package/dist/heart/daemon/subagent-installer.js +48 -7
  38. package/dist/heart/daemon/thoughts.js +379 -0
  39. package/dist/heart/daemon/update-checker.js +111 -0
  40. package/dist/heart/daemon/update-hooks.js +138 -0
  41. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  42. package/dist/heart/delegation.js +62 -0
  43. package/dist/heart/identity.js +82 -4
  44. package/dist/heart/kicks.js +1 -19
  45. package/dist/heart/progress-story.js +42 -0
  46. package/dist/heart/providers/anthropic.js +16 -2
  47. package/dist/heart/sense-truth.js +61 -0
  48. package/dist/heart/session-activity.js +169 -0
  49. package/dist/heart/session-recall.js +116 -0
  50. package/dist/heart/streaming.js +96 -21
  51. package/dist/heart/turn-coordinator.js +28 -0
  52. package/dist/mind/associative-recall.js +14 -2
  53. package/dist/mind/bundle-manifest.js +70 -0
  54. package/dist/mind/context.js +27 -11
  55. package/dist/mind/first-impressions.js +16 -2
  56. package/dist/mind/friends/channel.js +43 -0
  57. package/dist/mind/friends/store-file.js +19 -0
  58. package/dist/mind/friends/types.js +9 -1
  59. package/dist/mind/memory.js +10 -3
  60. package/dist/mind/pending.js +72 -9
  61. package/dist/mind/phrases.js +1 -0
  62. package/dist/mind/prompt.js +266 -77
  63. package/dist/mind/token-estimate.js +8 -12
  64. package/dist/nerves/cli-logging.js +15 -2
  65. package/dist/repertoire/ado-client.js +4 -2
  66. package/dist/repertoire/coding/feedback.js +134 -0
  67. package/dist/repertoire/coding/index.js +4 -1
  68. package/dist/repertoire/coding/manager.js +62 -4
  69. package/dist/repertoire/coding/spawner.js +3 -3
  70. package/dist/repertoire/coding/tools.js +41 -2
  71. package/dist/repertoire/data/ado-endpoints.json +188 -0
  72. package/dist/repertoire/tasks/board.js +12 -0
  73. package/dist/repertoire/tasks/index.js +23 -9
  74. package/dist/repertoire/tasks/transitions.js +1 -2
  75. package/dist/repertoire/tools-base.js +462 -245
  76. package/dist/repertoire/tools-bluebubbles.js +93 -0
  77. package/dist/repertoire/tools-teams.js +58 -25
  78. package/dist/repertoire/tools.js +57 -35
  79. package/dist/senses/bluebubbles-client.js +484 -0
  80. package/dist/senses/bluebubbles-entry.js +13 -0
  81. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  82. package/dist/senses/bluebubbles-media.js +338 -0
  83. package/dist/senses/bluebubbles-model.js +261 -0
  84. package/dist/senses/bluebubbles-mutation-log.js +116 -0
  85. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  86. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  87. package/dist/senses/bluebubbles.js +1142 -0
  88. package/dist/senses/cli.js +340 -138
  89. package/dist/senses/continuity.js +94 -0
  90. package/dist/senses/debug-activity.js +148 -0
  91. package/dist/senses/inner-dialog-worker.js +47 -18
  92. package/dist/senses/inner-dialog.js +330 -84
  93. package/dist/senses/pipeline.js +256 -0
  94. package/dist/senses/teams.js +541 -129
  95. package/dist/senses/trust-gate.js +112 -2
  96. package/package.json +14 -3
  97. package/subagents/README.md +46 -33
  98. package/subagents/work-doer.md +28 -24
  99. package/subagents/work-merger.md +24 -30
  100. package/subagents/work-planner.md +44 -27
  101. package/dist/heart/daemon/specialist-session.js +0 -142
  102. package/dist/inner-worker-entry.js +0 -4
@@ -0,0 +1,484 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createBlueBubblesClient = createBlueBubblesClient;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const config_1 = require("../heart/config");
6
+ const identity_1 = require("../heart/identity");
7
+ const runtime_1 = require("../nerves/runtime");
8
+ const bluebubbles_model_1 = require("./bluebubbles-model");
9
+ const bluebubbles_media_1 = require("./bluebubbles-media");
10
+ function buildBlueBubblesApiUrl(baseUrl, endpoint, password) {
11
+ const root = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
12
+ const url = new URL(endpoint.replace(/^\//, ""), root);
13
+ url.searchParams.set("password", password);
14
+ return url.toString();
15
+ }
16
+ function asRecord(value) {
17
+ return value && typeof value === "object" && !Array.isArray(value)
18
+ ? value
19
+ : null;
20
+ }
21
+ function readString(record, key) {
22
+ const value = record[key];
23
+ return typeof value === "string" ? value : undefined;
24
+ }
25
+ function extractMessageGuid(payload) {
26
+ if (!payload || typeof payload !== "object")
27
+ return undefined;
28
+ const record = payload;
29
+ const data = record.data && typeof record.data === "object" && !Array.isArray(record.data)
30
+ ? record.data
31
+ : null;
32
+ const candidates = [
33
+ record.messageGuid,
34
+ record.messageId,
35
+ record.guid,
36
+ data?.messageGuid,
37
+ data?.messageId,
38
+ data?.guid,
39
+ ];
40
+ for (const candidate of candidates) {
41
+ if (typeof candidate === "string" && candidate.trim()) {
42
+ return candidate.trim();
43
+ }
44
+ }
45
+ return undefined;
46
+ }
47
+ async function parseJsonBody(response) {
48
+ const raw = await response.text();
49
+ if (!raw.trim())
50
+ return null;
51
+ try {
52
+ return JSON.parse(raw);
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ function buildRepairUrl(baseUrl, messageGuid, password) {
59
+ const url = buildBlueBubblesApiUrl(baseUrl, `/api/v1/message/${encodeURIComponent(messageGuid)}`, password);
60
+ const parsed = new URL(url);
61
+ parsed.searchParams.set("with", "attachments,payloadData,chats,messageSummaryInfo");
62
+ return parsed.toString();
63
+ }
64
+ function extractChatIdentifierFromGuid(chatGuid) {
65
+ const parts = chatGuid.split(";");
66
+ return parts.length >= 3 ? parts[2]?.trim() || undefined : undefined;
67
+ }
68
+ function extractChatGuid(value) {
69
+ const record = asRecord(value);
70
+ const candidates = [
71
+ record?.chatGuid,
72
+ record?.guid,
73
+ record?.chat_guid,
74
+ record?.identifier,
75
+ record?.chatIdentifier,
76
+ record?.chat_identifier,
77
+ ];
78
+ for (const candidate of candidates) {
79
+ if (typeof candidate === "string" && candidate.trim()) {
80
+ return candidate.trim();
81
+ }
82
+ }
83
+ return undefined;
84
+ }
85
+ function extractQueriedChatIdentifier(chat, chatGuid) {
86
+ const explicitIdentifier = readString(chat, "chatIdentifier")
87
+ ?? readString(chat, "identifier")
88
+ ?? readString(chat, "chat_identifier");
89
+ if (explicitIdentifier) {
90
+ return explicitIdentifier;
91
+ }
92
+ return extractChatIdentifierFromGuid(chatGuid);
93
+ }
94
+ function extractChatQueryRows(payload) {
95
+ const record = asRecord(payload);
96
+ const data = Array.isArray(record?.data) ? record.data : payload;
97
+ if (!Array.isArray(data)) {
98
+ return [];
99
+ }
100
+ return data.map((entry) => asRecord(entry)).filter((entry) => entry !== null);
101
+ }
102
+ async function resolveChatGuidForIdentifier(config, channelConfig, chatIdentifier) {
103
+ const trimmedIdentifier = chatIdentifier.trim();
104
+ if (!trimmedIdentifier)
105
+ return undefined;
106
+ const url = buildBlueBubblesApiUrl(config.serverUrl, "/api/v1/chat/query", config.password);
107
+ const response = await fetch(url, {
108
+ method: "POST",
109
+ headers: { "Content-Type": "application/json" },
110
+ body: JSON.stringify({
111
+ limit: 500,
112
+ offset: 0,
113
+ with: ["participants"],
114
+ }),
115
+ signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
116
+ });
117
+ if (!response.ok) {
118
+ return undefined;
119
+ }
120
+ const payload = await parseJsonBody(response);
121
+ const rows = extractChatQueryRows(payload);
122
+ for (const row of rows) {
123
+ const guid = extractChatGuid(row);
124
+ if (!guid)
125
+ continue;
126
+ const identifier = extractQueriedChatIdentifier(row, guid);
127
+ if (identifier === trimmedIdentifier || guid === trimmedIdentifier) {
128
+ return guid;
129
+ }
130
+ }
131
+ return undefined;
132
+ }
133
+ function collectPreviewStrings(value, out, depth = 0) {
134
+ if (depth > 4 || out.length >= 4)
135
+ return;
136
+ if (typeof value === "string") {
137
+ const trimmed = value.trim();
138
+ if (trimmed)
139
+ out.push(trimmed);
140
+ return;
141
+ }
142
+ if (Array.isArray(value)) {
143
+ for (const entry of value)
144
+ collectPreviewStrings(entry, out, depth + 1);
145
+ return;
146
+ }
147
+ const record = asRecord(value);
148
+ if (!record)
149
+ return;
150
+ const preferredKeys = ["title", "summary", "subtitle", "previewText", "siteName", "host", "url"];
151
+ for (const key of preferredKeys) {
152
+ if (out.length >= 4)
153
+ break;
154
+ collectPreviewStrings(record[key], out, depth + 1);
155
+ }
156
+ }
157
+ function extractLinkPreviewText(data) {
158
+ const values = [];
159
+ collectPreviewStrings(data.payloadData, values);
160
+ collectPreviewStrings(data.messageSummaryInfo, values);
161
+ const unique = [...new Set(values.map((value) => value.trim()).filter(Boolean))];
162
+ if (unique.length === 0)
163
+ return undefined;
164
+ return unique.slice(0, 2).join(" — ");
165
+ }
166
+ function applyRepairNotice(event, notice) {
167
+ return {
168
+ ...event,
169
+ requiresRepair: false,
170
+ repairNotice: notice,
171
+ };
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
+ }
179
+ function hydrateTextForAgent(event, rawData) {
180
+ if (event.kind !== "message") {
181
+ return { ...event, requiresRepair: false };
182
+ }
183
+ if (event.balloonBundleId !== "com.apple.messages.URLBalloonProvider") {
184
+ return { ...event, requiresRepair: false };
185
+ }
186
+ const previewText = extractLinkPreviewText(rawData);
187
+ if (!previewText) {
188
+ return { ...event, requiresRepair: false };
189
+ }
190
+ const base = event.text.trim();
191
+ const textForAgent = base
192
+ ? `${base}\n[link preview: ${previewText}]`
193
+ : `[link preview: ${previewText}]`;
194
+ return {
195
+ ...event,
196
+ textForAgent,
197
+ requiresRepair: false,
198
+ };
199
+ }
200
+ function extractRepairData(payload) {
201
+ const record = asRecord(payload);
202
+ return asRecord(record?.data) ?? record;
203
+ }
204
+ function providerSupportsAudioInput(provider) {
205
+ void provider;
206
+ return false;
207
+ }
208
+ async function resolveChatGuid(chat, config, channelConfig) {
209
+ return chat.chatGuid
210
+ ?? await resolveChatGuidForIdentifier(config, channelConfig, chat.chatIdentifier ?? "");
211
+ }
212
+ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(), channelConfig = (0, config_1.getBlueBubblesChannelConfig)()) {
213
+ return {
214
+ async sendText(params) {
215
+ const trimmedText = params.text.trim();
216
+ if (!trimmedText) {
217
+ throw new Error("BlueBubbles send requires non-empty text.");
218
+ }
219
+ const resolvedChatGuid = await resolveChatGuid(params.chat, config, channelConfig);
220
+ if (!resolvedChatGuid) {
221
+ throw new Error("BlueBubbles send currently requires chat.chatGuid from the inbound event.");
222
+ }
223
+ const url = buildBlueBubblesApiUrl(config.serverUrl, "/api/v1/message/text", config.password);
224
+ const body = {
225
+ chatGuid: resolvedChatGuid,
226
+ tempGuid: (0, node_crypto_1.randomUUID)(),
227
+ message: trimmedText,
228
+ };
229
+ if (params.replyToMessageGuid?.trim()) {
230
+ body.method = "private-api";
231
+ body.selectedMessageGuid = params.replyToMessageGuid.trim();
232
+ body.partIndex = 0;
233
+ }
234
+ (0, runtime_1.emitNervesEvent)({
235
+ component: "senses",
236
+ event: "senses.bluebubbles_send_start",
237
+ message: "sending bluebubbles message",
238
+ meta: {
239
+ chatGuid: resolvedChatGuid,
240
+ hasReplyTarget: Boolean(params.replyToMessageGuid?.trim()),
241
+ },
242
+ });
243
+ const response = await fetch(url, {
244
+ method: "POST",
245
+ headers: { "Content-Type": "application/json" },
246
+ body: JSON.stringify(body),
247
+ signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
248
+ });
249
+ if (!response.ok) {
250
+ const errorText = await response.text().catch(() => "");
251
+ (0, runtime_1.emitNervesEvent)({
252
+ level: "error",
253
+ component: "senses",
254
+ event: "senses.bluebubbles_send_error",
255
+ message: "bluebubbles send failed",
256
+ meta: {
257
+ status: response.status,
258
+ reason: errorText || "unknown",
259
+ },
260
+ });
261
+ throw new Error(`BlueBubbles send failed (${response.status}): ${errorText || "unknown"}`);
262
+ }
263
+ const payload = await parseJsonBody(response);
264
+ const messageGuid = extractMessageGuid(payload);
265
+ (0, runtime_1.emitNervesEvent)({
266
+ component: "senses",
267
+ event: "senses.bluebubbles_send_end",
268
+ message: "bluebubbles message sent",
269
+ meta: {
270
+ chatGuid: resolvedChatGuid,
271
+ messageGuid: messageGuid ?? null,
272
+ },
273
+ });
274
+ return { messageGuid };
275
+ },
276
+ async editMessage(params) {
277
+ const messageGuid = params.messageGuid.trim();
278
+ const text = params.text.trim();
279
+ if (!messageGuid) {
280
+ throw new Error("BlueBubbles edit requires messageGuid.");
281
+ }
282
+ if (!text) {
283
+ throw new Error("BlueBubbles edit requires non-empty text.");
284
+ }
285
+ const editTimeoutMs = Math.max(channelConfig.requestTimeoutMs, 120000);
286
+ const url = buildBlueBubblesApiUrl(config.serverUrl, `/api/v1/message/${encodeURIComponent(messageGuid)}/edit`, config.password);
287
+ const response = await fetch(url, {
288
+ method: "POST",
289
+ headers: { "Content-Type": "application/json" },
290
+ body: JSON.stringify({
291
+ editedMessage: text,
292
+ backwardsCompatibilityMessage: params.backwardsCompatibilityMessage ?? `Edited to: ${text}`,
293
+ partIndex: typeof params.partIndex === "number" ? params.partIndex : 0,
294
+ }),
295
+ signal: AbortSignal.timeout(editTimeoutMs),
296
+ });
297
+ if (!response.ok) {
298
+ const errorText = await response.text().catch(() => "");
299
+ throw new Error(`BlueBubbles edit failed (${response.status}): ${errorText || "unknown"}`);
300
+ }
301
+ },
302
+ async setTyping(chat, typing) {
303
+ const resolvedChatGuid = await resolveChatGuid(chat, config, channelConfig);
304
+ if (!resolvedChatGuid) {
305
+ return;
306
+ }
307
+ const url = buildBlueBubblesApiUrl(config.serverUrl, `/api/v1/chat/${encodeURIComponent(resolvedChatGuid)}/typing`, config.password);
308
+ const response = await fetch(url, {
309
+ method: typing ? "POST" : "DELETE",
310
+ signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
311
+ });
312
+ if (!response.ok) {
313
+ const errorText = await response.text().catch(() => "");
314
+ throw new Error(`BlueBubbles typing failed (${response.status}): ${errorText || "unknown"}`);
315
+ }
316
+ },
317
+ async markChatRead(chat) {
318
+ const resolvedChatGuid = await resolveChatGuid(chat, config, channelConfig);
319
+ if (!resolvedChatGuid) {
320
+ return;
321
+ }
322
+ const url = buildBlueBubblesApiUrl(config.serverUrl, `/api/v1/chat/${encodeURIComponent(resolvedChatGuid)}/read`, config.password);
323
+ const response = await fetch(url, {
324
+ method: "POST",
325
+ signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
326
+ });
327
+ if (!response.ok) {
328
+ const errorText = await response.text().catch(() => "");
329
+ throw new Error(`BlueBubbles read failed (${response.status}): ${errorText || "unknown"}`);
330
+ }
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
+ },
366
+ async repairEvent(event) {
367
+ if (!event.requiresRepair) {
368
+ (0, runtime_1.emitNervesEvent)({
369
+ component: "senses",
370
+ event: "senses.bluebubbles_repair_skipped",
371
+ message: "bluebubbles event repair skipped",
372
+ meta: {
373
+ kind: event.kind,
374
+ messageGuid: event.messageGuid,
375
+ },
376
+ });
377
+ return event;
378
+ }
379
+ (0, runtime_1.emitNervesEvent)({
380
+ component: "senses",
381
+ event: "senses.bluebubbles_repair_start",
382
+ message: "repairing bluebubbles event by guid",
383
+ meta: {
384
+ kind: event.kind,
385
+ messageGuid: event.messageGuid,
386
+ eventType: event.eventType,
387
+ },
388
+ });
389
+ const url = buildRepairUrl(config.serverUrl, event.messageGuid, config.password);
390
+ try {
391
+ const response = await fetch(url, {
392
+ method: "GET",
393
+ signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
394
+ });
395
+ if (!response.ok) {
396
+ const errorText = await response.text().catch(() => "");
397
+ const repaired = applyRepairNotice(event, `BlueBubbles repair failed: ${errorText || `HTTP ${response.status}`}`);
398
+ (0, runtime_1.emitNervesEvent)({
399
+ level: "warn",
400
+ component: "senses",
401
+ event: "senses.bluebubbles_repair_error",
402
+ message: "bluebubbles repair request failed",
403
+ meta: {
404
+ messageGuid: event.messageGuid,
405
+ status: response.status,
406
+ reason: errorText || "unknown",
407
+ },
408
+ });
409
+ return repaired;
410
+ }
411
+ const payload = await parseJsonBody(response);
412
+ const data = extractRepairData(payload);
413
+ if (!data || typeof data.guid !== "string") {
414
+ const repaired = applyRepairNotice(event, "BlueBubbles repair failed: invalid message payload");
415
+ (0, runtime_1.emitNervesEvent)({
416
+ level: "warn",
417
+ component: "senses",
418
+ event: "senses.bluebubbles_repair_error",
419
+ message: "bluebubbles repair returned unusable payload",
420
+ meta: {
421
+ messageGuid: event.messageGuid,
422
+ },
423
+ });
424
+ return repaired;
425
+ }
426
+ const normalized = (0, bluebubbles_model_1.normalizeBlueBubblesEvent)({
427
+ type: event.eventType,
428
+ data,
429
+ });
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);
440
+ if (hydrated.kind === "message" &&
441
+ hydrated.balloonBundleId !== "com.apple.messages.URLBalloonProvider" &&
442
+ hydrated.attachments.length > 0) {
443
+ const media = await (0, bluebubbles_media_1.hydrateBlueBubblesAttachments)(hydrated.attachments, config, channelConfig, {
444
+ preferAudioInput: providerSupportsAudioInput((0, identity_1.loadAgentConfig)().provider),
445
+ });
446
+ const transcriptSuffix = media.transcriptAdditions.map((entry) => `[${entry}]`).join("\n");
447
+ const noticeSuffix = media.notices.map((entry) => `[${entry}]`).join("\n");
448
+ const combinedSuffix = [transcriptSuffix, noticeSuffix].filter(Boolean).join("\n");
449
+ hydrated = {
450
+ ...hydrated,
451
+ inputPartsForAgent: media.inputParts.length > 0 ? media.inputParts : undefined,
452
+ textForAgent: [hydrated.textForAgent, combinedSuffix].filter(Boolean).join("\n"),
453
+ };
454
+ }
455
+ (0, runtime_1.emitNervesEvent)({
456
+ component: "senses",
457
+ event: "senses.bluebubbles_repair_end",
458
+ message: "bluebubbles event repaired",
459
+ meta: {
460
+ kind: hydrated.kind,
461
+ messageGuid: hydrated.messageGuid,
462
+ repairedFrom: event.kind,
463
+ promotedFromMutation: event.kind === "mutation" && hydrated.kind === "message",
464
+ },
465
+ });
466
+ return hydrated;
467
+ }
468
+ catch (error) {
469
+ const reason = error instanceof Error ? error.message : String(error);
470
+ (0, runtime_1.emitNervesEvent)({
471
+ level: "warn",
472
+ component: "senses",
473
+ event: "senses.bluebubbles_repair_error",
474
+ message: "bluebubbles repair threw",
475
+ meta: {
476
+ messageGuid: event.messageGuid,
477
+ reason,
478
+ },
479
+ });
480
+ return applyRepairNotice(event, `BlueBubbles repair failed: ${reason}`);
481
+ }
482
+ },
483
+ };
484
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ // Thin entrypoint for `npm run bluebubbles` / `node dist/senses/bluebubbles-entry.js --agent <name>`.
3
+ // Separated from bluebubbles.ts so the BlueBubbles adapter stays testable.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ if (!process.argv.includes("--agent")) {
6
+ // eslint-disable-next-line no-console -- pre-boot guard: --agent check before imports
7
+ console.error("Missing required --agent <name> argument.\nUsage: node dist/senses/bluebubbles-entry.js --agent ouroboros");
8
+ process.exit(1);
9
+ }
10
+ const bluebubbles_1 = require("./bluebubbles");
11
+ const runtime_logging_1 = require("../heart/daemon/runtime-logging");
12
+ (0, runtime_logging_1.configureDaemonRuntimeLogger)("bluebubbles");
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
+ }