@ouro.bot/cli 0.1.0-alpha.723 → 0.1.0-alpha.725
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 +295 -275
- package/dist/heart/active-work.js +10 -3
- package/dist/heart/core.js +343 -77
- package/dist/heart/daemon/cli-exec.js +132 -15
- 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 +82 -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/mail.js +129 -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
|
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.RSVP_OUTBOUND_STATE_POLICY_VERSION = void 0;
|
|
37
|
+
exports.classifyRsvpSendBoundary = classifyRsvpSendBoundary;
|
|
38
|
+
exports.executeRsvpSendBoundary = executeRsvpSendBoundary;
|
|
37
39
|
exports.readRsvpOutboundState = readRsvpOutboundState;
|
|
38
40
|
exports.ensureRsvpOutboundState = ensureRsvpOutboundState;
|
|
39
41
|
exports.writeRsvpBaseline = writeRsvpBaseline;
|
|
@@ -42,11 +44,369 @@ exports.recordRsvpOutboundAttempt = recordRsvpOutboundAttempt;
|
|
|
42
44
|
const fs = __importStar(require("node:fs"));
|
|
43
45
|
const path = __importStar(require("node:path"));
|
|
44
46
|
const node_crypto_1 = require("node:crypto");
|
|
47
|
+
const habit_lifecycle_1 = require("../heart/habits/habit-lifecycle");
|
|
48
|
+
const habit_parser_1 = require("../heart/habits/habit-parser");
|
|
45
49
|
const runtime_1 = require("../nerves/runtime");
|
|
46
50
|
exports.RSVP_OUTBOUND_STATE_POLICY_VERSION = "rsvp-outbound-state/v1";
|
|
51
|
+
const AMBIGUOUS_HTTP_STATUSES = new Set([408, 409, 425, 429]);
|
|
52
|
+
const retainedRsvpSendLeases = new Map();
|
|
53
|
+
function classifyRsvpSendBoundary(observation) {
|
|
54
|
+
if (!observation.transportInvoked)
|
|
55
|
+
return "not_crossed";
|
|
56
|
+
const messageGuid = observation.messageGuid?.trim() ?? "";
|
|
57
|
+
if (observation.httpStatus !== null
|
|
58
|
+
&& observation.httpStatus >= 200
|
|
59
|
+
&& observation.httpStatus <= 299
|
|
60
|
+
&& messageGuid.length > 0)
|
|
61
|
+
return "crossed";
|
|
62
|
+
if (observation.httpStatus !== null
|
|
63
|
+
&& observation.httpStatus >= 400
|
|
64
|
+
&& observation.httpStatus <= 499
|
|
65
|
+
&& !AMBIGUOUS_HTTP_STATUSES.has(observation.httpStatus))
|
|
66
|
+
return "not_crossed";
|
|
67
|
+
return "crossing_unknown";
|
|
68
|
+
}
|
|
47
69
|
function sha256Hex(input) {
|
|
48
70
|
return (0, node_crypto_1.createHash)("sha256").update(input).digest("hex");
|
|
49
71
|
}
|
|
72
|
+
function lifecycleNowIso(deps) {
|
|
73
|
+
return (deps.now?.() ?? new Date()).toISOString();
|
|
74
|
+
}
|
|
75
|
+
function lifecycleErrorCode(error, fallback) {
|
|
76
|
+
return error instanceof habit_lifecycle_1.HabitLifecycleError ? error.code : fallback;
|
|
77
|
+
}
|
|
78
|
+
function emitBoundaryTelemetry(event) {
|
|
79
|
+
try {
|
|
80
|
+
(0, runtime_1.emitNervesEvent)(event);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Observability must never mutate or obscure the durable transport boundary.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function failedBoundaryResult(operationId, errorCode, options = {}) {
|
|
87
|
+
const boundaryState = options.boundaryState ?? "not_crossed";
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
operationId,
|
|
91
|
+
boundaryState,
|
|
92
|
+
transportInvoked: options.transportInvoked ?? false,
|
|
93
|
+
replayed: options.replayed ?? false,
|
|
94
|
+
transportResult: {
|
|
95
|
+
httpStatus: null,
|
|
96
|
+
messageGuid: null,
|
|
97
|
+
errorCode,
|
|
98
|
+
},
|
|
99
|
+
errorCode,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function currentHabitStatus(agentRoot, habitId, deps) {
|
|
103
|
+
const definitionPath = path.join(agentRoot, "habits", `${habitId}.md`);
|
|
104
|
+
try {
|
|
105
|
+
return (0, habit_parser_1.parseHabitFile)((deps.fs ?? fs).readFileSync(definitionPath, "utf8"), definitionPath).status;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
return (0, habit_parser_1.createDegradedHabitFile)(definitionPath, "read_error", "", String(error)).status;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function cancellationFencesSend(agentRoot, habitId, deps) {
|
|
112
|
+
return (0, habit_lifecycle_1.listHabitLifecycleJournals)({ agentRoot, habitId }, deps).some((journal) => (journal.operationKind === "cancel" && journal.generation >= 1));
|
|
113
|
+
}
|
|
114
|
+
function terminalBoundaryResult(journal, replayed, errorCode) {
|
|
115
|
+
const boundaryState = journal.boundaryState;
|
|
116
|
+
const transportResult = journal.transportResult;
|
|
117
|
+
const failureCode = errorCode ?? transportResult.errorCode ?? undefined;
|
|
118
|
+
return {
|
|
119
|
+
ok: boundaryState === "crossed",
|
|
120
|
+
operationId: journal.operationId,
|
|
121
|
+
boundaryState,
|
|
122
|
+
transportInvoked: journal.transportInvokedAt !== null,
|
|
123
|
+
replayed,
|
|
124
|
+
transportResult,
|
|
125
|
+
...(boundaryState === "crossed" || failureCode === undefined ? {} : { errorCode: failureCode }),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function emitBoundaryClassified(habitId, operationId, boundaryState, transportInvoked) {
|
|
129
|
+
emitBoundaryTelemetry({
|
|
130
|
+
component: "rsvp",
|
|
131
|
+
event: "habit_send_boundary_classified",
|
|
132
|
+
message: "classified RSVP habit send boundary",
|
|
133
|
+
meta: { habitId, operationId, boundaryState, transportInvoked },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function persistBoundaryClassification(input) {
|
|
137
|
+
const boundaryState = classifyRsvpSendBoundary(input.observation);
|
|
138
|
+
const classifiedAt = lifecycleNowIso(input.deps);
|
|
139
|
+
let terminal;
|
|
140
|
+
let resultErrorCode = input.errorCode;
|
|
141
|
+
try {
|
|
142
|
+
terminal = (0, habit_lifecycle_1.transitionHabitLifecycleJournal)(input.intent, {
|
|
143
|
+
state: boundaryState,
|
|
144
|
+
at: classifiedAt,
|
|
145
|
+
transportInvokedAt: input.observation.transportInvoked ? input.transportInvokedAt : null,
|
|
146
|
+
transportResult: {
|
|
147
|
+
httpStatus: input.observation.httpStatus,
|
|
148
|
+
messageGuid: input.observation.messageGuid,
|
|
149
|
+
errorCode: input.observation.errorCode,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
(0, habit_lifecycle_1.writeHabitLifecycleJournal)(input.lease, terminal, input.deps);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
const fallbackError = "classification_durability_unknown";
|
|
156
|
+
const fallbackBoundaryState = input.observation.transportInvoked
|
|
157
|
+
? "crossing_unknown"
|
|
158
|
+
: "not_crossed";
|
|
159
|
+
terminal = (0, habit_lifecycle_1.transitionHabitLifecycleJournal)(input.intent, {
|
|
160
|
+
state: fallbackBoundaryState,
|
|
161
|
+
at: classifiedAt,
|
|
162
|
+
transportInvokedAt: input.observation.transportInvoked
|
|
163
|
+
? input.transportInvokedAt ?? input.intent.intentAt
|
|
164
|
+
: null,
|
|
165
|
+
transportResult: {
|
|
166
|
+
httpStatus: input.observation.httpStatus,
|
|
167
|
+
messageGuid: input.observation.messageGuid,
|
|
168
|
+
errorCode: fallbackError,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
(0, habit_lifecycle_1.writeHabitLifecycleJournal)(input.lease, terminal, input.deps);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// The caller still reports the only safe classification when terminal durability cannot be proven.
|
|
176
|
+
}
|
|
177
|
+
resultErrorCode = fallbackError;
|
|
178
|
+
}
|
|
179
|
+
emitBoundaryClassified(input.lease.habitId, input.lease.operationId, terminal.boundaryState, terminal.transportInvokedAt !== null);
|
|
180
|
+
return terminalBoundaryResult(terminal, input.replayed, resultErrorCode);
|
|
181
|
+
}
|
|
182
|
+
function rsvpSendLeaseKey(agentRoot, habitId, operationId) {
|
|
183
|
+
return JSON.stringify([path.resolve(agentRoot), habitId, operationId]);
|
|
184
|
+
}
|
|
185
|
+
async function releaseRsvpSendLease(leaseKey, lease, deps) {
|
|
186
|
+
const lifecycle = deps.lifecycle ?? {};
|
|
187
|
+
const release = deps.releaseLifecycleLock ?? habit_lifecycle_1.releaseHabitLifecycleLock;
|
|
188
|
+
const attempts = Math.floor(habit_lifecycle_1.HABIT_LIFECYCLE_TIMEOUT_MS / habit_lifecycle_1.HABIT_LIFECYCLE_POLL_MS) + 1;
|
|
189
|
+
const wait = lifecycle.sleep ?? ((milliseconds) => new Promise((resolve) => {
|
|
190
|
+
setTimeout(resolve, milliseconds);
|
|
191
|
+
}));
|
|
192
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
193
|
+
try {
|
|
194
|
+
if (release(lease, lifecycle)) {
|
|
195
|
+
retainedRsvpSendLeases.delete(leaseKey);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
retainedRsvpSendLeases.set(leaseKey, lease);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
if (attempt + 1 < attempts)
|
|
204
|
+
await wait(habit_lifecycle_1.HABIT_LIFECYCLE_POLL_MS);
|
|
205
|
+
}
|
|
206
|
+
retainedRsvpSendLeases.set(leaseKey, lease);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
function transportErrorObservation(error, transportInvoked) {
|
|
210
|
+
const record = error && typeof error === "object" ? error : {};
|
|
211
|
+
const declaredInvoked = record.transportInvoked === true;
|
|
212
|
+
const httpStatus = typeof record.httpStatus === "number" && Number.isInteger(record.httpStatus)
|
|
213
|
+
? record.httpStatus
|
|
214
|
+
: null;
|
|
215
|
+
const name = typeof record.name === "string" ? record.name : "";
|
|
216
|
+
const errorCode = typeof record.errorCode === "string" && record.errorCode.trim().length > 0
|
|
217
|
+
? record.errorCode.trim()
|
|
218
|
+
: name === "TimeoutError"
|
|
219
|
+
? "timeout"
|
|
220
|
+
: name === "AbortError"
|
|
221
|
+
? "abort"
|
|
222
|
+
: error instanceof TypeError
|
|
223
|
+
? "socket"
|
|
224
|
+
: transportInvoked || declaredInvoked
|
|
225
|
+
? "transport_error"
|
|
226
|
+
: "validation";
|
|
227
|
+
return {
|
|
228
|
+
transportInvoked: transportInvoked || declaredInvoked,
|
|
229
|
+
httpStatus,
|
|
230
|
+
messageGuid: null,
|
|
231
|
+
errorCode,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
async function executeRsvpSendBoundary(input, deps = {}) {
|
|
235
|
+
const lifecycle = deps.lifecycle ?? {};
|
|
236
|
+
const operation = (0, habit_lifecycle_1.buildHabitSendOperation)({
|
|
237
|
+
habitId: input.habitId,
|
|
238
|
+
outboundIdempotencyKey: input.outboundIdempotencyKey,
|
|
239
|
+
});
|
|
240
|
+
if (input.noSend)
|
|
241
|
+
return failedBoundaryResult(operation.operationId, "immutable_no_send");
|
|
242
|
+
const leaseKey = rsvpSendLeaseKey(input.agentRoot, input.habitId, operation.operationId);
|
|
243
|
+
let lease = retainedRsvpSendLeases.get(leaseKey);
|
|
244
|
+
try {
|
|
245
|
+
if (lease && !(0, habit_lifecycle_1.habitLifecycleLeaseIsCurrent)(lease, lifecycle)) {
|
|
246
|
+
retainedRsvpSendLeases.delete(leaseKey);
|
|
247
|
+
lease = undefined;
|
|
248
|
+
}
|
|
249
|
+
if (!lease) {
|
|
250
|
+
const lock = await (0, habit_lifecycle_1.acquireHabitLifecycleLock)({
|
|
251
|
+
agentRoot: input.agentRoot,
|
|
252
|
+
habitId: input.habitId,
|
|
253
|
+
operationId: operation.operationId,
|
|
254
|
+
}, lifecycle);
|
|
255
|
+
if (lock.status === "timeout") {
|
|
256
|
+
return failedBoundaryResult(operation.operationId, lock.error);
|
|
257
|
+
}
|
|
258
|
+
lease = lock.lease;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
return failedBoundaryResult(operation.operationId, lifecycleErrorCode(error, "lifecycle_lock_failed"));
|
|
263
|
+
}
|
|
264
|
+
const activeLease = lease;
|
|
265
|
+
let transportInvoked = false;
|
|
266
|
+
let transportInvokedAt = null;
|
|
267
|
+
let intent = null;
|
|
268
|
+
const markTransportInvoked = () => {
|
|
269
|
+
if (transportInvoked)
|
|
270
|
+
return;
|
|
271
|
+
transportInvoked = true;
|
|
272
|
+
transportInvokedAt = lifecycleNowIso(lifecycle);
|
|
273
|
+
emitBoundaryTelemetry({
|
|
274
|
+
component: "rsvp",
|
|
275
|
+
event: "habit_transport_invoked",
|
|
276
|
+
message: "invoked RSVP habit transport",
|
|
277
|
+
meta: { habitId: input.habitId, operationId: operation.operationId },
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
const boundaryResult = await (async () => {
|
|
281
|
+
try {
|
|
282
|
+
const existing = (0, habit_lifecycle_1.readHabitLifecycleJournal)({
|
|
283
|
+
agentRoot: input.agentRoot,
|
|
284
|
+
habitId: input.habitId,
|
|
285
|
+
operationId: operation.operationId,
|
|
286
|
+
}, lifecycle);
|
|
287
|
+
if (existing?.state === "not_crossed"
|
|
288
|
+
|| existing?.state === "crossing_unknown"
|
|
289
|
+
|| existing?.state === "crossed")
|
|
290
|
+
return terminalBoundaryResult(existing, true);
|
|
291
|
+
if (existing?.state === "send_intent") {
|
|
292
|
+
return persistBoundaryClassification({
|
|
293
|
+
lease: activeLease,
|
|
294
|
+
intent: existing,
|
|
295
|
+
observation: {
|
|
296
|
+
transportInvoked: true,
|
|
297
|
+
httpStatus: null,
|
|
298
|
+
messageGuid: null,
|
|
299
|
+
errorCode: "recovered_unclassified_send_intent",
|
|
300
|
+
},
|
|
301
|
+
transportInvokedAt: existing.intentAt,
|
|
302
|
+
replayed: true,
|
|
303
|
+
deps: lifecycle,
|
|
304
|
+
errorCode: "recovered_unclassified_send_intent",
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const status = currentHabitStatus(input.agentRoot, input.habitId, lifecycle);
|
|
308
|
+
if (status !== "active") {
|
|
309
|
+
return failedBoundaryResult(operation.operationId, `habit_status_${status}`);
|
|
310
|
+
}
|
|
311
|
+
if (cancellationFencesSend(input.agentRoot, input.habitId, lifecycle)) {
|
|
312
|
+
return failedBoundaryResult(operation.operationId, "cancellation_pending");
|
|
313
|
+
}
|
|
314
|
+
intent = existing ?? (0, habit_lifecycle_1.createHabitLifecycleJournal)({
|
|
315
|
+
habitId: input.habitId,
|
|
316
|
+
operationId: operation.operationId,
|
|
317
|
+
operationKind: "send",
|
|
318
|
+
updatedAt: lifecycleNowIso(lifecycle),
|
|
319
|
+
});
|
|
320
|
+
if (existing === null)
|
|
321
|
+
(0, habit_lifecycle_1.writeHabitLifecycleJournal)(activeLease, intent, lifecycle);
|
|
322
|
+
intent = (0, habit_lifecycle_1.transitionHabitLifecycleJournal)(intent, {
|
|
323
|
+
state: "send_intent",
|
|
324
|
+
at: lifecycleNowIso(lifecycle),
|
|
325
|
+
});
|
|
326
|
+
try {
|
|
327
|
+
(0, habit_lifecycle_1.writeHabitLifecycleJournal)(activeLease, intent, lifecycle);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
const visible = (0, habit_lifecycle_1.readHabitLifecycleJournal)({
|
|
331
|
+
agentRoot: input.agentRoot,
|
|
332
|
+
habitId: input.habitId,
|
|
333
|
+
operationId: operation.operationId,
|
|
334
|
+
}, lifecycle);
|
|
335
|
+
if (visible?.state === "send_intent") {
|
|
336
|
+
return persistBoundaryClassification({
|
|
337
|
+
lease: activeLease,
|
|
338
|
+
intent: visible,
|
|
339
|
+
observation: {
|
|
340
|
+
transportInvoked: false,
|
|
341
|
+
httpStatus: null,
|
|
342
|
+
messageGuid: null,
|
|
343
|
+
errorCode: lifecycleErrorCode(error, "lifecycle_write_failed"),
|
|
344
|
+
},
|
|
345
|
+
transportInvokedAt: null,
|
|
346
|
+
replayed: false,
|
|
347
|
+
deps: lifecycle,
|
|
348
|
+
errorCode: lifecycleErrorCode(error, "lifecycle_write_failed"),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
return failedBoundaryResult(operation.operationId, lifecycleErrorCode(error, "lifecycle_write_failed"));
|
|
352
|
+
}
|
|
353
|
+
emitBoundaryTelemetry({
|
|
354
|
+
component: "rsvp",
|
|
355
|
+
event: "habit_send_intent",
|
|
356
|
+
message: "recorded durable RSVP habit send intent",
|
|
357
|
+
meta: { habitId: input.habitId, operationId: operation.operationId },
|
|
358
|
+
});
|
|
359
|
+
let observation;
|
|
360
|
+
try {
|
|
361
|
+
const result = await input.invokeTransport(markTransportInvoked);
|
|
362
|
+
const messageGuid = result?.messageGuid?.trim() || null;
|
|
363
|
+
if (messageGuid !== null && !transportInvoked)
|
|
364
|
+
markTransportInvoked();
|
|
365
|
+
observation = {
|
|
366
|
+
transportInvoked,
|
|
367
|
+
httpStatus: typeof result?.httpStatus === "number" ? result.httpStatus : 200,
|
|
368
|
+
messageGuid,
|
|
369
|
+
errorCode: messageGuid === null ? "missing_message_guid" : null,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
observation = transportErrorObservation(error, transportInvoked);
|
|
374
|
+
if (observation.transportInvoked && !transportInvoked)
|
|
375
|
+
markTransportInvoked();
|
|
376
|
+
}
|
|
377
|
+
return persistBoundaryClassification({
|
|
378
|
+
lease: activeLease,
|
|
379
|
+
intent,
|
|
380
|
+
observation,
|
|
381
|
+
transportInvokedAt,
|
|
382
|
+
replayed: false,
|
|
383
|
+
deps: lifecycle,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
const errorCode = lifecycleErrorCode(error, "send_boundary_failed");
|
|
388
|
+
return failedBoundaryResult(operation.operationId, errorCode, {
|
|
389
|
+
transportInvoked,
|
|
390
|
+
boundaryState: transportInvoked ? "crossing_unknown" : "not_crossed",
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
})();
|
|
394
|
+
let released = false;
|
|
395
|
+
try {
|
|
396
|
+
released = await releaseRsvpSendLease(leaseKey, activeLease, deps);
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
retainedRsvpSendLeases.set(leaseKey, activeLease);
|
|
400
|
+
}
|
|
401
|
+
if (!released) {
|
|
402
|
+
return {
|
|
403
|
+
...boundaryResult,
|
|
404
|
+
ok: false,
|
|
405
|
+
errorCode: "lifecycle_lock_release_failed",
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return boundaryResult;
|
|
409
|
+
}
|
|
50
410
|
function hashString(input) {
|
|
51
411
|
return `sha256:${sha256Hex(input)}`;
|
|
52
412
|
}
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.BlueBubblesSendError = void 0;
|
|
36
37
|
exports.createBlueBubblesClient = createBlueBubblesClient;
|
|
37
38
|
const node_crypto_1 = require("node:crypto");
|
|
38
39
|
const config_1 = require("../../heart/config");
|
|
@@ -43,6 +44,21 @@ const minimax_1 = require("../../heart/providers/minimax");
|
|
|
43
44
|
const minimax_vlm_1 = require("../../heart/providers/minimax-vlm");
|
|
44
45
|
const model_1 = require("./model");
|
|
45
46
|
const media_1 = require("./media");
|
|
47
|
+
class BlueBubblesSendError extends Error {
|
|
48
|
+
httpStatus;
|
|
49
|
+
status;
|
|
50
|
+
errorCode;
|
|
51
|
+
transportInvoked;
|
|
52
|
+
constructor(input) {
|
|
53
|
+
super(input.message, input.cause === undefined ? undefined : { cause: input.cause });
|
|
54
|
+
this.name = "BlueBubblesSendError";
|
|
55
|
+
this.httpStatus = input.httpStatus;
|
|
56
|
+
this.status = input.httpStatus;
|
|
57
|
+
this.errorCode = input.errorCode;
|
|
58
|
+
this.transportInvoked = input.transportInvoked;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.BlueBubblesSendError = BlueBubblesSendError;
|
|
46
62
|
function buildBlueBubblesApiUrl(baseUrl, endpoint, password) {
|
|
47
63
|
const root = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
48
64
|
const url = new URL(endpoint.replace(/^\//, ""), root);
|
|
@@ -94,6 +110,28 @@ async function parseJsonBody(response) {
|
|
|
94
110
|
function describeCaughtValue(error) {
|
|
95
111
|
return error instanceof Error ? error.message : String(error);
|
|
96
112
|
}
|
|
113
|
+
function sendTransportErrorCode(error) {
|
|
114
|
+
if (error instanceof Error && error.name === "TimeoutError")
|
|
115
|
+
return "timeout";
|
|
116
|
+
if (error instanceof Error && error.name === "AbortError")
|
|
117
|
+
return "abort";
|
|
118
|
+
if (error instanceof TypeError)
|
|
119
|
+
return "socket";
|
|
120
|
+
return "transport_error";
|
|
121
|
+
}
|
|
122
|
+
function emitBlueBubblesSendError(input) {
|
|
123
|
+
(0, runtime_1.emitNervesEvent)({
|
|
124
|
+
level: "error",
|
|
125
|
+
component: "senses",
|
|
126
|
+
event: "senses.bluebubbles_send_error",
|
|
127
|
+
message: "bluebubbles send failed",
|
|
128
|
+
meta: {
|
|
129
|
+
status: input.status,
|
|
130
|
+
errorCode: input.errorCode,
|
|
131
|
+
reason: input.reason,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
}
|
|
97
135
|
function buildRepairUrl(baseUrl, messageGuid, password) {
|
|
98
136
|
const url = buildBlueBubblesApiUrl(baseUrl, `/api/v1/message/${encodeURIComponent(messageGuid)}`, password);
|
|
99
137
|
const parsed = new URL(url);
|
|
@@ -290,27 +328,62 @@ function createBlueBubblesClient(config = (0, config_1.getBlueBubblesConfig)(),
|
|
|
290
328
|
hasReplyTarget: Boolean(params.replyToMessageGuid?.trim()),
|
|
291
329
|
},
|
|
292
330
|
});
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
331
|
+
const requestTimeoutSignal = AbortSignal.timeout(channelConfig.requestTimeoutMs);
|
|
332
|
+
params.onTransportInvocation?.();
|
|
333
|
+
let response;
|
|
334
|
+
try {
|
|
335
|
+
response = await fetch(url, {
|
|
336
|
+
method: "POST",
|
|
337
|
+
headers: { "Content-Type": "application/json" },
|
|
338
|
+
body: JSON.stringify(body),
|
|
339
|
+
redirect: "manual",
|
|
340
|
+
signal: params.signal
|
|
341
|
+
? AbortSignal.any([params.signal, requestTimeoutSignal])
|
|
342
|
+
: requestTimeoutSignal,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
const errorCode = sendTransportErrorCode(error);
|
|
347
|
+
const reason = describeCaughtValue(error);
|
|
348
|
+
emitBlueBubblesSendError({ status: null, errorCode, reason });
|
|
349
|
+
throw new BlueBubblesSendError({
|
|
350
|
+
message: `BlueBubbles send failed (${errorCode}): ${reason}`,
|
|
351
|
+
httpStatus: null,
|
|
352
|
+
errorCode,
|
|
353
|
+
transportInvoked: true,
|
|
354
|
+
cause: error,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
299
357
|
if (!response.ok) {
|
|
300
358
|
const errorText = await response.text().catch(() => "");
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
message:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
},
|
|
359
|
+
const reason = errorText || "unknown";
|
|
360
|
+
const errorCode = `http_${response.status}`;
|
|
361
|
+
emitBlueBubblesSendError({ status: response.status, errorCode, reason });
|
|
362
|
+
throw new BlueBubblesSendError({
|
|
363
|
+
message: `BlueBubbles send failed (${response.status}): ${reason}`,
|
|
364
|
+
httpStatus: response.status,
|
|
365
|
+
errorCode,
|
|
366
|
+
transportInvoked: true,
|
|
310
367
|
});
|
|
311
|
-
throw new Error(`BlueBubbles send failed (${response.status}): ${errorText || "unknown"}`);
|
|
312
368
|
}
|
|
313
|
-
const
|
|
369
|
+
const rawPayload = await response.text();
|
|
370
|
+
let payload = null;
|
|
371
|
+
if (rawPayload.trim()) {
|
|
372
|
+
try {
|
|
373
|
+
payload = JSON.parse(rawPayload);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
const errorCode = "malformed_response";
|
|
377
|
+
emitBlueBubblesSendError({ status: response.status, errorCode, reason: errorCode });
|
|
378
|
+
throw new BlueBubblesSendError({
|
|
379
|
+
message: `BlueBubbles send failed (${response.status}): malformed response`,
|
|
380
|
+
httpStatus: response.status,
|
|
381
|
+
errorCode,
|
|
382
|
+
transportInvoked: true,
|
|
383
|
+
cause: error,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
314
387
|
const messageGuid = extractMessageGuid(payload);
|
|
315
388
|
(0, runtime_1.emitNervesEvent)({
|
|
316
389
|
component: "senses",
|