@ouro.bot/cli 0.1.0-alpha.724 → 0.1.0-alpha.726
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.
- package/changelog.json +18 -0
- package/dist/heart/active-work.js +10 -3
- package/dist/heart/core.js +343 -77
- package/dist/heart/daemon/cli-exec.js +72 -14
- package/dist/heart/daemon/cli-help.js +13 -2
- package/dist/heart/daemon/cli-parse.js +82 -1
- package/dist/heart/daemon/daemon.js +286 -33
- package/dist/heart/daemon/doctor.js +21 -6
- package/dist/heart/daemon/habit-private-wake.js +6 -1
- package/dist/heart/habits/habit-cancel.js +810 -0
- package/dist/heart/habits/habit-lifecycle.js +1327 -0
- package/dist/heart/habits/habit-migration.js +28 -23
- package/dist/heart/habits/habit-parser.js +72 -8
- package/dist/heart/habits/habit-runtime-state.js +3 -24
- package/dist/heart/habits/habit-scheduler.js +268 -50
- package/dist/heart/habits/habit-session.js +16 -0
- package/dist/heart/hatch/hatch-flow.js +6 -4
- package/dist/heart/mailbox/readers/runtime-readers.js +26 -27
- package/dist/heart/orientation-frame.js +58 -1
- package/dist/heart/provider-attempt.js +4 -1
- package/dist/heart/providers/anthropic.js +23 -2
- package/dist/heart/start-of-turn-packet.js +14 -11
- package/dist/heart/streaming.js +565 -185
- package/dist/mind/obligation-steering.js +15 -9
- package/dist/mind/prompt.js +58 -26
- package/dist/nerves/coverage/run-artifacts.js +55 -35
- package/dist/nerves/review/cli.js +2 -2
- package/dist/nerves/review/core.js +14 -7
- package/dist/repertoire/tools-base.js +2 -0
- package/dist/repertoire/tools-habits.js +103 -0
- package/dist/repertoire/tools.js +18 -3
- package/dist/rsvp/cli.js +126 -20
- package/dist/rsvp/diagnostics.js +12 -2
- package/dist/rsvp/habit-policy.js +7 -0
- package/dist/rsvp/habit-stage.js +19 -4
- package/dist/rsvp/native-habit-runner.js +151 -11
- package/dist/rsvp/outbound-state.js +360 -0
- package/dist/senses/bluebubbles/client.js +90 -17
- package/dist/senses/bluebubbles/inbound-log.js +1 -0
- package/dist/senses/bluebubbles/index.js +662 -425
- package/dist/senses/bluebubbles/model.js +57 -13
- package/dist/senses/bluebubbles/mutation-log.js +21 -10
- package/dist/senses/bluebubbles/reaction-policy.js +55 -0
- package/dist/senses/bluebubbles/semantic-receipts.js +1818 -0
- package/dist/senses/cli/tui-store.js +1 -0
- package/dist/senses/cli.js +1 -0
- package/dist/senses/habit-lifecycle-guard.js +24 -0
- package/dist/senses/ingress-evidence.js +2 -0
- package/dist/senses/pipeline.js +90 -24
- package/dist/senses/private-runtime-worker.js +21 -20
- package/dist/senses/private-runtime.js +79 -29
- package/dist/senses/shared-turn.js +1 -0
- package/dist/senses/teams.js +1 -0
- package/package.json +6 -1
|
@@ -79,8 +79,10 @@ const client_1 = require("./client");
|
|
|
79
79
|
const inbound_log_1 = require("./inbound-log");
|
|
80
80
|
const mutation_log_1 = require("./mutation-log");
|
|
81
81
|
const processed_log_1 = require("./processed-log");
|
|
82
|
+
const semantic_receipts_1 = require("./semantic-receipts");
|
|
82
83
|
const runtime_state_1 = require("./runtime-state");
|
|
83
84
|
const session_cleanup_1 = require("./session-cleanup");
|
|
85
|
+
const reaction_policy_1 = require("./reaction-policy");
|
|
84
86
|
const active_turns_1 = require("./active-turns");
|
|
85
87
|
const trust_gate_1 = require("../trust-gate");
|
|
86
88
|
const pipeline_1 = require("../pipeline");
|
|
@@ -358,8 +360,8 @@ function resolveFriendParams(event) {
|
|
|
358
360
|
}
|
|
359
361
|
return {
|
|
360
362
|
provider: "imessage-handle",
|
|
361
|
-
externalId: event.sender.externalId
|
|
362
|
-
displayName: event.sender.displayName
|
|
363
|
+
externalId: event.sender.externalId,
|
|
364
|
+
displayName: event.sender.displayName,
|
|
363
365
|
channel: "bluebubbles",
|
|
364
366
|
};
|
|
365
367
|
}
|
|
@@ -463,11 +465,49 @@ function extractHistoricalLaneSummary(messages) {
|
|
|
463
465
|
}
|
|
464
466
|
return summaries;
|
|
465
467
|
}
|
|
466
|
-
function buildBlueBubblesOrientationSource(event, existingMessages, repliedToText) {
|
|
468
|
+
function buildBlueBubblesOrientationSource(event, existingMessages, evidence, conversationKind, repliedToText) {
|
|
467
469
|
const repairNotice = event.repairNotice?.trim();
|
|
470
|
+
const actor = evidence.actor;
|
|
471
|
+
const participants = evidence.participants
|
|
472
|
+
.filter((participant) => !(participant.provider === actor.provider
|
|
473
|
+
&& participant.externalId === actor.externalId))
|
|
474
|
+
.map((participant) => ({
|
|
475
|
+
role: "group_participant_only",
|
|
476
|
+
provider: participant.provider,
|
|
477
|
+
externalId: participant.externalId,
|
|
478
|
+
displayName: participant.displayName,
|
|
479
|
+
}));
|
|
480
|
+
const targetGuid = evidence.targetGuid;
|
|
481
|
+
const targetAuthorship = evidence.targetAuthorship;
|
|
482
|
+
const presentation = {
|
|
483
|
+
authority: "presentation_only",
|
|
484
|
+
conversationKind,
|
|
485
|
+
event: {
|
|
486
|
+
provider: evidence.provider,
|
|
487
|
+
kind: evidence.kind,
|
|
488
|
+
sourceEventType: evidence.sourceEventType,
|
|
489
|
+
fromMe: evidence.fromMe,
|
|
490
|
+
},
|
|
491
|
+
actor: {
|
|
492
|
+
role: "observed_actor",
|
|
493
|
+
provider: actor.provider,
|
|
494
|
+
externalId: actor.externalId,
|
|
495
|
+
displayName: actor.displayName,
|
|
496
|
+
},
|
|
497
|
+
participants,
|
|
498
|
+
...(targetGuid
|
|
499
|
+
? {
|
|
500
|
+
target: {
|
|
501
|
+
messageGuid: targetGuid,
|
|
502
|
+
authorship: targetAuthorship ?? "unknown",
|
|
503
|
+
},
|
|
504
|
+
}
|
|
505
|
+
: {}),
|
|
506
|
+
};
|
|
468
507
|
if (event.kind !== "message") {
|
|
469
508
|
return {
|
|
470
509
|
kind: "bluebubbles",
|
|
510
|
+
...presentation,
|
|
471
511
|
lane: "mutation",
|
|
472
512
|
...(repairNotice ? { repairNotice } : {}),
|
|
473
513
|
};
|
|
@@ -477,6 +517,7 @@ function buildBlueBubblesOrientationSource(event, existingMessages, repliedToTex
|
|
|
477
517
|
const hasThreadRoute = !!threadId || summaries.some((summary) => summary.key.startsWith("thread:"));
|
|
478
518
|
return {
|
|
479
519
|
kind: "bluebubbles",
|
|
520
|
+
...presentation,
|
|
480
521
|
lane: threadId ? "thread" : "top_level",
|
|
481
522
|
defaultReplyTarget: threadId ? "current_lane" : "top_level",
|
|
482
523
|
...(threadId ? { threadId } : {}),
|
|
@@ -518,37 +559,6 @@ function sessionLikelyContainsMessage(event, existingMessages) {
|
|
|
518
559
|
return extractMessageText(message.content).includes(fragment);
|
|
519
560
|
});
|
|
520
561
|
}
|
|
521
|
-
function mutationEntryToEvent(entry) {
|
|
522
|
-
return {
|
|
523
|
-
kind: "mutation",
|
|
524
|
-
eventType: entry.eventType,
|
|
525
|
-
mutationType: entry.mutationType,
|
|
526
|
-
messageGuid: entry.messageGuid,
|
|
527
|
-
targetMessageGuid: entry.targetMessageGuid ?? undefined,
|
|
528
|
-
timestamp: Date.parse(entry.recordedAt) || Date.now(),
|
|
529
|
-
fromMe: entry.fromMe,
|
|
530
|
-
sender: {
|
|
531
|
-
provider: "imessage-handle",
|
|
532
|
-
externalId: entry.chatIdentifier ?? entry.chatGuid ?? "unknown",
|
|
533
|
-
rawId: entry.chatIdentifier ?? entry.chatGuid ?? "unknown",
|
|
534
|
-
displayName: entry.chatIdentifier ?? entry.chatGuid ?? "Unknown",
|
|
535
|
-
},
|
|
536
|
-
chat: {
|
|
537
|
-
chatGuid: entry.chatGuid ?? undefined,
|
|
538
|
-
chatIdentifier: entry.chatIdentifier ?? undefined,
|
|
539
|
-
displayName: undefined,
|
|
540
|
-
isGroup: Boolean(entry.chatGuid?.includes(";+;")),
|
|
541
|
-
sessionKey: entry.sessionKey,
|
|
542
|
-
sendTarget: entry.chatGuid
|
|
543
|
-
? { kind: "chat_guid", value: entry.chatGuid }
|
|
544
|
-
: { kind: "chat_identifier", value: entry.chatIdentifier ?? "unknown" },
|
|
545
|
-
participantHandles: [],
|
|
546
|
-
},
|
|
547
|
-
shouldNotifyAgent: entry.shouldNotifyAgent,
|
|
548
|
-
textForAgent: entry.textForAgent,
|
|
549
|
-
requiresRepair: true,
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
562
|
function getBlueBubblesContinuityIngressTexts(event) {
|
|
553
563
|
if (event.kind !== "message")
|
|
554
564
|
return [];
|
|
@@ -603,7 +613,10 @@ function shouldUseBlueBubblesRecoveryBudget(source, options) {
|
|
|
603
613
|
|| source === "recovery-bootstrap";
|
|
604
614
|
}
|
|
605
615
|
function shouldCountBlueBubblesRecoveryResultAsSkipped(reason) {
|
|
606
|
-
return reason === "already_processed"
|
|
616
|
+
return reason === "already_processed"
|
|
617
|
+
|| reason === "autonomy_budget_blocked"
|
|
618
|
+
|| reason === "mutation_state_only"
|
|
619
|
+
|| reason === "from_me";
|
|
607
620
|
}
|
|
608
621
|
function recordBlueBubblesRecoveryFailureForBudget(agentName, event, source) {
|
|
609
622
|
try {
|
|
@@ -731,7 +744,7 @@ function emitBlueBubblesMarkReadWarning(chat, error) {
|
|
|
731
744
|
},
|
|
732
745
|
});
|
|
733
746
|
}
|
|
734
|
-
function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat, onVisibleActivity) {
|
|
747
|
+
function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat, onVisibleActivity, options = {}) {
|
|
735
748
|
let textBuffer = "";
|
|
736
749
|
let typingActive = false;
|
|
737
750
|
let queue = Promise.resolve();
|
|
@@ -885,11 +898,13 @@ function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat, onVi
|
|
|
885
898
|
});
|
|
886
899
|
}
|
|
887
900
|
return {
|
|
901
|
+
settleOutputMode: "retractable_buffer",
|
|
888
902
|
onModelStart() {
|
|
889
903
|
if (outboundClosed)
|
|
890
904
|
return;
|
|
891
|
-
|
|
892
|
-
|
|
905
|
+
if (options.enableSilenceWatchdog !== false)
|
|
906
|
+
startSilenceWatchdog();
|
|
907
|
+
if (options.enableActivitySignals !== false && !isGroupChat)
|
|
893
908
|
startTypingNow();
|
|
894
909
|
(0, runtime_1.emitNervesEvent)({
|
|
895
910
|
component: "senses",
|
|
@@ -909,7 +924,7 @@ function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat, onVi
|
|
|
909
924
|
onTextChunk(text) {
|
|
910
925
|
if (outboundClosed)
|
|
911
926
|
return;
|
|
912
|
-
if (isGroupChat && !typingActive)
|
|
927
|
+
if (options.enableActivitySignals !== false && isGroupChat && !typingActive)
|
|
913
928
|
startTypingNow();
|
|
914
929
|
textBuffer += text;
|
|
915
930
|
},
|
|
@@ -921,7 +936,7 @@ function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat, onVi
|
|
|
921
936
|
// console. Keep the human-facing thread quiet until the agent has real
|
|
922
937
|
// text (or the generic silence watchdog fires), while preserving native
|
|
923
938
|
// read/typing signals and nerves telemetry for debugging.
|
|
924
|
-
if (!typingActive && name !== "observe" && name !== "speak")
|
|
939
|
+
if (options.enableActivitySignals !== false && !typingActive && name !== "observe" && name !== "speak")
|
|
925
940
|
startTypingNow();
|
|
926
941
|
(0, runtime_1.emitNervesEvent)({
|
|
927
942
|
component: "senses",
|
|
@@ -993,7 +1008,7 @@ function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat, onVi
|
|
|
993
1008
|
meta: { messageLength: trimmed.length },
|
|
994
1009
|
});
|
|
995
1010
|
},
|
|
996
|
-
async flush() {
|
|
1011
|
+
async flush(flushOptions = {}) {
|
|
997
1012
|
if (outboundClosed) {
|
|
998
1013
|
textBuffer = "";
|
|
999
1014
|
await waitForBlueBubblesCallbackCleanup(queue, BLUEBUBBLES_CALLBACK_CLEANUP_TIMEOUT_MS, {
|
|
@@ -1038,6 +1053,7 @@ function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat, onVi
|
|
|
1038
1053
|
chat,
|
|
1039
1054
|
text: trimmed,
|
|
1040
1055
|
replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
|
|
1056
|
+
...(flushOptions.signal ? { signal: flushOptions.signal } : {}),
|
|
1041
1057
|
});
|
|
1042
1058
|
recordVisibleActivity();
|
|
1043
1059
|
},
|
|
@@ -1078,6 +1094,11 @@ function writeJson(res, statusCode, payload) {
|
|
|
1078
1094
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
1079
1095
|
res.end(JSON.stringify(payload));
|
|
1080
1096
|
}
|
|
1097
|
+
function writeSemanticCaptureFailure(res) {
|
|
1098
|
+
res.statusCode = 503;
|
|
1099
|
+
res.setHeader("Content-Type", "application/json");
|
|
1100
|
+
res.end('{"ok":false,"error":"semantic_capture_failed"}');
|
|
1101
|
+
}
|
|
1081
1102
|
function isWebhookPasswordValid(url, expectedPassword) {
|
|
1082
1103
|
const provided = url.searchParams.get("password");
|
|
1083
1104
|
return !provided || provided === expectedPassword;
|
|
@@ -1183,7 +1204,10 @@ async function shouldFilterAgentSelfHandle(event, resolvedDeps) {
|
|
|
1183
1204
|
}
|
|
1184
1205
|
return true;
|
|
1185
1206
|
}
|
|
1186
|
-
|
|
1207
|
+
function hasObservedBlueBubblesDirection(event) {
|
|
1208
|
+
return typeof event.fromMe === "boolean";
|
|
1209
|
+
}
|
|
1210
|
+
async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, options) {
|
|
1187
1211
|
const client = resolvedDeps.createClient();
|
|
1188
1212
|
const agentName = resolvedDeps.getAgentName();
|
|
1189
1213
|
(0, runtime_cwd_1.recoverRuntimeCwd)();
|
|
@@ -1222,24 +1246,6 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1222
1246
|
});
|
|
1223
1247
|
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "from_me" };
|
|
1224
1248
|
}
|
|
1225
|
-
if (event.kind === "mutation") {
|
|
1226
|
-
try {
|
|
1227
|
-
resolvedDeps.recordMutation(resolvedDeps.getAgentName(), event);
|
|
1228
|
-
}
|
|
1229
|
-
catch (error) {
|
|
1230
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1231
|
-
level: "error",
|
|
1232
|
-
component: "senses",
|
|
1233
|
-
event: "senses.bluebubbles_mutation_log_error",
|
|
1234
|
-
message: "failed recording bluebubbles mutation sidecar",
|
|
1235
|
-
meta: {
|
|
1236
|
-
messageGuid: event.messageGuid,
|
|
1237
|
-
mutationType: event.mutationType,
|
|
1238
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
1239
|
-
},
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
1249
|
if (event.kind === "mutation" && !event.shouldNotifyAgent) {
|
|
1244
1250
|
(0, runtime_1.emitNervesEvent)({
|
|
1245
1251
|
component: "senses",
|
|
@@ -1252,52 +1258,7 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1252
1258
|
});
|
|
1253
1259
|
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "mutation_state_only" };
|
|
1254
1260
|
}
|
|
1255
|
-
let ownsInFlightMessage = false;
|
|
1256
1261
|
let activeTurnId = null;
|
|
1257
|
-
const inFlightKey = event.kind === "message"
|
|
1258
|
-
? {
|
|
1259
|
-
sessionKey: options.preclaimedInFlight?.sessionKey ?? event.chat.sessionKey,
|
|
1260
|
-
messageGuid: options.preclaimedInFlight?.messageGuid ?? event.messageGuid,
|
|
1261
|
-
}
|
|
1262
|
-
: null;
|
|
1263
|
-
if (event.kind === "message") {
|
|
1264
|
-
if ((0, processed_log_1.hasProcessedBlueBubblesMessage)(agentName, event.chat.sessionKey, event.messageGuid)
|
|
1265
|
-
|| (0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, event.messageGuid)) {
|
|
1266
|
-
if (inFlightKey && options.preclaimedInFlight) {
|
|
1267
|
-
endBlueBubblesMessageInFlight(inFlightKey.sessionKey, inFlightKey.messageGuid);
|
|
1268
|
-
}
|
|
1269
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1270
|
-
component: "senses",
|
|
1271
|
-
event: "senses.bluebubbles_recovery_skip",
|
|
1272
|
-
message: "skipped bluebubbles message already marked as handled",
|
|
1273
|
-
meta: {
|
|
1274
|
-
messageGuid: event.messageGuid,
|
|
1275
|
-
sessionKey: event.chat.sessionKey,
|
|
1276
|
-
source,
|
|
1277
|
-
dedupeReason: "processed",
|
|
1278
|
-
},
|
|
1279
|
-
});
|
|
1280
|
-
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "already_processed" };
|
|
1281
|
-
}
|
|
1282
|
-
if (options.preclaimedInFlight) {
|
|
1283
|
-
ownsInFlightMessage = true;
|
|
1284
|
-
}
|
|
1285
|
-
else if (!beginBlueBubblesMessageInFlight(inFlightKey.sessionKey, inFlightKey.messageGuid)) {
|
|
1286
|
-
(0, runtime_1.emitNervesEvent)({
|
|
1287
|
-
component: "senses",
|
|
1288
|
-
event: "senses.bluebubbles_recovery_skip",
|
|
1289
|
-
message: "skipped bluebubbles message already in flight",
|
|
1290
|
-
meta: {
|
|
1291
|
-
messageGuid: event.messageGuid,
|
|
1292
|
-
sessionKey: event.chat.sessionKey,
|
|
1293
|
-
source,
|
|
1294
|
-
dedupeReason: "in_flight",
|
|
1295
|
-
},
|
|
1296
|
-
});
|
|
1297
|
-
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "already_processed" };
|
|
1298
|
-
}
|
|
1299
|
-
ownsInFlightMessage = true;
|
|
1300
|
-
}
|
|
1301
1262
|
if (event.kind === "message" && shouldUseBlueBubblesRecoveryBudget(source, options)) {
|
|
1302
1263
|
const decision = (0, autonomy_budget_1.reserveAutonomyBudget)((0, identity_1.getAgentRoot)(agentName), {
|
|
1303
1264
|
agent: agentName,
|
|
@@ -1310,8 +1271,6 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1310
1271
|
storm: blueBubblesRecoveryStormInput(agentName, event, source),
|
|
1311
1272
|
});
|
|
1312
1273
|
if (!decision.allowed) {
|
|
1313
|
-
endBlueBubblesMessageInFlight(inFlightKey.sessionKey, inFlightKey.messageGuid);
|
|
1314
|
-
ownsInFlightMessage = false;
|
|
1315
1274
|
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "autonomy_budget_blocked" };
|
|
1316
1275
|
}
|
|
1317
1276
|
}
|
|
@@ -1352,7 +1311,8 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1352
1311
|
if (event.kind === "message") {
|
|
1353
1312
|
// Record EARLY for audit and crash recovery. This is capture truth, not
|
|
1354
1313
|
// a claim that the agent turn completed successfully.
|
|
1355
|
-
const inboundSource = source !== "webhook"
|
|
1314
|
+
const inboundSource = (options.semanticRecovery || source !== "webhook")
|
|
1315
|
+
&& sessionLikelyContainsMessage(event, existing?.messages ?? sessionMessages)
|
|
1356
1316
|
? "recovery-bootstrap"
|
|
1357
1317
|
: source;
|
|
1358
1318
|
(0, inbound_log_1.recordBlueBubblesInbound)(agentName, event, inboundSource);
|
|
@@ -1393,16 +1353,6 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1393
1353
|
meta: { threadGuid, hasText: !!repliedToText },
|
|
1394
1354
|
});
|
|
1395
1355
|
}
|
|
1396
|
-
// Anchor reaction mutations to the message they point at, including whose
|
|
1397
|
-
// message it was. Unresolved targets keep the explicit "unidentified message"
|
|
1398
|
-
// wording that normalizeBlueBubblesEvent already produced.
|
|
1399
|
-
const isReaction = event.kind === "mutation" && event.mutationType === "reaction";
|
|
1400
|
-
if (isReaction && event.reaction && event.targetMessageGuid) {
|
|
1401
|
-
/* v8 ignore start -- best-effort network lookup; resolveReactionTarget and renderBlueBubblesReactionText are covered by unit tests @preserve */
|
|
1402
|
-
const target = await resolveReactionTarget(client, event.targetMessageGuid);
|
|
1403
|
-
event.textForAgent = (0, model_1.renderBlueBubblesReactionText)(event.reaction, target);
|
|
1404
|
-
/* v8 ignore stop */
|
|
1405
|
-
}
|
|
1406
1356
|
// Build inbound user message as user-visible speech only; source/routing facts live in the orientation frame.
|
|
1407
1357
|
const priorMessages = existing?.messages ?? sessionMessages;
|
|
1408
1358
|
const userMessage = {
|
|
@@ -1414,8 +1364,7 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1414
1364
|
messages: [...priorMessages, userMessage],
|
|
1415
1365
|
currentUserMessages: [userMessage],
|
|
1416
1366
|
structuredOutputs: existing?.structuredOutputs ?? [],
|
|
1417
|
-
source: buildBlueBubblesOrientationSource(event, priorMessages, repliedToText),
|
|
1418
|
-
...(isReaction ? { speechKind: "reaction" } : {}),
|
|
1367
|
+
source: buildBlueBubblesOrientationSource(event, priorMessages, options.orientationEvidence, options.orientationConversationKind, repliedToText),
|
|
1419
1368
|
});
|
|
1420
1369
|
const visibleActivityTurnId = activeTurnId;
|
|
1421
1370
|
const callbacks = createBlueBubblesCallbacks(client, event.chat, replyTarget, event.chat.isGroup, visibleActivityTurnId
|
|
@@ -1463,10 +1412,6 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1463
1412
|
timeoutReason = reason;
|
|
1464
1413
|
recoveryTimedOut = true;
|
|
1465
1414
|
callbacks.cancelOutbound("turn_timeout");
|
|
1466
|
-
if (ownsInFlightMessage && inFlightKey) {
|
|
1467
|
-
endBlueBubblesMessageInFlight(inFlightKey.sessionKey, inFlightKey.messageGuid);
|
|
1468
|
-
ownsInFlightMessage = false;
|
|
1469
|
-
}
|
|
1470
1415
|
controller.abort(reason);
|
|
1471
1416
|
timeoutReject?.(reason);
|
|
1472
1417
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -1509,7 +1454,7 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1509
1454
|
pendingDir,
|
|
1510
1455
|
friendStore: store,
|
|
1511
1456
|
provider: "imessage-handle",
|
|
1512
|
-
externalId: event.sender.externalId
|
|
1457
|
+
externalId: event.sender.externalId,
|
|
1513
1458
|
isGroupChat: event.chat.isGroup,
|
|
1514
1459
|
groupHasFamilyMember,
|
|
1515
1460
|
hasExistingGroupWithFamily,
|
|
@@ -1560,7 +1505,15 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1560
1505
|
},
|
|
1561
1506
|
accumulateFriendTokens: resolvedDeps.accumulateFriendTokens,
|
|
1562
1507
|
signal: controller.signal,
|
|
1563
|
-
runAgentOptions: {
|
|
1508
|
+
runAgentOptions: {
|
|
1509
|
+
mcpManager,
|
|
1510
|
+
orientationFrame,
|
|
1511
|
+
toolContext: {
|
|
1512
|
+
signin: async () => undefined,
|
|
1513
|
+
agentRoot: (0, identity_1.getAgentRoot)(),
|
|
1514
|
+
currentIngressEvidence: options.currentIngressEvidence,
|
|
1515
|
+
},
|
|
1516
|
+
},
|
|
1564
1517
|
callbacks: failoverAwareCallbacks,
|
|
1565
1518
|
failoverState: (() => {
|
|
1566
1519
|
if (!bbFailoverStates.has(event.chat.sessionKey)) {
|
|
@@ -1705,204 +1658,531 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1705
1658
|
finally {
|
|
1706
1659
|
if (activeTurnId)
|
|
1707
1660
|
(0, active_turns_1.finishBlueBubblesActiveTurn)(agentName, activeTurnId);
|
|
1708
|
-
if (ownsInFlightMessage && inFlightKey) {
|
|
1709
|
-
endBlueBubblesMessageInFlight(inFlightKey.sessionKey, inFlightKey.messageGuid);
|
|
1710
|
-
}
|
|
1711
1661
|
}
|
|
1712
1662
|
}
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1663
|
+
function recordBlueBubblesAuditSidecar(agentName, event, source, resolvedDeps) {
|
|
1664
|
+
if (event.kind === "message") {
|
|
1665
|
+
if (event.fromMe !== true)
|
|
1666
|
+
(0, inbound_log_1.recordBlueBubblesInbound)(agentName, event, source);
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1717
1669
|
try {
|
|
1718
|
-
|
|
1670
|
+
resolvedDeps.recordMutation(agentName, event);
|
|
1719
1671
|
}
|
|
1720
1672
|
catch (error) {
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
notifiedAgent: false,
|
|
1733
|
-
reason: "ignored",
|
|
1734
|
-
};
|
|
1735
|
-
}
|
|
1736
|
-
throw error;
|
|
1673
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1674
|
+
level: "error",
|
|
1675
|
+
component: "senses",
|
|
1676
|
+
event: "senses.bluebubbles_mutation_log_error",
|
|
1677
|
+
message: "failed recording bluebubbles mutation sidecar",
|
|
1678
|
+
meta: {
|
|
1679
|
+
messageGuid: event.messageGuid,
|
|
1680
|
+
mutationType: event.mutationType,
|
|
1681
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
1682
|
+
},
|
|
1683
|
+
});
|
|
1737
1684
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
// can promote an updated-message back to a message if it has recoverable
|
|
1744
|
-
// content, which then re-runs the full VLM-describe pipeline on the same
|
|
1745
|
-
// attachment.
|
|
1746
|
-
//
|
|
1747
|
-
// Without this early check, we paid DOUBLE latency and double tokens on
|
|
1748
|
-
// every image-bearing message. Verified live on 2026-04-08T00:58Z: two
|
|
1749
|
-
// sequential VLM describes for attachment guid 317E37EB-..., 13.7s +
|
|
1750
|
-
// 14.0s each, for the exact same 291KB JPEG — triggered by a sequence of
|
|
1751
|
-
// `new-message` followed ~3s later by `updated-message` for the same guid.
|
|
1752
|
-
//
|
|
1753
|
-
// We still route the skip through `handleBlueBubblesNormalizedEvent` so
|
|
1754
|
-
// the downstream `already_processed` path fires its observability events
|
|
1755
|
-
// and the caller sees a consistent return shape.
|
|
1685
|
+
}
|
|
1686
|
+
function reactionTargetAuthorship(target) {
|
|
1687
|
+
return target.fromMe === true ? "agent" : "non_agent_unknown";
|
|
1688
|
+
}
|
|
1689
|
+
async function captureBlueBubblesSemanticEvent(normalized, resolvedDeps, source) {
|
|
1756
1690
|
const agentName = resolvedDeps.getAgentName();
|
|
1757
|
-
|
|
1758
|
-
// only needs to discriminate between known processed, in-flight, or new.
|
|
1759
|
-
const duplicateReason = (0, processed_log_1.hasProcessedBlueBubblesMessage)(agentName, normalized.chat.sessionKey, normalized.messageGuid)
|
|
1760
|
-
|| (0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, normalized.messageGuid)
|
|
1761
|
-
? "processed"
|
|
1762
|
-
: isBlueBubblesMessageInFlight(normalized.chat.sessionKey, normalized.messageGuid)
|
|
1763
|
-
? "in_flight"
|
|
1764
|
-
: null;
|
|
1765
|
-
if (normalized.messageGuid
|
|
1766
|
-
&& duplicateReason) {
|
|
1691
|
+
if (!hasObservedBlueBubblesDirection(normalized)) {
|
|
1767
1692
|
(0, runtime_1.emitNervesEvent)({
|
|
1768
1693
|
level: "warn",
|
|
1769
1694
|
component: "senses",
|
|
1770
|
-
event: "senses.
|
|
1771
|
-
message: "
|
|
1695
|
+
event: "senses.bluebubbles_direction_unobserved",
|
|
1696
|
+
message: "kept bluebubbles event audit-only because message direction was unobserved",
|
|
1772
1697
|
meta: {
|
|
1773
1698
|
messageGuid: normalized.messageGuid,
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
normalizedKind: normalized.kind,
|
|
1777
|
-
dedupeReason: duplicateReason,
|
|
1699
|
+
kind: normalized.kind,
|
|
1700
|
+
source,
|
|
1778
1701
|
},
|
|
1779
1702
|
});
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1703
|
+
recordBlueBubblesAuditSidecar(agentName, normalized, source, resolvedDeps);
|
|
1704
|
+
return { status: "audit_only", normalized };
|
|
1705
|
+
}
|
|
1706
|
+
const observed = normalized;
|
|
1707
|
+
const cutover = (0, semantic_receipts_1.initializeBlueBubblesSemanticCutover)(agentName);
|
|
1708
|
+
let resolvedReactionTarget;
|
|
1709
|
+
let targetAuthorship = null;
|
|
1710
|
+
if (observed.kind === "mutation"
|
|
1711
|
+
&& observed.mutationType === "reaction"
|
|
1712
|
+
&& observed.targetMessageGuid) {
|
|
1713
|
+
resolvedReactionTarget = await resolveReactionTarget(resolvedDeps.createClient(), observed.targetMessageGuid);
|
|
1714
|
+
targetAuthorship = reactionTargetAuthorship(resolvedReactionTarget);
|
|
1715
|
+
}
|
|
1716
|
+
let coordinateGeneration;
|
|
1717
|
+
if (observed.kind === "mutation"
|
|
1718
|
+
&& observed.mutationType === "reaction"
|
|
1719
|
+
&& observed.reaction
|
|
1720
|
+
&& !observed.revision
|
|
1721
|
+
&& !Number.isFinite(observed.effectiveTimestamp)
|
|
1722
|
+
&& observed.sender.observed === true) {
|
|
1723
|
+
const identity = (0, semantic_receipts_1.buildBlueBubblesSemanticIdentity)({
|
|
1724
|
+
providerNamespace: cutover.providerNamespace,
|
|
1725
|
+
kind: "reaction",
|
|
1726
|
+
eventGuid: observed.messageGuid,
|
|
1727
|
+
targetGuid: observed.targetMessageGuid,
|
|
1728
|
+
actorExternalId: observed.sender.externalId,
|
|
1729
|
+
canonicalValue: observed.reaction.canonicalValue,
|
|
1730
|
+
canonicalAction: observed.reaction.action,
|
|
1731
|
+
coordinateGeneration: 0,
|
|
1732
|
+
});
|
|
1733
|
+
if (identity?.coordinateKey && identity.coordinateHash) {
|
|
1734
|
+
const coordinate = await (0, semantic_receipts_1.allocateBlueBubblesReactionCoordinate)(agentName, {
|
|
1735
|
+
coordinateKey: identity.coordinateKey,
|
|
1736
|
+
coordinateHash: identity.coordinateHash,
|
|
1737
|
+
canonicalAction: observed.reaction.action,
|
|
1738
|
+
});
|
|
1739
|
+
coordinateGeneration = coordinate.generation;
|
|
1798
1740
|
}
|
|
1799
1741
|
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
return false;
|
|
1807
|
-
seenMessageGuids.add(entry.messageGuid);
|
|
1808
|
-
return true;
|
|
1809
|
-
})
|
|
1810
|
-
.filter((entry) => {
|
|
1811
|
-
if ((0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, entry.messageGuid))
|
|
1812
|
-
return false;
|
|
1813
|
-
return !markCapturedInboundSuperseded(agentName, entry);
|
|
1742
|
+
const capture = (0, semantic_receipts_1.buildBlueBubblesSemanticCapture)({
|
|
1743
|
+
cutover,
|
|
1744
|
+
capturedAt: new Date().toISOString(),
|
|
1745
|
+
event: observed,
|
|
1746
|
+
targetAuthorship,
|
|
1747
|
+
coordinateGeneration,
|
|
1814
1748
|
});
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
if ((0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, messageGuid))
|
|
1820
|
-
return;
|
|
1821
|
-
const previous = pendingByGuid.get(messageGuid);
|
|
1822
|
-
if (!previous) {
|
|
1823
|
-
pendingByGuid.set(messageGuid, recordedAt);
|
|
1824
|
-
return;
|
|
1825
|
-
}
|
|
1826
|
-
const previousMs = Date.parse(previous);
|
|
1827
|
-
const nextMs = Date.parse(recordedAt);
|
|
1828
|
-
if (Number.isFinite(nextMs) && (!Number.isFinite(previousMs) || nextMs < previousMs)) {
|
|
1829
|
-
pendingByGuid.set(messageGuid, recordedAt);
|
|
1830
|
-
}
|
|
1831
|
-
};
|
|
1832
|
-
for (const entry of listPendingCapturedInboundMessages(agentName)) {
|
|
1833
|
-
add(entry.messageGuid, entry.recordedAt);
|
|
1749
|
+
if (!capture) {
|
|
1750
|
+
(0, semantic_receipts_1.classifyBlueBubblesRecoveryRecord)(observed, cutover);
|
|
1751
|
+
recordBlueBubblesAuditSidecar(agentName, observed, source, resolvedDeps);
|
|
1752
|
+
return { status: "audit_only", normalized: observed };
|
|
1834
1753
|
}
|
|
1835
|
-
|
|
1836
|
-
|
|
1754
|
+
const writeResult = (0, semantic_receipts_1.writeBlueBubblesSemanticCapture)(agentName, capture);
|
|
1755
|
+
if (writeResult === "semantic_identity_collision") {
|
|
1756
|
+
throw new Error("semantic_capture_failed");
|
|
1837
1757
|
}
|
|
1838
|
-
|
|
1758
|
+
recordBlueBubblesAuditSidecar(agentName, observed, source, resolvedDeps);
|
|
1759
|
+
return {
|
|
1760
|
+
status: "captured",
|
|
1761
|
+
capture,
|
|
1762
|
+
normalized: observed,
|
|
1763
|
+
writeResult,
|
|
1764
|
+
...(resolvedReactionTarget ? { resolvedReactionTarget } : {}),
|
|
1765
|
+
};
|
|
1839
1766
|
}
|
|
1840
|
-
function
|
|
1841
|
-
|
|
1767
|
+
function semanticCaptureChat(capture) {
|
|
1768
|
+
const event = capture.event;
|
|
1769
|
+
const sessionKey = event.sessionKey?.trim();
|
|
1770
|
+
const chatGuid = event.chatGuid?.trim() || undefined;
|
|
1771
|
+
const chatIdentifier = event.chatIdentifier?.trim() || undefined;
|
|
1772
|
+
if (!sessionKey)
|
|
1842
1773
|
return null;
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1774
|
+
const fallbackSendTarget = sessionKey.startsWith("chat:")
|
|
1775
|
+
? { kind: "chat_guid", value: sessionKey.slice("chat:".length).trim() }
|
|
1776
|
+
: sessionKey.startsWith("chat_identifier:")
|
|
1777
|
+
? { kind: "chat_identifier", value: sessionKey.slice("chat_identifier:".length).trim() }
|
|
1778
|
+
: null;
|
|
1779
|
+
const sendTarget = chatGuid
|
|
1780
|
+
? { kind: "chat_guid", value: chatGuid }
|
|
1781
|
+
: chatIdentifier
|
|
1782
|
+
? { kind: "chat_identifier", value: chatIdentifier }
|
|
1783
|
+
: fallbackSendTarget;
|
|
1784
|
+
if (!sendTarget?.value)
|
|
1785
|
+
return null;
|
|
1786
|
+
return {
|
|
1787
|
+
chatGuid,
|
|
1788
|
+
chatIdentifier,
|
|
1789
|
+
isGroup: Boolean(chatGuid?.includes(";+;") || event.participants.length > 1),
|
|
1790
|
+
sessionKey,
|
|
1791
|
+
sendTarget,
|
|
1792
|
+
participantHandles: event.participants.map((participant) => participant.externalId),
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
function semanticCaptureRequiresRoutingRepair(capture) {
|
|
1796
|
+
return !capture.event.chatGuid?.trim() && !capture.event.chatIdentifier?.trim();
|
|
1797
|
+
}
|
|
1798
|
+
function hasResolvedBlueBubblesRouting(event) {
|
|
1799
|
+
return Boolean(event.chat.chatGuid?.trim() || event.chat.chatIdentifier?.trim());
|
|
1800
|
+
}
|
|
1801
|
+
function isAuditOnlySemanticCapture(capture) {
|
|
1802
|
+
return capture.event.kind === "read" || capture.event.kind === "delivery";
|
|
1803
|
+
}
|
|
1804
|
+
function semanticCaptureToNormalizedEvent(capture) {
|
|
1805
|
+
const event = capture.event;
|
|
1806
|
+
const messageGuid = event.eventGuid?.trim();
|
|
1807
|
+
const chat = semanticCaptureChat(capture);
|
|
1808
|
+
if (!messageGuid || !chat)
|
|
1809
|
+
return null;
|
|
1810
|
+
const sender = {
|
|
1811
|
+
provider: "imessage-handle",
|
|
1812
|
+
externalId: event.actor.externalId,
|
|
1813
|
+
rawId: event.actor.externalId,
|
|
1814
|
+
displayName: event.actor.displayName ?? event.actor.externalId,
|
|
1815
|
+
observed: true,
|
|
1816
|
+
};
|
|
1817
|
+
const timestamp = Date.parse(event.effectiveAt ?? capture.capturedAt);
|
|
1818
|
+
if (event.kind === "message") {
|
|
1819
|
+
const text = event.text ?? "";
|
|
1820
|
+
return {
|
|
1821
|
+
kind: "message",
|
|
1822
|
+
eventType: event.sourceEventType,
|
|
1823
|
+
messageGuid,
|
|
1824
|
+
timestamp,
|
|
1825
|
+
fromMe: event.fromMe,
|
|
1826
|
+
sender,
|
|
1827
|
+
chat,
|
|
1828
|
+
text,
|
|
1829
|
+
textForAgent: text,
|
|
1830
|
+
attachments: [],
|
|
1831
|
+
hasPayloadData: false,
|
|
1832
|
+
requiresRepair: true,
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
const shouldNotifyAgent = event.kind === "reaction" || event.kind === "edit" || event.kind === "unsend";
|
|
1836
|
+
const targetMessageGuid = event.targetGuid ?? undefined;
|
|
1837
|
+
const reaction = event.kind === "reaction" && event.canonicalAction && event.canonicalValue
|
|
1838
|
+
? {
|
|
1839
|
+
...(0, model_1.describeBlueBubblesReaction)(event.rawTransportValue ?? event.canonicalValue, event.rawTransportValue ?? event.canonicalValue),
|
|
1840
|
+
canonicalValue: event.canonicalValue,
|
|
1841
|
+
action: event.canonicalAction,
|
|
1842
|
+
}
|
|
1843
|
+
: undefined;
|
|
1844
|
+
const textForAgent = reaction
|
|
1845
|
+
? (0, model_1.renderBlueBubblesReactionText)(reaction, { guid: targetMessageGuid })
|
|
1846
|
+
: event.kind === "edit"
|
|
1847
|
+
? event.text ? `edited message: ${event.text}` : "edited a message"
|
|
1848
|
+
: event.kind === "unsend"
|
|
1849
|
+
? "unsent a message"
|
|
1850
|
+
: event.kind === "read"
|
|
1851
|
+
? "message marked as read"
|
|
1852
|
+
: "message marked as delivered";
|
|
1853
|
+
return {
|
|
1854
|
+
kind: "mutation",
|
|
1855
|
+
eventType: event.sourceEventType,
|
|
1856
|
+
mutationType: event.kind,
|
|
1857
|
+
messageGuid,
|
|
1858
|
+
targetMessageGuid,
|
|
1859
|
+
timestamp,
|
|
1860
|
+
fromMe: event.fromMe,
|
|
1861
|
+
sender,
|
|
1862
|
+
chat,
|
|
1863
|
+
shouldNotifyAgent,
|
|
1864
|
+
textForAgent,
|
|
1865
|
+
requiresRepair: true,
|
|
1866
|
+
...(reaction ? { reaction } : {}),
|
|
1867
|
+
...(event.kind === "edit" && event.text !== null ? { editedText: event.text } : {}),
|
|
1868
|
+
...(event.revision ? { revision: event.revision } : {}),
|
|
1869
|
+
...(event.effectiveAt ? { effectiveTimestamp: Date.parse(event.effectiveAt) } : {}),
|
|
1870
|
+
...(event.kind === "unsend" && event.effectiveAt
|
|
1871
|
+
? { retractionTimestamp: Date.parse(event.effectiveAt) }
|
|
1872
|
+
: {}),
|
|
1873
|
+
};
|
|
1845
1874
|
}
|
|
1846
|
-
function
|
|
1847
|
-
|
|
1875
|
+
function semanticHandledOutcome(event, result) {
|
|
1876
|
+
if (result.reason === "from_me")
|
|
1877
|
+
return "ignored_self";
|
|
1878
|
+
if (event.kind === "message") {
|
|
1879
|
+
return result.notifiedAgent ? "message_completed" : "message_observed";
|
|
1880
|
+
}
|
|
1881
|
+
switch (event.mutationType) {
|
|
1882
|
+
case "edit": return "edit_capture_only";
|
|
1883
|
+
case "unsend": return "unsend_capture_only";
|
|
1884
|
+
case "read": return "read_audit_only";
|
|
1885
|
+
case "delivery": return "delivery_audit_only";
|
|
1886
|
+
case "reaction":
|
|
1887
|
+
if (result.reason === "restricted_feedback_settled")
|
|
1888
|
+
return "restricted_feedback_settled";
|
|
1889
|
+
if (result.reason === "restricted_feedback_observed")
|
|
1890
|
+
return "restricted_feedback_observed";
|
|
1891
|
+
return "restricted_feedback_failed";
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
function materializeRestrictedBlueBubblesReactionEvent(captured, repaired) {
|
|
1895
|
+
// Classification reached `restricted_feedback` from the validated v1
|
|
1896
|
+
// capture's reaction tuple. Direct capture and recovery both construct the
|
|
1897
|
+
// paired normalized event from that same tuple, so it is the authoritative
|
|
1898
|
+
// reaction object; repair contributes routing only and may legitimately
|
|
1899
|
+
// promote its own transport-shaped result to `message`.
|
|
1900
|
+
const event = captured.normalized;
|
|
1901
|
+
return {
|
|
1902
|
+
...event,
|
|
1903
|
+
mutationType: "reaction",
|
|
1904
|
+
reaction: event.reaction,
|
|
1905
|
+
targetMessageGuid: event.targetMessageGuid,
|
|
1906
|
+
chat: repaired.chat,
|
|
1907
|
+
requiresRepair: repaired.requiresRepair,
|
|
1908
|
+
repairNotice: repaired.repairNotice,
|
|
1909
|
+
};
|
|
1848
1910
|
}
|
|
1849
|
-
function
|
|
1850
|
-
const
|
|
1851
|
-
|
|
1852
|
-
|
|
1911
|
+
async function classifyCapturedBlueBubblesReaction(captured, resolvedDeps) {
|
|
1912
|
+
const event = captured.capture.event;
|
|
1913
|
+
if (event.kind !== "reaction" || !event.canonicalAction || !event.canonicalValue)
|
|
1914
|
+
return null;
|
|
1915
|
+
const input = {
|
|
1916
|
+
fromMe: event.fromMe,
|
|
1917
|
+
action: event.canonicalAction,
|
|
1918
|
+
canonicalValue: event.canonicalValue,
|
|
1919
|
+
targetAuthorship: event.targetAuthorship,
|
|
1920
|
+
};
|
|
1921
|
+
const initial = (0, reaction_policy_1.classifyBlueBubblesReaction)(input);
|
|
1922
|
+
if (initial.route !== "trust_required") {
|
|
1923
|
+
return { decision: initial, trustedFriend: null };
|
|
1924
|
+
}
|
|
1925
|
+
let trustedActor = false;
|
|
1926
|
+
let trustedFriend = null;
|
|
1853
1927
|
try {
|
|
1854
|
-
|
|
1928
|
+
const friend = await resolvedDeps.createFriendStore().findByExternalId("imessage-handle", event.actor.externalId);
|
|
1929
|
+
trustedActor = friends_1.TRUSTED_LEVELS.has(friend?.trustLevel ?? "stranger");
|
|
1930
|
+
if (trustedActor && friend)
|
|
1931
|
+
trustedFriend = friend;
|
|
1855
1932
|
}
|
|
1856
|
-
catch {
|
|
1857
|
-
|
|
1933
|
+
catch (error) {
|
|
1934
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1935
|
+
level: "warn",
|
|
1936
|
+
component: "senses",
|
|
1937
|
+
event: "senses.bluebubbles_reaction_trust_lookup_error",
|
|
1938
|
+
message: "failed direct actor trust lookup for bluebubbles reaction",
|
|
1939
|
+
meta: {
|
|
1940
|
+
keyHash: captured.capture.keyHash,
|
|
1941
|
+
provider: event.actor.provider,
|
|
1942
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
1943
|
+
},
|
|
1944
|
+
});
|
|
1858
1945
|
}
|
|
1859
|
-
return
|
|
1860
|
-
.
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1946
|
+
return {
|
|
1947
|
+
decision: (0, reaction_policy_1.classifyBlueBubblesReaction)({ ...input, trustedActor }),
|
|
1948
|
+
trustedFriend,
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
async function handleRestrictedBlueBubblesReactionFeedback(input) {
|
|
1952
|
+
const { event, trustedFriend, client, resolvedDeps, options } = input;
|
|
1953
|
+
const target = input.resolvedReactionTarget
|
|
1954
|
+
?? await resolveReactionTarget(client, event.targetMessageGuid);
|
|
1955
|
+
event.textForAgent = (0, model_1.renderBlueBubblesReactionText)(event.reaction, target);
|
|
1956
|
+
const channel = (0, friends_1.getChannelCapabilities)("bluebubbles");
|
|
1957
|
+
const context = { friend: trustedFriend, channel, isGroupChat: event.chat.isGroup };
|
|
1958
|
+
const userMessage = {
|
|
1959
|
+
role: "user",
|
|
1960
|
+
content: buildInboundContent(event),
|
|
1961
|
+
};
|
|
1962
|
+
const orientationFrame = (0, orientation_frame_1.buildOrientationFrame)({
|
|
1963
|
+
channel: "bluebubbles",
|
|
1964
|
+
messages: [userMessage],
|
|
1965
|
+
currentUserMessages: [userMessage],
|
|
1966
|
+
source: buildBlueBubblesOrientationSource(event, [], options.orientationEvidence, options.orientationConversationKind),
|
|
1967
|
+
speechKind: "reaction",
|
|
1968
|
+
});
|
|
1969
|
+
const callbacks = createBlueBubblesCallbacks(client, event.chat, createReplyTargetController(event), event.chat.isGroup, undefined, { enableSilenceWatchdog: false, enableActivitySignals: false });
|
|
1970
|
+
const controller = new AbortController();
|
|
1971
|
+
const timeoutMs = options.timeoutMs ?? BLUEBUBBLES_LIVE_TURN_TIMEOUT_MS;
|
|
1972
|
+
let timeoutTimer;
|
|
1973
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1974
|
+
timeoutTimer = setTimeout(() => {
|
|
1975
|
+
const error = new BlueBubblesRecoveryTurnTimeoutError(timeoutMs);
|
|
1976
|
+
callbacks.cancelOutbound("turn_timeout");
|
|
1977
|
+
controller.abort(error);
|
|
1978
|
+
reject(error);
|
|
1979
|
+
}, timeoutMs);
|
|
1980
|
+
if (typeof timeoutTimer.unref === "function") {
|
|
1981
|
+
timeoutTimer.unref();
|
|
1865
1982
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1983
|
+
});
|
|
1984
|
+
try {
|
|
1985
|
+
const restrictedTurnAndDelivery = async () => {
|
|
1986
|
+
const result = await resolvedDeps.runAgent([userMessage], callbacks, "bluebubbles", controller.signal, {
|
|
1987
|
+
isReactionSignal: true,
|
|
1988
|
+
restrictedReactionFeedback: true,
|
|
1989
|
+
skipKeptNotes: true,
|
|
1990
|
+
orientationFrame,
|
|
1991
|
+
toolContext: {
|
|
1992
|
+
signin: async () => undefined,
|
|
1993
|
+
context,
|
|
1994
|
+
currentIngressEvidence: options.currentIngressEvidence,
|
|
1995
|
+
},
|
|
1996
|
+
});
|
|
1997
|
+
if (result.outcome === "settled" || result.outcome === "blocked") {
|
|
1998
|
+
await callbacks.flush({ signal: controller.signal });
|
|
1999
|
+
}
|
|
2000
|
+
return result;
|
|
2001
|
+
};
|
|
2002
|
+
const result = await Promise.race([
|
|
2003
|
+
restrictedTurnAndDelivery(),
|
|
2004
|
+
timeoutPromise,
|
|
2005
|
+
]);
|
|
2006
|
+
if (result.outcome === "settled" || result.outcome === "blocked") {
|
|
2007
|
+
return {
|
|
2008
|
+
handled: true,
|
|
2009
|
+
notifiedAgent: true,
|
|
2010
|
+
kind: event.kind,
|
|
2011
|
+
reason: "restricted_feedback_settled",
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
if (result.outcome === "observed") {
|
|
2015
|
+
return {
|
|
2016
|
+
handled: true,
|
|
2017
|
+
notifiedAgent: false,
|
|
2018
|
+
kind: event.kind,
|
|
2019
|
+
reason: "restricted_feedback_observed",
|
|
2020
|
+
};
|
|
1868
2021
|
}
|
|
2022
|
+
return {
|
|
2023
|
+
handled: true,
|
|
2024
|
+
notifiedAgent: false,
|
|
2025
|
+
kind: event.kind,
|
|
2026
|
+
reason: "restricted_feedback_failed",
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
catch (error) {
|
|
2030
|
+
callbacks.onError(error instanceof Error ? error : new Error(String(error)), "terminal");
|
|
2031
|
+
return {
|
|
2032
|
+
handled: true,
|
|
2033
|
+
notifiedAgent: false,
|
|
2034
|
+
kind: event.kind,
|
|
2035
|
+
reason: "restricted_feedback_failed",
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
finally {
|
|
2039
|
+
clearTimeout(timeoutTimer);
|
|
2040
|
+
await callbacks.finish({ timeoutMs: BLUEBUBBLES_CALLBACK_CLEANUP_TIMEOUT_MS });
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
function writeCapturedBlueBubblesHandled(agentName, capture, outcome) {
|
|
2044
|
+
const handledResult = (0, semantic_receipts_1.writeBlueBubblesSemanticHandled)(agentName, {
|
|
2045
|
+
schemaVersion: 1,
|
|
2046
|
+
canonicalKey: capture.canonicalKey,
|
|
2047
|
+
keyHash: capture.keyHash,
|
|
2048
|
+
handledAt: new Date().toISOString(),
|
|
2049
|
+
outcome,
|
|
2050
|
+
detailCode: null,
|
|
1869
2051
|
});
|
|
2052
|
+
if (handledResult === "semantic_handled_collision") {
|
|
2053
|
+
throw new Error("semantic_handled_collision");
|
|
2054
|
+
}
|
|
1870
2055
|
}
|
|
1871
|
-
function
|
|
1872
|
-
const
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
2056
|
+
async function handleCapturedBlueBubblesSemanticEvent(captured, resolvedDeps, source, options = {}) {
|
|
2057
|
+
const agentName = resolvedDeps.getAgentName();
|
|
2058
|
+
const identity = {
|
|
2059
|
+
canonicalKey: captured.capture.canonicalKey,
|
|
2060
|
+
keyHash: captured.capture.keyHash,
|
|
2061
|
+
};
|
|
2062
|
+
const claim = await (0, semantic_receipts_1.acquireBlueBubblesSemanticClaim)(agentName, identity);
|
|
2063
|
+
if (claim.status === "already_handled") {
|
|
2064
|
+
return {
|
|
2065
|
+
handled: true,
|
|
2066
|
+
notifiedAgent: false,
|
|
2067
|
+
kind: captured.normalized.kind,
|
|
2068
|
+
reason: "already_processed",
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
if (claim.status === "timeout") {
|
|
2072
|
+
return {
|
|
2073
|
+
handled: false,
|
|
2074
|
+
notifiedAgent: false,
|
|
2075
|
+
kind: captured.normalized.kind,
|
|
2076
|
+
reason: "semantic_claim_timeout",
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
try {
|
|
2080
|
+
const capturedOptions = {
|
|
2081
|
+
...options,
|
|
2082
|
+
currentIngressEvidence: Object.freeze({
|
|
2083
|
+
schemaVersion: 1,
|
|
2084
|
+
provider: "bluebubbles",
|
|
2085
|
+
captureKeyHash: captured.capture.keyHash,
|
|
2086
|
+
}),
|
|
2087
|
+
orientationEvidence: captured.capture.event,
|
|
2088
|
+
orientationConversationKind: captured.normalized.chat.isGroup ? "group" : "one_to_one",
|
|
2089
|
+
};
|
|
2090
|
+
const reactionClassification = await classifyCapturedBlueBubblesReaction(captured, resolvedDeps);
|
|
2091
|
+
const reactionDecision = reactionClassification?.decision ?? null;
|
|
2092
|
+
if (reactionDecision?.route === "capture_only") {
|
|
2093
|
+
const result = {
|
|
2094
|
+
handled: true,
|
|
2095
|
+
notifiedAgent: false,
|
|
2096
|
+
kind: captured.normalized.kind,
|
|
2097
|
+
reason: reactionDecision.outcome === "ignored_self" ? "from_me" : "mutation_state_only",
|
|
2098
|
+
};
|
|
2099
|
+
writeCapturedBlueBubblesHandled(agentName, captured.capture, reactionDecision.outcome);
|
|
2100
|
+
return result;
|
|
2101
|
+
}
|
|
2102
|
+
let handledEvent = captured.normalized;
|
|
2103
|
+
let result;
|
|
2104
|
+
if (isAuditOnlySemanticCapture(captured.capture)) {
|
|
2105
|
+
result = {
|
|
2106
|
+
handled: true,
|
|
2107
|
+
notifiedAgent: false,
|
|
2108
|
+
kind: captured.normalized.kind,
|
|
2109
|
+
reason: "mutation_state_only",
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
else {
|
|
2113
|
+
const client = resolvedDeps.createClient();
|
|
2114
|
+
const repaired = await client.repairEvent(captured.normalized);
|
|
2115
|
+
if (semanticCaptureRequiresRoutingRepair(captured.capture)
|
|
2116
|
+
&& !hasResolvedBlueBubblesRouting(repaired)) {
|
|
2117
|
+
throw new Error("semantic_capture_routing_invalid");
|
|
2118
|
+
}
|
|
2119
|
+
const identityGroundedRepaired = {
|
|
2120
|
+
...repaired,
|
|
2121
|
+
fromMe: captured.normalized.fromMe,
|
|
2122
|
+
sender: captured.normalized.sender,
|
|
2123
|
+
};
|
|
2124
|
+
handledEvent = identityGroundedRepaired;
|
|
2125
|
+
if (reactionDecision?.route === "restricted_feedback") {
|
|
2126
|
+
const restrictedEvent = materializeRestrictedBlueBubblesReactionEvent(captured, repaired);
|
|
2127
|
+
handledEvent = restrictedEvent;
|
|
2128
|
+
result = await handleRestrictedBlueBubblesReactionFeedback({
|
|
2129
|
+
event: restrictedEvent,
|
|
2130
|
+
trustedFriend: reactionClassification.trustedFriend,
|
|
2131
|
+
client,
|
|
2132
|
+
resolvedDeps,
|
|
2133
|
+
options: capturedOptions,
|
|
2134
|
+
resolvedReactionTarget: captured.resolvedReactionTarget ?? options.resolvedReactionTarget,
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
else {
|
|
2138
|
+
result = await handleBlueBubblesNormalizedEvent(identityGroundedRepaired, resolvedDeps, source, {
|
|
2139
|
+
...capturedOptions,
|
|
2140
|
+
resolvedReactionTarget: captured.resolvedReactionTarget ?? options.resolvedReactionTarget,
|
|
2141
|
+
});
|
|
1883
2142
|
}
|
|
1884
2143
|
}
|
|
2144
|
+
writeCapturedBlueBubblesHandled(agentName, captured.capture, semanticHandledOutcome(handledEvent, result));
|
|
2145
|
+
return result;
|
|
2146
|
+
}
|
|
2147
|
+
finally {
|
|
2148
|
+
(0, semantic_receipts_1.releaseBlueBubblesSemanticClaim)(agentName, claim);
|
|
1885
2149
|
}
|
|
1886
|
-
return false;
|
|
1887
2150
|
}
|
|
1888
|
-
function
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
(
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2151
|
+
async function handleBlueBubblesEvent(payload, deps = {}) {
|
|
2152
|
+
const resolvedDeps = { ...defaultDeps, ...deps };
|
|
2153
|
+
let normalized;
|
|
2154
|
+
try {
|
|
2155
|
+
normalized = (0, model_1.normalizeBlueBubblesEvent)(payload);
|
|
2156
|
+
}
|
|
2157
|
+
catch (error) {
|
|
2158
|
+
if (error instanceof model_1.BlueBubblesIgnoredEventError) {
|
|
2159
|
+
(0, runtime_1.emitNervesEvent)({
|
|
2160
|
+
component: "senses",
|
|
2161
|
+
event: "senses.bluebubbles_event_skipped",
|
|
2162
|
+
message: "skipped ignorable bluebubbles event",
|
|
2163
|
+
meta: {
|
|
2164
|
+
eventType: error.eventType,
|
|
2165
|
+
},
|
|
2166
|
+
});
|
|
2167
|
+
return {
|
|
2168
|
+
handled: true,
|
|
2169
|
+
notifiedAgent: false,
|
|
2170
|
+
reason: "ignored",
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
throw error;
|
|
2174
|
+
}
|
|
2175
|
+
const captured = await captureBlueBubblesSemanticEvent(normalized, resolvedDeps, "webhook");
|
|
2176
|
+
if (captured.status === "audit_only") {
|
|
2177
|
+
return { handled: true, notifiedAgent: false, kind: normalized.kind, reason: "ignored" };
|
|
2178
|
+
}
|
|
2179
|
+
return handleCapturedBlueBubblesSemanticEvent(captured, resolvedDeps, "webhook");
|
|
2180
|
+
}
|
|
2181
|
+
function parseTimestampMs(value) {
|
|
2182
|
+
if (!value)
|
|
2183
|
+
return null;
|
|
2184
|
+
const parsed = Date.parse(value);
|
|
2185
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1906
2186
|
}
|
|
1907
2187
|
function resolveBlueBubblesCatchUpSince(previousState, nowMs = Date.now()) {
|
|
1908
2188
|
if (previousState.upstreamStatus === "error") {
|
|
@@ -1927,9 +2207,9 @@ function formatBlueBubblesRuntimeDetail(queued, failed, active) {
|
|
|
1927
2207
|
return "upstream reachable";
|
|
1928
2208
|
}
|
|
1929
2209
|
function blueBubblesPendingRecoverySnapshot(agentName, nowMs = Date.now()) {
|
|
1930
|
-
const pendingEntries =
|
|
2210
|
+
const pendingEntries = (0, semantic_receipts_1.listPendingBlueBubblesSemanticCaptures)(agentName);
|
|
1931
2211
|
const pendingRecordedAt = pendingEntries
|
|
1932
|
-
.map((entry) => entry.
|
|
2212
|
+
.map((entry) => entry.capturedAt)
|
|
1933
2213
|
.map((value) => ({ value, ms: Date.parse(value) }))
|
|
1934
2214
|
.filter((entry) => Number.isFinite(entry.ms))
|
|
1935
2215
|
.sort((left, right) => left.ms - right.ms);
|
|
@@ -2048,7 +2328,8 @@ async function catchUpMissedBlueBubblesMessages(deps = {}, previousState, option
|
|
|
2048
2328
|
const client = resolvedDeps.createClient();
|
|
2049
2329
|
const result = { inspected: 0, recovered: 0, skipped: 0, failed: 0 };
|
|
2050
2330
|
const state = previousState ?? (0, runtime_state_1.readBlueBubblesRuntimeState)(agentName);
|
|
2051
|
-
const
|
|
2331
|
+
const cutover = (0, semantic_receipts_1.initializeBlueBubblesSemanticCutover)(agentName);
|
|
2332
|
+
const catchUpSince = Math.max(resolveBlueBubblesCatchUpSince(state), Date.parse(cutover.effectiveAt));
|
|
2052
2333
|
const processTurns = options.processTurns !== false;
|
|
2053
2334
|
/* v8 ignore next -- older injected test doubles may omit the catch-up query method */
|
|
2054
2335
|
if (!client.listRecentMessages)
|
|
@@ -2121,37 +2402,38 @@ async function catchUpMissedBlueBubblesMessages(deps = {}, previousState, option
|
|
|
2121
2402
|
for (const event of candidates) {
|
|
2122
2403
|
result.inspected++;
|
|
2123
2404
|
if (event.fromMe
|
|
2124
|
-
|| event.timestamp < catchUpSince
|
|
2125
|
-
|| (0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, event.messageGuid)
|
|
2126
|
-
|| isBlueBubblesMessageInFlight(event.chat.sessionKey, event.messageGuid)) {
|
|
2405
|
+
|| event.timestamp < catchUpSince) {
|
|
2127
2406
|
result.skipped++;
|
|
2128
2407
|
continue;
|
|
2129
2408
|
}
|
|
2130
|
-
|
|
2131
|
-
|
|
2409
|
+
try {
|
|
2410
|
+
const captured = await captureBlueBubblesSemanticEvent(event, resolvedDeps, "upstream-catchup");
|
|
2411
|
+
if (captured.status === "audit_only") {
|
|
2132
2412
|
result.skipped++;
|
|
2133
2413
|
continue;
|
|
2134
2414
|
}
|
|
2135
|
-
(
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
result.skipped++;
|
|
2415
|
+
if (!processTurns) {
|
|
2416
|
+
if (captured.writeResult === "semantic_capture_duplicate") {
|
|
2417
|
+
result.skipped++;
|
|
2418
|
+
}
|
|
2419
|
+
else {
|
|
2420
|
+
result.queued = (result.queued ?? 0) + 1;
|
|
2421
|
+
}
|
|
2143
2422
|
continue;
|
|
2144
2423
|
}
|
|
2145
|
-
const handled = await
|
|
2424
|
+
const handled = await handleCapturedBlueBubblesSemanticEvent(captured, resolvedDeps, "upstream-catchup", {
|
|
2146
2425
|
timeoutMs: BLUEBUBBLES_RECOVERY_TURN_TIMEOUT_MS,
|
|
2147
2426
|
autonomyBudgetTrigger: "recovery",
|
|
2148
2427
|
});
|
|
2149
|
-
if (
|
|
2428
|
+
if (handled.reason === "semantic_claim_timeout") {
|
|
2429
|
+
continue;
|
|
2430
|
+
}
|
|
2431
|
+
if (shouldCountBlueBubblesRecoveryResultAsSkipped(handled.reason) || !handled.notifiedAgent) {
|
|
2150
2432
|
result.skipped++;
|
|
2151
2433
|
}
|
|
2152
2434
|
else {
|
|
2153
2435
|
result.recovered++;
|
|
2154
|
-
result.lastRecoveredMessageGuid =
|
|
2436
|
+
result.lastRecoveredMessageGuid = event.messageGuid;
|
|
2155
2437
|
}
|
|
2156
2438
|
}
|
|
2157
2439
|
catch (error) {
|
|
@@ -2179,72 +2461,43 @@ async function catchUpMissedBlueBubblesMessages(deps = {}, previousState, option
|
|
|
2179
2461
|
}
|
|
2180
2462
|
return result;
|
|
2181
2463
|
}
|
|
2182
|
-
function inboundEntryToRecoveryEvent(entry) {
|
|
2183
|
-
const chatIdentifier = entry.chatIdentifier ?? extractChatIdentifierFromSessionKey(entry.sessionKey) ?? "unknown";
|
|
2184
|
-
const normalizedSessionKey = normalizeBlueBubblesSessionKey(entry.sessionKey);
|
|
2185
|
-
const chatGuid = entry.chatGuid ?? (normalizedSessionKey.startsWith("chat:") ? normalizedSessionKey.slice("chat:".length) : undefined);
|
|
2186
|
-
const isGroup = normalizedSessionKey.includes("+;");
|
|
2187
|
-
return {
|
|
2188
|
-
kind: "message",
|
|
2189
|
-
eventType: "new-message",
|
|
2190
|
-
messageGuid: entry.messageGuid,
|
|
2191
|
-
timestamp: parseTimestampMs(entry.recordedAt) ?? Date.now(),
|
|
2192
|
-
fromMe: false,
|
|
2193
|
-
sender: {
|
|
2194
|
-
provider: "imessage-handle",
|
|
2195
|
-
externalId: chatIdentifier,
|
|
2196
|
-
rawId: chatIdentifier,
|
|
2197
|
-
displayName: chatIdentifier,
|
|
2198
|
-
},
|
|
2199
|
-
chat: {
|
|
2200
|
-
chatGuid,
|
|
2201
|
-
chatIdentifier,
|
|
2202
|
-
isGroup,
|
|
2203
|
-
sessionKey: normalizedSessionKey,
|
|
2204
|
-
sendTarget: chatGuid
|
|
2205
|
-
? { kind: "chat_guid", value: chatGuid }
|
|
2206
|
-
: { kind: "chat_identifier", value: chatIdentifier },
|
|
2207
|
-
participantHandles: [],
|
|
2208
|
-
},
|
|
2209
|
-
text: entry.textForAgent,
|
|
2210
|
-
textForAgent: entry.textForAgent,
|
|
2211
|
-
attachments: [],
|
|
2212
|
-
hasPayloadData: false,
|
|
2213
|
-
requiresRepair: true,
|
|
2214
|
-
};
|
|
2215
|
-
}
|
|
2216
2464
|
async function recoverCapturedBlueBubblesInboundMessages(deps = {}) {
|
|
2217
2465
|
const resolvedDeps = { ...defaultDeps, ...deps };
|
|
2218
2466
|
const agentName = resolvedDeps.getAgentName();
|
|
2219
|
-
const
|
|
2467
|
+
const cutover = (0, semantic_receipts_1.initializeBlueBubblesSemanticCutover)(agentName);
|
|
2220
2468
|
const result = { recovered: 0, skipped: 0, failed: 0 };
|
|
2221
|
-
const
|
|
2222
|
-
const candidates = (0,
|
|
2223
|
-
.
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
.
|
|
2230
|
-
|
|
2231
|
-
if (
|
|
2232
|
-
|| (0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, entry.messageGuid)
|
|
2233
|
-
|| isBlueBubblesMessageInFlight(entry.sessionKey, entry.messageGuid)) {
|
|
2469
|
+
const seenKeyHashes = new Set();
|
|
2470
|
+
const candidates = (0, semantic_receipts_1.listPendingBlueBubblesSemanticCaptures)(agentName)
|
|
2471
|
+
.sort((left, right) => left.capturedAt.localeCompare(right.capturedAt));
|
|
2472
|
+
for (const capture of candidates) {
|
|
2473
|
+
if (seenKeyHashes.has(capture.keyHash)) {
|
|
2474
|
+
result.skipped++;
|
|
2475
|
+
continue;
|
|
2476
|
+
}
|
|
2477
|
+
seenKeyHashes.add(capture.keyHash);
|
|
2478
|
+
const disposition = (0, semantic_receipts_1.classifyBlueBubblesRecoveryRecord)(capture, cutover);
|
|
2479
|
+
if (disposition.disposition === "audit_only" && disposition.reason !== "audit_event") {
|
|
2234
2480
|
result.skipped++;
|
|
2235
2481
|
continue;
|
|
2236
2482
|
}
|
|
2483
|
+
const normalized = semanticCaptureToNormalizedEvent(capture);
|
|
2237
2484
|
try {
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2485
|
+
if (!normalized)
|
|
2486
|
+
throw new Error("semantic_capture_routing_invalid");
|
|
2487
|
+
const handled = await handleCapturedBlueBubblesSemanticEvent({
|
|
2488
|
+
status: "captured",
|
|
2489
|
+
capture,
|
|
2490
|
+
normalized,
|
|
2491
|
+
writeResult: "semantic_capture_duplicate",
|
|
2492
|
+
}, resolvedDeps, "webhook", {
|
|
2244
2493
|
timeoutMs: BLUEBUBBLES_RECOVERY_TURN_TIMEOUT_MS,
|
|
2245
2494
|
autonomyBudgetTrigger: "recovery",
|
|
2495
|
+
semanticRecovery: true,
|
|
2246
2496
|
});
|
|
2247
|
-
if (
|
|
2497
|
+
if (handled.reason === "semantic_claim_timeout") {
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
2500
|
+
if (shouldCountBlueBubblesRecoveryResultAsSkipped(handled.reason) || !handled.notifiedAgent) {
|
|
2248
2501
|
result.skipped++;
|
|
2249
2502
|
}
|
|
2250
2503
|
else {
|
|
@@ -2252,7 +2505,8 @@ async function recoverCapturedBlueBubblesInboundMessages(deps = {}) {
|
|
|
2252
2505
|
}
|
|
2253
2506
|
}
|
|
2254
2507
|
catch (error) {
|
|
2255
|
-
|
|
2508
|
+
if (normalized)
|
|
2509
|
+
recordBlueBubblesRecoveryFailureForBudget(agentName, normalized, "webhook");
|
|
2256
2510
|
result.failed++;
|
|
2257
2511
|
(0, runtime_1.emitNervesEvent)({
|
|
2258
2512
|
level: "warn",
|
|
@@ -2260,58 +2514,33 @@ async function recoverCapturedBlueBubblesInboundMessages(deps = {}) {
|
|
|
2260
2514
|
event: "senses.bluebubbles_capture_recovery_error",
|
|
2261
2515
|
message: "captured bluebubbles message recovery failed",
|
|
2262
2516
|
meta: {
|
|
2263
|
-
messageGuid:
|
|
2264
|
-
sessionKey:
|
|
2517
|
+
messageGuid: capture.event.eventGuid,
|
|
2518
|
+
sessionKey: capture.event.sessionKey,
|
|
2265
2519
|
reason: error instanceof Error ? error.message : String(error),
|
|
2266
2520
|
},
|
|
2267
2521
|
});
|
|
2268
2522
|
}
|
|
2269
2523
|
}
|
|
2524
|
+
const semanticEventGuids = new Set(candidates
|
|
2525
|
+
.map((capture) => capture.event.eventGuid)
|
|
2526
|
+
.filter((eventGuid) => typeof eventGuid === "string")
|
|
2527
|
+
.map((eventGuid) => eventGuid.trim().toLowerCase()));
|
|
2528
|
+
for (const legacyEntry of (0, inbound_log_1.listRecordedBlueBubblesInbound)(agentName)) {
|
|
2529
|
+
if (semanticEventGuids.has(legacyEntry.messageGuid.trim().toLowerCase()))
|
|
2530
|
+
continue;
|
|
2531
|
+
(0, semantic_receipts_1.classifyBlueBubblesRecoveryRecord)(legacyEntry, cutover);
|
|
2532
|
+
result.skipped++;
|
|
2533
|
+
}
|
|
2270
2534
|
return result;
|
|
2271
2535
|
}
|
|
2272
2536
|
async function recoverMissedBlueBubblesMessages(deps = {}) {
|
|
2273
2537
|
const resolvedDeps = { ...defaultDeps, ...deps };
|
|
2274
2538
|
const agentName = resolvedDeps.getAgentName();
|
|
2275
|
-
const
|
|
2539
|
+
const cutover = (0, semantic_receipts_1.initializeBlueBubblesSemanticCutover)(agentName);
|
|
2276
2540
|
const result = { recovered: 0, skipped: 0, pending: 0, failed: 0 };
|
|
2277
2541
|
for (const candidate of (0, mutation_log_1.listBlueBubblesRecoveryCandidates)(agentName)) {
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|| isBlueBubblesMessageInFlight(candidate.sessionKey, candidate.messageGuid)) {
|
|
2281
|
-
result.skipped++;
|
|
2282
|
-
continue;
|
|
2283
|
-
}
|
|
2284
|
-
try {
|
|
2285
|
-
const repaired = await client.repairEvent(mutationEntryToEvent(candidate));
|
|
2286
|
-
if (repaired.kind !== "message") {
|
|
2287
|
-
result.pending++;
|
|
2288
|
-
continue;
|
|
2289
|
-
}
|
|
2290
|
-
const handled = await handleBlueBubblesNormalizedEvent(repaired, resolvedDeps, "mutation-recovery", {
|
|
2291
|
-
timeoutMs: BLUEBUBBLES_RECOVERY_TURN_TIMEOUT_MS,
|
|
2292
|
-
autonomyBudgetTrigger: "recovery",
|
|
2293
|
-
});
|
|
2294
|
-
if (shouldCountBlueBubblesRecoveryResultAsSkipped(handled.reason)) {
|
|
2295
|
-
result.skipped++;
|
|
2296
|
-
}
|
|
2297
|
-
else {
|
|
2298
|
-
result.recovered++;
|
|
2299
|
-
}
|
|
2300
|
-
}
|
|
2301
|
-
catch (error) {
|
|
2302
|
-
recordBlueBubblesRecoveryFailureForBudget(agentName, mutationEntryToEvent(candidate), "mutation-recovery");
|
|
2303
|
-
result.failed++;
|
|
2304
|
-
(0, runtime_1.emitNervesEvent)({
|
|
2305
|
-
level: "warn",
|
|
2306
|
-
component: "senses",
|
|
2307
|
-
event: "senses.bluebubbles_recovery_error",
|
|
2308
|
-
message: "bluebubbles backlog recovery failed",
|
|
2309
|
-
meta: {
|
|
2310
|
-
messageGuid: candidate.messageGuid,
|
|
2311
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
2312
|
-
},
|
|
2313
|
-
});
|
|
2314
|
-
}
|
|
2542
|
+
(0, semantic_receipts_1.classifyBlueBubblesRecoveryRecord)(candidate, cutover);
|
|
2543
|
+
result.skipped++;
|
|
2315
2544
|
}
|
|
2316
2545
|
if (result.recovered > 0 || result.skipped > 0 || result.pending > 0 || result.failed > 0) {
|
|
2317
2546
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -2404,26 +2633,33 @@ function createBlueBubblesWebhookHandler(deps = {}) {
|
|
|
2404
2633
|
return;
|
|
2405
2634
|
}
|
|
2406
2635
|
const resolvedDeps = { ...defaultDeps, ...deps };
|
|
2407
|
-
|
|
2408
|
-
|
|
2636
|
+
let captured;
|
|
2637
|
+
try {
|
|
2638
|
+
captured = await captureBlueBubblesSemanticEvent(normalized, resolvedDeps, "webhook");
|
|
2409
2639
|
}
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2640
|
+
catch (error) {
|
|
2641
|
+
(0, runtime_1.emitNervesEvent)({
|
|
2642
|
+
level: "error",
|
|
2643
|
+
component: "senses",
|
|
2644
|
+
event: "senses.bluebubbles_webhook_error",
|
|
2645
|
+
message: "bluebubbles semantic capture failed before acknowledgement",
|
|
2646
|
+
meta: {
|
|
2647
|
+
messageGuid: normalized.messageGuid,
|
|
2648
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
2649
|
+
},
|
|
2650
|
+
});
|
|
2651
|
+
writeSemanticCaptureFailure(res);
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
if (captured.status === "audit_only") {
|
|
2655
|
+
writeJson(res, 200, {
|
|
2656
|
+
handled: true,
|
|
2657
|
+
notifiedAgent: false,
|
|
2658
|
+
kind: normalized.kind,
|
|
2659
|
+
queued: false,
|
|
2660
|
+
reason: "ignored",
|
|
2661
|
+
});
|
|
2662
|
+
return;
|
|
2427
2663
|
}
|
|
2428
2664
|
writeJson(res, 200, {
|
|
2429
2665
|
handled: true,
|
|
@@ -2433,7 +2669,7 @@ function createBlueBubblesWebhookHandler(deps = {}) {
|
|
|
2433
2669
|
reason: "queued",
|
|
2434
2670
|
});
|
|
2435
2671
|
setTimeout(() => {
|
|
2436
|
-
void
|
|
2672
|
+
void handleCapturedBlueBubblesSemanticEvent(captured, resolvedDeps, "webhook").catch((error) => {
|
|
2437
2673
|
(0, runtime_1.emitNervesEvent)({
|
|
2438
2674
|
level: "error",
|
|
2439
2675
|
component: "senses",
|
|
@@ -2852,6 +3088,7 @@ async function drainAndSendPendingBlueBubbles(deps = {}, pendingRoot) {
|
|
|
2852
3088
|
}
|
|
2853
3089
|
function startBlueBubblesApp(deps = {}) {
|
|
2854
3090
|
const resolvedDeps = { ...defaultDeps, ...deps };
|
|
3091
|
+
(0, semantic_receipts_1.initializeBlueBubblesSemanticCutover)(resolvedDeps.getAgentName());
|
|
2855
3092
|
resolvedDeps.createClient();
|
|
2856
3093
|
const channelConfig = (0, config_1.getBlueBubblesChannelConfig)();
|
|
2857
3094
|
const server = resolvedDeps.createServer(createBlueBubblesWebhookHandler(deps));
|