@ouro.bot/cli 0.1.0-alpha.696 → 0.1.0-alpha.698
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/agent-entry.js +32 -2
- package/dist/heart/autonomy-budget.js +434 -0
- package/dist/heart/core.js +11 -0
- package/dist/heart/daemon/cadence.js +110 -0
- package/dist/heart/daemon/cli-exec.js +20 -0
- package/dist/heart/daemon/cli-help.js +74 -0
- package/dist/heart/daemon/cli-parse.js +466 -0
- package/dist/heart/daemon/daemon-entry.js +104 -3
- package/dist/heart/daemon/daemon.js +24 -3
- package/dist/heart/daemon/doctor.js +151 -0
- package/dist/heart/daemon/stale-bundle-prune.js +23 -6
- package/dist/heart/habits/habit-parser.js +4 -1
- package/dist/heart/habits/habit-scheduler.js +46 -38
- package/dist/heart/habits/habit-session.js +2 -3
- package/dist/heart/provider-credentials.js +16 -0
- package/dist/heart/run-ledger.js +247 -0
- package/dist/heart/runtime-credentials.js +12 -3
- package/dist/mind/prompt-budget.js +380 -0
- package/dist/repertoire/tools-base.js +2 -0
- package/dist/repertoire/tools-rsvp.js +139 -0
- package/dist/rsvp/aisleplanner-client.js +354 -0
- package/dist/rsvp/cli.js +704 -0
- package/dist/rsvp/config.js +327 -0
- package/dist/rsvp/cutover.js +431 -0
- package/dist/rsvp/diagnostics.js +222 -0
- package/dist/rsvp/diff-renderer.js +161 -0
- package/dist/rsvp/habit-policy.js +85 -0
- package/dist/rsvp/habit-stage.js +93 -0
- package/dist/rsvp/incident-bundle.js +97 -0
- package/dist/rsvp/migration.js +204 -0
- package/dist/rsvp/outbound-state.js +254 -0
- package/dist/rsvp/query.js +118 -0
- package/dist/rsvp/replay.js +233 -0
- package/dist/rsvp/snapshot.js +232 -0
- package/dist/senses/bluebubbles/client.js +13 -7
- package/dist/senses/bluebubbles/index.js +227 -22
- package/dist/senses/bluebubbles/outbound-state.js +304 -0
- package/dist/senses/bluebubbles/replay.js +147 -0
- package/dist/senses/context-packet-ledger.js +245 -0
- package/dist/senses/context-packets.js +154 -0
- package/dist/senses/pipeline.js +91 -1
- package/dist/senses/private-runtime-worker.js +170 -31
- package/dist/senses/private-runtime.js +13 -1
- package/dist/senses/voice/twilio-phone.js +9 -5
- package/package.json +1 -1
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RSVP_SNAPSHOT_POLICY_VERSION = void 0;
|
|
4
|
+
exports.rsvpHash = rsvpHash;
|
|
5
|
+
exports.rsvpSnapshotContentHash = rsvpSnapshotContentHash;
|
|
6
|
+
exports.buildRsvpSnapshot = buildRsvpSnapshot;
|
|
7
|
+
exports.parseRsvpSnapshot = parseRsvpSnapshot;
|
|
8
|
+
exports.serializeRsvpSnapshotMetadata = serializeRsvpSnapshotMetadata;
|
|
9
|
+
const node_crypto_1 = require("node:crypto");
|
|
10
|
+
const runtime_1 = require("../nerves/runtime");
|
|
11
|
+
exports.RSVP_SNAPSHOT_POLICY_VERSION = "rsvp-snapshot/v1";
|
|
12
|
+
function sha256Hex(input) {
|
|
13
|
+
return (0, node_crypto_1.createHash)("sha256").update(input).digest("hex");
|
|
14
|
+
}
|
|
15
|
+
function rsvpHash(input) {
|
|
16
|
+
return `sha256:${sha256Hex(canonicalJson(input))}`;
|
|
17
|
+
}
|
|
18
|
+
function canonicalJson(value) {
|
|
19
|
+
if (Array.isArray(value))
|
|
20
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
|
|
21
|
+
if (value && typeof value === "object") {
|
|
22
|
+
const record = value;
|
|
23
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
24
|
+
}
|
|
25
|
+
return JSON.stringify(value);
|
|
26
|
+
}
|
|
27
|
+
function assertIsoTimestamp(value, label) {
|
|
28
|
+
if (!Number.isFinite(Date.parse(value))) {
|
|
29
|
+
throw new Error(`${label} must be an ISO timestamp`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function stringValue(value) {
|
|
33
|
+
return typeof value === "string" ? value : "";
|
|
34
|
+
}
|
|
35
|
+
function groupIdValue(value) {
|
|
36
|
+
if (typeof value === "string" || typeof value === "number")
|
|
37
|
+
return value;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function normalizeStatus(value) {
|
|
41
|
+
if (value === null || value === undefined)
|
|
42
|
+
return { status: "pending", sourceStatus: null };
|
|
43
|
+
const raw = String(value);
|
|
44
|
+
if (raw === "attending")
|
|
45
|
+
return { status: "attending", sourceStatus: raw };
|
|
46
|
+
if (raw === "declined")
|
|
47
|
+
return { status: "declined", sourceStatus: raw };
|
|
48
|
+
return { status: "unknown", sourceStatus: raw };
|
|
49
|
+
}
|
|
50
|
+
function displayName(firstName, lastName) {
|
|
51
|
+
const name = `${firstName} ${lastName}`.trim();
|
|
52
|
+
return name || "Unnamed guest";
|
|
53
|
+
}
|
|
54
|
+
function normalizeGuest(id, row) {
|
|
55
|
+
const firstName = stringValue(row.first_name).trim();
|
|
56
|
+
const lastName = stringValue(row.last_name).trim();
|
|
57
|
+
const normalizedStatus = normalizeStatus(row.attending_status);
|
|
58
|
+
return {
|
|
59
|
+
id,
|
|
60
|
+
firstName,
|
|
61
|
+
lastName,
|
|
62
|
+
displayName: displayName(firstName, lastName),
|
|
63
|
+
groupId: groupIdValue(row.group_id),
|
|
64
|
+
status: normalizedStatus.status,
|
|
65
|
+
sourceStatus: normalizedStatus.sourceStatus,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function summaryFor(guests) {
|
|
69
|
+
const summary = {
|
|
70
|
+
attending: 0,
|
|
71
|
+
declined: 0,
|
|
72
|
+
pending: 0,
|
|
73
|
+
unknown: 0,
|
|
74
|
+
total: guests.length,
|
|
75
|
+
};
|
|
76
|
+
for (const guest of guests) {
|
|
77
|
+
summary[guest.status] += 1;
|
|
78
|
+
}
|
|
79
|
+
return summary;
|
|
80
|
+
}
|
|
81
|
+
function hashBasis(snapshot) {
|
|
82
|
+
return {
|
|
83
|
+
schemaVersion: snapshot.schemaVersion,
|
|
84
|
+
policyVersion: snapshot.policyVersion,
|
|
85
|
+
agent: snapshot.agent,
|
|
86
|
+
fetchedAt: snapshot.fetchedAt,
|
|
87
|
+
source: snapshot.source,
|
|
88
|
+
provenance: snapshot.provenance,
|
|
89
|
+
guests: snapshot.guests,
|
|
90
|
+
allGuestIdsHash: snapshot.allGuestIdsHash,
|
|
91
|
+
summary: snapshot.summary,
|
|
92
|
+
privacy: snapshot.privacy,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function rsvpSnapshotContentHash(snapshot) {
|
|
96
|
+
return rsvpHash(hashBasis(snapshot));
|
|
97
|
+
}
|
|
98
|
+
function snapshotIdFor(contentHash) {
|
|
99
|
+
return `rsvp_${contentHash.slice("sha256:".length, "sha256:".length + 32)}`;
|
|
100
|
+
}
|
|
101
|
+
function allGuestIdsHash(allGuests) {
|
|
102
|
+
return rsvpHash(Object.keys(allGuests).sort());
|
|
103
|
+
}
|
|
104
|
+
function buildRsvpSnapshot(input) {
|
|
105
|
+
assertIsoTimestamp(input.fetchedAt, "fetchedAt");
|
|
106
|
+
if (input.provenance.kind === "legacy-import")
|
|
107
|
+
assertIsoTimestamp(input.provenance.importedAt, "importedAt");
|
|
108
|
+
const guests = Object.entries(input.guests)
|
|
109
|
+
.map(([id, row]) => normalizeGuest(id, row))
|
|
110
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
111
|
+
const base = {
|
|
112
|
+
schemaVersion: 1,
|
|
113
|
+
policyVersion: exports.RSVP_SNAPSHOT_POLICY_VERSION,
|
|
114
|
+
agent: input.agent,
|
|
115
|
+
fetchedAt: input.fetchedAt,
|
|
116
|
+
source: input.source,
|
|
117
|
+
provenance: input.provenance,
|
|
118
|
+
guests,
|
|
119
|
+
allGuestIdsHash: allGuestIdsHash(input.allGuests),
|
|
120
|
+
summary: summaryFor(guests),
|
|
121
|
+
privacy: {
|
|
122
|
+
rawCredentialsStored: false,
|
|
123
|
+
indexPolicy: { search: false, vector: false },
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
const contentHash = rsvpHash(hashBasis(base));
|
|
127
|
+
const snapshot = {
|
|
128
|
+
...base,
|
|
129
|
+
snapshotId: snapshotIdFor(contentHash),
|
|
130
|
+
contentHash,
|
|
131
|
+
};
|
|
132
|
+
(0, runtime_1.emitNervesEvent)({
|
|
133
|
+
component: "rsvp",
|
|
134
|
+
event: "rsvp.snapshot_built",
|
|
135
|
+
message: "built RSVP snapshot",
|
|
136
|
+
meta: {
|
|
137
|
+
agent: snapshot.agent,
|
|
138
|
+
snapshotId: snapshot.snapshotId,
|
|
139
|
+
total: snapshot.summary.total,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
return snapshot;
|
|
143
|
+
}
|
|
144
|
+
function isRecord(value) {
|
|
145
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
146
|
+
}
|
|
147
|
+
function parseGuest(value) {
|
|
148
|
+
if (!isRecord(value))
|
|
149
|
+
return null;
|
|
150
|
+
if (typeof value.id !== "string")
|
|
151
|
+
return null;
|
|
152
|
+
if (typeof value.firstName !== "string")
|
|
153
|
+
return null;
|
|
154
|
+
if (typeof value.lastName !== "string")
|
|
155
|
+
return null;
|
|
156
|
+
if (typeof value.displayName !== "string")
|
|
157
|
+
return null;
|
|
158
|
+
if (!(typeof value.groupId === "string" || typeof value.groupId === "number" || value.groupId === null))
|
|
159
|
+
return null;
|
|
160
|
+
if (value.status !== "attending" && value.status !== "declined" && value.status !== "pending" && value.status !== "unknown")
|
|
161
|
+
return null;
|
|
162
|
+
if (!(typeof value.sourceStatus === "string" || value.sourceStatus === null))
|
|
163
|
+
return null;
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
function parseSummary(value) {
|
|
167
|
+
if (!isRecord(value))
|
|
168
|
+
return null;
|
|
169
|
+
const summary = value;
|
|
170
|
+
return typeof summary.attending === "number"
|
|
171
|
+
&& typeof summary.declined === "number"
|
|
172
|
+
&& typeof summary.pending === "number"
|
|
173
|
+
&& typeof summary.unknown === "number"
|
|
174
|
+
&& typeof summary.total === "number"
|
|
175
|
+
? summary
|
|
176
|
+
: null;
|
|
177
|
+
}
|
|
178
|
+
function parseRsvpSnapshot(value) {
|
|
179
|
+
if (!isRecord(value))
|
|
180
|
+
return { ok: false, reason: "snapshot must be an object" };
|
|
181
|
+
if (value.schemaVersion !== 1)
|
|
182
|
+
return { ok: false, reason: "unsupported schemaVersion" };
|
|
183
|
+
if (value.policyVersion !== exports.RSVP_SNAPSHOT_POLICY_VERSION)
|
|
184
|
+
return { ok: false, reason: "unsupported policyVersion" };
|
|
185
|
+
if (typeof value.snapshotId !== "string")
|
|
186
|
+
return { ok: false, reason: "missing snapshotId" };
|
|
187
|
+
if (typeof value.agent !== "string")
|
|
188
|
+
return { ok: false, reason: "missing agent" };
|
|
189
|
+
if (typeof value.fetchedAt !== "string" || !Number.isFinite(Date.parse(value.fetchedAt)))
|
|
190
|
+
return { ok: false, reason: "invalid fetchedAt" };
|
|
191
|
+
if (!isRecord(value.source))
|
|
192
|
+
return { ok: false, reason: "invalid source" };
|
|
193
|
+
if (!isRecord(value.provenance))
|
|
194
|
+
return { ok: false, reason: "invalid provenance" };
|
|
195
|
+
if (!Array.isArray(value.guests))
|
|
196
|
+
return { ok: false, reason: "guests must be an array" };
|
|
197
|
+
const guests = value.guests.map(parseGuest);
|
|
198
|
+
if (guests.some((guest) => guest === null))
|
|
199
|
+
return { ok: false, reason: "invalid guest row" };
|
|
200
|
+
const ids = guests.map((guest) => guest.id);
|
|
201
|
+
if (new Set(ids).size !== ids.length)
|
|
202
|
+
return { ok: false, reason: "duplicate guest id" };
|
|
203
|
+
if (typeof value.allGuestIdsHash !== "string")
|
|
204
|
+
return { ok: false, reason: "missing allGuestIdsHash" };
|
|
205
|
+
const summary = parseSummary(value.summary);
|
|
206
|
+
if (!summary)
|
|
207
|
+
return { ok: false, reason: "invalid summary" };
|
|
208
|
+
if (!isRecord(value.privacy) || value.privacy.rawCredentialsStored !== false)
|
|
209
|
+
return { ok: false, reason: "invalid privacy" };
|
|
210
|
+
const snapshot = value;
|
|
211
|
+
if (typeof value.contentHash !== "string" || rsvpSnapshotContentHash(snapshot) !== value.contentHash) {
|
|
212
|
+
return { ok: false, reason: "contentHash mismatch" };
|
|
213
|
+
}
|
|
214
|
+
if (snapshotIdFor(value.contentHash) !== value.snapshotId)
|
|
215
|
+
return { ok: false, reason: "snapshotId mismatch" };
|
|
216
|
+
return { ok: true, snapshot };
|
|
217
|
+
}
|
|
218
|
+
function serializeRsvpSnapshotMetadata(snapshot) {
|
|
219
|
+
return {
|
|
220
|
+
schemaVersion: 1,
|
|
221
|
+
policyVersion: exports.RSVP_SNAPSHOT_POLICY_VERSION,
|
|
222
|
+
snapshotId: snapshot.snapshotId,
|
|
223
|
+
contentHash: snapshot.contentHash,
|
|
224
|
+
agent: snapshot.agent,
|
|
225
|
+
fetchedAt: snapshot.fetchedAt,
|
|
226
|
+
source: snapshot.source,
|
|
227
|
+
summary: snapshot.summary,
|
|
228
|
+
guestIdsHash: rsvpHash(snapshot.guests.map((guest) => guest.id).sort()),
|
|
229
|
+
rawGuestNamesStored: false,
|
|
230
|
+
indexPolicy: { search: false, vector: false },
|
|
231
|
+
};
|
|
232
|
+
}
|
|
@@ -273,7 +273,7 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
|
|
|
273
273
|
const url = buildBlueBubblesApiUrl(config.serverUrl, "/api/v1/message/text", config.password);
|
|
274
274
|
const body = {
|
|
275
275
|
chatGuid: resolvedChatGuid,
|
|
276
|
-
tempGuid: (0, node_crypto_1.randomUUID)(),
|
|
276
|
+
tempGuid: params.tempGuid?.trim() || (0, node_crypto_1.randomUUID)(),
|
|
277
277
|
message: trimmedText,
|
|
278
278
|
};
|
|
279
279
|
if (params.replyToMessageGuid?.trim()) {
|
|
@@ -425,15 +425,21 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
|
|
|
425
425
|
message: "querying recent bluebubbles messages",
|
|
426
426
|
meta: { limit, offset },
|
|
427
427
|
});
|
|
428
|
+
const body = {
|
|
429
|
+
limit,
|
|
430
|
+
offset,
|
|
431
|
+
sort: "DESC",
|
|
432
|
+
...(params.chatGuid?.trim() ? { chatGuid: params.chatGuid.trim() } : {}),
|
|
433
|
+
...(params.chatIdentifier?.trim() ? { chatIdentifier: params.chatIdentifier.trim() } : {}),
|
|
434
|
+
...(typeof params.beforeTimestamp === "number" && Number.isFinite(params.beforeTimestamp)
|
|
435
|
+
? { beforeTimestamp: params.beforeTimestamp }
|
|
436
|
+
: {}),
|
|
437
|
+
with: ["chats", "attachments", "payloadData", "messageSummaryInfo"],
|
|
438
|
+
};
|
|
428
439
|
const response = await fetch(url, {
|
|
429
440
|
method: "POST",
|
|
430
441
|
headers: { "Content-Type": "application/json" },
|
|
431
|
-
body: JSON.stringify(
|
|
432
|
-
limit,
|
|
433
|
-
offset,
|
|
434
|
-
sort: "DESC",
|
|
435
|
-
with: ["chats", "attachments", "payloadData", "messageSummaryInfo"],
|
|
436
|
-
}),
|
|
442
|
+
body: JSON.stringify(body),
|
|
437
443
|
signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
|
|
438
444
|
});
|
|
439
445
|
if (!response.ok) {
|
|
@@ -59,6 +59,7 @@ exports.startBlueBubblesApp = startBlueBubblesApp;
|
|
|
59
59
|
const fs = __importStar(require("node:fs"));
|
|
60
60
|
const http = __importStar(require("node:http"));
|
|
61
61
|
const path = __importStar(require("node:path"));
|
|
62
|
+
const node_crypto_1 = require("node:crypto");
|
|
62
63
|
const core_1 = require("../../heart/core");
|
|
63
64
|
const config_1 = require("../../heart/config");
|
|
64
65
|
const identity_1 = require("../../heart/identity");
|
|
@@ -83,6 +84,9 @@ const session_cleanup_1 = require("./session-cleanup");
|
|
|
83
84
|
const active_turns_1 = require("./active-turns");
|
|
84
85
|
const trust_gate_1 = require("../trust-gate");
|
|
85
86
|
const pipeline_1 = require("../pipeline");
|
|
87
|
+
const context_packets_1 = require("../context-packets");
|
|
88
|
+
const context_packet_ledger_1 = require("../context-packet-ledger");
|
|
89
|
+
const autonomy_budget_1 = require("../../heart/autonomy-budget");
|
|
86
90
|
const bbFailoverStates = new Map();
|
|
87
91
|
/**
|
|
88
92
|
* In-flight message tracker.
|
|
@@ -259,6 +263,8 @@ const BLUEBUBBLES_LIVE_TURN_STALLED_MS = 90_000;
|
|
|
259
263
|
const BLUEBUBBLES_SILENCE_WATCHDOG_MS = 75_000;
|
|
260
264
|
const BLUEBUBBLES_CALLBACK_CLEANUP_TIMEOUT_MS = 2_000;
|
|
261
265
|
const BLUEBUBBLES_ACTIVITY_OPERATION_TIMEOUT_MS = 20_000;
|
|
266
|
+
const BLUEBUBBLES_CONTEXT_PACKET_LIMIT = 40;
|
|
267
|
+
const BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS = 48 * 60 * 60 * 1000;
|
|
262
268
|
const BLUEBUBBLES_CATCHUP_PAGE_SIZE = 50;
|
|
263
269
|
const BLUEBUBBLES_CATCHUP_MAX_PAGES = 20;
|
|
264
270
|
const BLUEBUBBLES_HEALTHY_CATCHUP_OVERLAP_MS = 90_000;
|
|
@@ -558,6 +564,174 @@ function getBlueBubblesContinuityIngressTexts(event) {
|
|
|
558
564
|
.join("\n");
|
|
559
565
|
return fallbackText ? [fallbackText] : [];
|
|
560
566
|
}
|
|
567
|
+
function sha256Hex(input) {
|
|
568
|
+
return (0, node_crypto_1.createHash)("sha256").update(input).digest("hex");
|
|
569
|
+
}
|
|
570
|
+
function timestampIsoForAutonomy(event) {
|
|
571
|
+
return Number.isFinite(event.timestamp)
|
|
572
|
+
? new Date(event.timestamp).toISOString()
|
|
573
|
+
: new Date().toISOString();
|
|
574
|
+
}
|
|
575
|
+
function blueBubblesAutonomyTarget(event) {
|
|
576
|
+
return {
|
|
577
|
+
messageGuid: event.messageGuid,
|
|
578
|
+
sessionKeyHash: sha256Hex(event.chat.sessionKey),
|
|
579
|
+
chatGuidHash: event.chat.chatGuid ? sha256Hex(event.chat.chatGuid) : undefined,
|
|
580
|
+
chatIdentifierHash: event.chat.chatIdentifier ? sha256Hex(event.chat.chatIdentifier) : undefined,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
function blueBubblesRecoveryStormInput(agentName, event, source) {
|
|
584
|
+
return {
|
|
585
|
+
agent: agentName,
|
|
586
|
+
triggerType: "recovery",
|
|
587
|
+
sourceKind: "sense",
|
|
588
|
+
senseOrHabit: "bluebubbles",
|
|
589
|
+
provider: "bluebubbles-recovery",
|
|
590
|
+
target: blueBubblesAutonomyTarget(event),
|
|
591
|
+
normalizedErrorName: "BlueBubblesRecoveryError",
|
|
592
|
+
normalizedErrorCode: "RECOVERY_FAILED",
|
|
593
|
+
codeLocation: `senses/bluebubbles/${source}`,
|
|
594
|
+
idempotencyBucket: `bluebubbles-recovery:${source}`,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
function shouldUseBlueBubblesRecoveryBudget(source, options) {
|
|
598
|
+
return options.autonomyBudgetTrigger === "recovery"
|
|
599
|
+
|| source === "mutation-recovery"
|
|
600
|
+
|| source === "upstream-catchup"
|
|
601
|
+
|| source === "recovery-bootstrap";
|
|
602
|
+
}
|
|
603
|
+
function shouldCountBlueBubblesRecoveryResultAsSkipped(reason) {
|
|
604
|
+
return reason === "already_processed" || reason === "autonomy_budget_blocked";
|
|
605
|
+
}
|
|
606
|
+
function recordBlueBubblesRecoveryFailureForBudget(agentName, event, source) {
|
|
607
|
+
try {
|
|
608
|
+
(0, autonomy_budget_1.recordAutonomyFailure)((0, identity_1.getAgentRoot)(agentName), {
|
|
609
|
+
...blueBubblesRecoveryStormInput(agentName, event, source),
|
|
610
|
+
occurredAt: timestampIsoForAutonomy(event),
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
// Autonomy failure accounting must not turn a recovery error into a broader sense outage.
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function chatKeyForContext(event) {
|
|
618
|
+
return event.chat.chatGuid ?? event.chat.chatIdentifier ?? event.chat.sessionKey;
|
|
619
|
+
}
|
|
620
|
+
function isSameBlueBubblesChat(candidate, anchor) {
|
|
621
|
+
return candidate.chat.sessionKey === anchor.chat.sessionKey
|
|
622
|
+
|| (!!candidate.chat.chatGuid && candidate.chat.chatGuid === anchor.chat.chatGuid)
|
|
623
|
+
|| (!!candidate.chat.chatIdentifier && candidate.chat.chatIdentifier === anchor.chat.chatIdentifier);
|
|
624
|
+
}
|
|
625
|
+
function contextAuthorLabel(event) {
|
|
626
|
+
if (event.fromMe)
|
|
627
|
+
return "Slugger";
|
|
628
|
+
return event.sender.displayName || event.sender.externalId || "Unknown";
|
|
629
|
+
}
|
|
630
|
+
function messageToContextInput(event, chatGuidHash) {
|
|
631
|
+
return {
|
|
632
|
+
timestamp: new Date(event.timestamp).toISOString(),
|
|
633
|
+
authorLabel: contextAuthorLabel(event),
|
|
634
|
+
body: event.textForAgent || event.text,
|
|
635
|
+
sourceRef: {
|
|
636
|
+
sense: "bluebubbles",
|
|
637
|
+
adapter: "bluebubbles-api-v1",
|
|
638
|
+
service: "imessage",
|
|
639
|
+
chatGuid: event.chat.chatGuid,
|
|
640
|
+
chatGuidHash,
|
|
641
|
+
messageGuid: event.messageGuid,
|
|
642
|
+
senderExternalIdHash: sha256Hex(event.sender.externalId || event.sender.rawId || "unknown"),
|
|
643
|
+
observedAt: new Date(event.timestamp).toISOString(),
|
|
644
|
+
},
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function insertEphemeralContextMessages(messages, contextMessages) {
|
|
648
|
+
if (contextMessages.length === 0)
|
|
649
|
+
return messages;
|
|
650
|
+
const insertionIndex = Math.max(0, messages.length - 1);
|
|
651
|
+
return [
|
|
652
|
+
...messages.slice(0, insertionIndex),
|
|
653
|
+
...contextMessages,
|
|
654
|
+
...messages.slice(insertionIndex),
|
|
655
|
+
];
|
|
656
|
+
}
|
|
657
|
+
async function buildBlueBubblesContextMessages(input) {
|
|
658
|
+
if (input.event.kind !== "message")
|
|
659
|
+
return [];
|
|
660
|
+
const event = input.event;
|
|
661
|
+
if (!Number.isFinite(event.timestamp))
|
|
662
|
+
return [];
|
|
663
|
+
const chatKey = chatKeyForContext(event);
|
|
664
|
+
const chatGuidHash = sha256Hex(chatKey);
|
|
665
|
+
const anchorTimestamp = new Date(event.timestamp).toISOString();
|
|
666
|
+
try {
|
|
667
|
+
const query = {
|
|
668
|
+
beforeTimestamp: event.timestamp,
|
|
669
|
+
limit: BLUEBUBBLES_CONTEXT_PACKET_LIMIT,
|
|
670
|
+
offset: 0,
|
|
671
|
+
...(event.chat.chatGuid ? { chatGuid: event.chat.chatGuid } : {}),
|
|
672
|
+
...(!event.chat.chatGuid && event.chat.chatIdentifier ? { chatIdentifier: event.chat.chatIdentifier } : {}),
|
|
673
|
+
};
|
|
674
|
+
const candidates = input.client.listRecentMessages
|
|
675
|
+
? await input.client.listRecentMessages(query)
|
|
676
|
+
: [];
|
|
677
|
+
const history = candidates
|
|
678
|
+
.filter((candidate) => candidate.kind === "message")
|
|
679
|
+
.filter((candidate) => isSameBlueBubblesChat(candidate, event))
|
|
680
|
+
.filter((candidate) => candidate.messageGuid !== event.messageGuid)
|
|
681
|
+
.filter((candidate) => candidate.timestamp <= event.timestamp)
|
|
682
|
+
.filter((candidate) => event.timestamp - candidate.timestamp <= BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS);
|
|
683
|
+
if (history.length === 0)
|
|
684
|
+
return [];
|
|
685
|
+
const packet = (0, context_packets_1.buildSenseContextPacket)({
|
|
686
|
+
agent: input.agentName,
|
|
687
|
+
sense: "bluebubbles",
|
|
688
|
+
sessionKey: event.chat.sessionKey,
|
|
689
|
+
chatKeyHash: chatGuidHash,
|
|
690
|
+
anchorMessageGuid: event.messageGuid,
|
|
691
|
+
anchorTimestamp,
|
|
692
|
+
windowBeforeMessages: BLUEBUBBLES_CONTEXT_PACKET_LIMIT,
|
|
693
|
+
windowBeforeMs: BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS,
|
|
694
|
+
messages: history.map((candidate) => messageToContextInput(candidate, chatGuidHash)),
|
|
695
|
+
});
|
|
696
|
+
(0, context_packet_ledger_1.writeSenseContextPacket)(input.agentRoot, packet);
|
|
697
|
+
const rendered = (0, context_packets_1.renderSenseContextPacketForPrompt)(packet, {
|
|
698
|
+
redactionPatterns: [/password=[^\s]+/gi],
|
|
699
|
+
});
|
|
700
|
+
(0, runtime_1.emitNervesEvent)({
|
|
701
|
+
component: "senses",
|
|
702
|
+
event: "senses.bluebubbles_context_packet_injected",
|
|
703
|
+
message: "injected bluebubbles same-chat context packet",
|
|
704
|
+
meta: {
|
|
705
|
+
packetId: packet.packetId,
|
|
706
|
+
messageGuid: event.messageGuid,
|
|
707
|
+
contextMessages: history.length,
|
|
708
|
+
},
|
|
709
|
+
});
|
|
710
|
+
return [{ role: "system", content: rendered.text }];
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
(0, runtime_1.emitNervesEvent)({
|
|
714
|
+
level: "warn",
|
|
715
|
+
component: "senses",
|
|
716
|
+
event: "senses.bluebubbles_context_packet_error",
|
|
717
|
+
message: "failed to build live bluebubbles context packet",
|
|
718
|
+
meta: {
|
|
719
|
+
messageGuid: event.messageGuid,
|
|
720
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
721
|
+
},
|
|
722
|
+
});
|
|
723
|
+
const fallback = (0, context_packet_ledger_1.readLatestVisibleSenseContextPacket)(input.agentRoot, {
|
|
724
|
+
sense: "bluebubbles",
|
|
725
|
+
chatKeyHash: chatGuidHash,
|
|
726
|
+
beforeAnchorTimestamp: anchorTimestamp,
|
|
727
|
+
maxAgeMs: 24 * 60 * 60 * 1000,
|
|
728
|
+
});
|
|
729
|
+
if (!fallback)
|
|
730
|
+
return [];
|
|
731
|
+
const rendered = (0, context_packets_1.renderSenseContextPacketForPrompt)(fallback);
|
|
732
|
+
return [{ role: "system", content: rendered.text }];
|
|
733
|
+
}
|
|
734
|
+
}
|
|
561
735
|
function createReplyTargetController(event) {
|
|
562
736
|
const defaultTargetLabel = event.kind === "message" && event.threadOriginatorGuid?.trim() ? "current_lane" : "top_level";
|
|
563
737
|
let selection = event.kind === "message" && event.threadOriginatorGuid?.trim()
|
|
@@ -1164,6 +1338,23 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1164
1338
|
}
|
|
1165
1339
|
ownsInFlightMessage = true;
|
|
1166
1340
|
}
|
|
1341
|
+
if (event.kind === "message" && shouldUseBlueBubblesRecoveryBudget(source, options)) {
|
|
1342
|
+
const decision = (0, autonomy_budget_1.reserveAutonomyBudget)((0, identity_1.getAgentRoot)(agentName), {
|
|
1343
|
+
agent: agentName,
|
|
1344
|
+
triggerType: "recovery",
|
|
1345
|
+
sourceKind: "sense",
|
|
1346
|
+
senseOrHabit: "bluebubbles",
|
|
1347
|
+
target: blueBubblesAutonomyTarget(event),
|
|
1348
|
+
idempotencyKey: `bluebubbles-recovery:${source}:${event.messageGuid}`,
|
|
1349
|
+
now: timestampIsoForAutonomy(event),
|
|
1350
|
+
storm: blueBubblesRecoveryStormInput(agentName, event, source),
|
|
1351
|
+
});
|
|
1352
|
+
if (!decision.allowed) {
|
|
1353
|
+
endBlueBubblesMessageInFlight(inFlightKey.sessionKey, inFlightKey.messageGuid);
|
|
1354
|
+
ownsInFlightMessage = false;
|
|
1355
|
+
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "autonomy_budget_blocked" };
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1167
1358
|
try {
|
|
1168
1359
|
// ── Adapter setup: friend, session, content, callbacks ──────────
|
|
1169
1360
|
const store = resolvedDeps.createFriendStore();
|
|
@@ -1359,27 +1550,35 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
|
|
|
1359
1550
|
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
1360
1551
|
drainPending: pending_1.drainPending,
|
|
1361
1552
|
drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)(resolvedDeps.getAgentName(), deferredFriendId),
|
|
1362
|
-
runAgent: (msgs, cb, channel, sig, opts) =>
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1553
|
+
runAgent: async (msgs, cb, channel, sig, opts) => {
|
|
1554
|
+
const contextMessages = await buildBlueBubblesContextMessages({
|
|
1555
|
+
agentName,
|
|
1556
|
+
agentRoot: (0, identity_1.getAgentRoot)(),
|
|
1557
|
+
client,
|
|
1558
|
+
event,
|
|
1559
|
+
});
|
|
1560
|
+
return resolvedDeps.runAgent(insertEphemeralContextMessages(msgs, contextMessages), cb, channel, sig, {
|
|
1561
|
+
...opts,
|
|
1562
|
+
toolContext: {
|
|
1563
|
+
/* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
|
|
1564
|
+
signin: async () => undefined,
|
|
1565
|
+
...opts?.toolContext,
|
|
1566
|
+
summarize,
|
|
1567
|
+
bluebubblesReplyTarget: {
|
|
1568
|
+
setSelection: (selection) => replyTarget.setSelection(selection),
|
|
1569
|
+
},
|
|
1570
|
+
codingFeedback: {
|
|
1571
|
+
send: async (message) => {
|
|
1572
|
+
await client.sendText({
|
|
1573
|
+
chat: event.chat,
|
|
1574
|
+
text: message,
|
|
1575
|
+
replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
|
|
1576
|
+
});
|
|
1577
|
+
},
|
|
1379
1578
|
},
|
|
1380
1579
|
},
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1580
|
+
});
|
|
1581
|
+
},
|
|
1383
1582
|
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
1384
1583
|
const prepared = resolvedDeps.postTurnTrim(turnMessages, usage, hooks);
|
|
1385
1584
|
resolvedDeps.deferPostTurnPersist(sessionPathArg, prepared, usage, state);
|
|
@@ -1970,8 +2169,9 @@ async function catchUpMissedBlueBubblesMessages(deps = {}, previousState, option
|
|
|
1970
2169
|
}
|
|
1971
2170
|
const handled = await handleBlueBubblesNormalizedEvent(repaired, resolvedDeps, "upstream-catchup", {
|
|
1972
2171
|
timeoutMs: BLUEBUBBLES_RECOVERY_TURN_TIMEOUT_MS,
|
|
2172
|
+
autonomyBudgetTrigger: "recovery",
|
|
1973
2173
|
});
|
|
1974
|
-
if (handled.reason
|
|
2174
|
+
if (shouldCountBlueBubblesRecoveryResultAsSkipped(handled.reason)) {
|
|
1975
2175
|
result.skipped++;
|
|
1976
2176
|
}
|
|
1977
2177
|
else {
|
|
@@ -1980,6 +2180,7 @@ async function catchUpMissedBlueBubblesMessages(deps = {}, previousState, option
|
|
|
1980
2180
|
}
|
|
1981
2181
|
}
|
|
1982
2182
|
catch (error) {
|
|
2183
|
+
recordBlueBubblesRecoveryFailureForBudget(agentName, event, "upstream-catchup");
|
|
1983
2184
|
result.failed++;
|
|
1984
2185
|
(0, runtime_1.emitNervesEvent)({
|
|
1985
2186
|
level: "warn",
|
|
@@ -2066,8 +2267,9 @@ async function recoverCapturedBlueBubblesInboundMessages(deps = {}) {
|
|
|
2066
2267
|
}
|
|
2067
2268
|
const handled = await handleBlueBubblesNormalizedEvent(repaired, resolvedDeps, entry.source, {
|
|
2068
2269
|
timeoutMs: BLUEBUBBLES_RECOVERY_TURN_TIMEOUT_MS,
|
|
2270
|
+
autonomyBudgetTrigger: "recovery",
|
|
2069
2271
|
});
|
|
2070
|
-
if (handled.reason
|
|
2272
|
+
if (shouldCountBlueBubblesRecoveryResultAsSkipped(handled.reason)) {
|
|
2071
2273
|
result.skipped++;
|
|
2072
2274
|
}
|
|
2073
2275
|
else {
|
|
@@ -2075,6 +2277,7 @@ async function recoverCapturedBlueBubblesInboundMessages(deps = {}) {
|
|
|
2075
2277
|
}
|
|
2076
2278
|
}
|
|
2077
2279
|
catch (error) {
|
|
2280
|
+
recordBlueBubblesRecoveryFailureForBudget(agentName, inboundEntryToRecoveryEvent(entry), entry.source);
|
|
2078
2281
|
result.failed++;
|
|
2079
2282
|
(0, runtime_1.emitNervesEvent)({
|
|
2080
2283
|
level: "warn",
|
|
@@ -2111,8 +2314,9 @@ async function recoverMissedBlueBubblesMessages(deps = {}) {
|
|
|
2111
2314
|
}
|
|
2112
2315
|
const handled = await handleBlueBubblesNormalizedEvent(repaired, resolvedDeps, "mutation-recovery", {
|
|
2113
2316
|
timeoutMs: BLUEBUBBLES_RECOVERY_TURN_TIMEOUT_MS,
|
|
2317
|
+
autonomyBudgetTrigger: "recovery",
|
|
2114
2318
|
});
|
|
2115
|
-
if (handled.reason
|
|
2319
|
+
if (shouldCountBlueBubblesRecoveryResultAsSkipped(handled.reason)) {
|
|
2116
2320
|
result.skipped++;
|
|
2117
2321
|
}
|
|
2118
2322
|
else {
|
|
@@ -2120,6 +2324,7 @@ async function recoverMissedBlueBubblesMessages(deps = {}) {
|
|
|
2120
2324
|
}
|
|
2121
2325
|
}
|
|
2122
2326
|
catch (error) {
|
|
2327
|
+
recordBlueBubblesRecoveryFailureForBudget(agentName, mutationEntryToEvent(candidate), "mutation-recovery");
|
|
2123
2328
|
result.failed++;
|
|
2124
2329
|
(0, runtime_1.emitNervesEvent)({
|
|
2125
2330
|
level: "warn",
|