@ouro.bot/cli 0.1.0-alpha.724 → 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.
Files changed (54) hide show
  1. package/changelog.json +12 -0
  2. package/dist/heart/active-work.js +10 -3
  3. package/dist/heart/core.js +343 -77
  4. package/dist/heart/daemon/cli-exec.js +72 -14
  5. package/dist/heart/daemon/cli-help.js +13 -2
  6. package/dist/heart/daemon/cli-parse.js +82 -1
  7. package/dist/heart/daemon/daemon.js +286 -33
  8. package/dist/heart/daemon/doctor.js +21 -6
  9. package/dist/heart/daemon/habit-private-wake.js +6 -1
  10. package/dist/heart/habits/habit-cancel.js +810 -0
  11. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  12. package/dist/heart/habits/habit-migration.js +28 -23
  13. package/dist/heart/habits/habit-parser.js +72 -8
  14. package/dist/heart/habits/habit-runtime-state.js +3 -24
  15. package/dist/heart/habits/habit-scheduler.js +268 -50
  16. package/dist/heart/habits/habit-session.js +16 -0
  17. package/dist/heart/hatch/hatch-flow.js +6 -4
  18. package/dist/heart/mailbox/readers/runtime-readers.js +26 -27
  19. package/dist/heart/orientation-frame.js +58 -1
  20. package/dist/heart/provider-attempt.js +4 -1
  21. package/dist/heart/providers/anthropic.js +23 -2
  22. package/dist/heart/start-of-turn-packet.js +14 -11
  23. package/dist/heart/streaming.js +565 -185
  24. package/dist/mind/obligation-steering.js +15 -9
  25. package/dist/mind/prompt.js +58 -26
  26. package/dist/nerves/coverage/run-artifacts.js +55 -35
  27. package/dist/nerves/review/cli.js +2 -2
  28. package/dist/nerves/review/core.js +14 -7
  29. package/dist/repertoire/tools-base.js +2 -0
  30. package/dist/repertoire/tools-habits.js +103 -0
  31. package/dist/repertoire/tools.js +18 -3
  32. package/dist/rsvp/cli.js +126 -20
  33. package/dist/rsvp/diagnostics.js +12 -2
  34. package/dist/rsvp/habit-policy.js +7 -0
  35. package/dist/rsvp/habit-stage.js +19 -4
  36. package/dist/rsvp/native-habit-runner.js +151 -11
  37. package/dist/rsvp/outbound-state.js +360 -0
  38. package/dist/senses/bluebubbles/client.js +90 -17
  39. package/dist/senses/bluebubbles/inbound-log.js +1 -0
  40. package/dist/senses/bluebubbles/index.js +662 -425
  41. package/dist/senses/bluebubbles/model.js +57 -13
  42. package/dist/senses/bluebubbles/mutation-log.js +21 -10
  43. package/dist/senses/bluebubbles/reaction-policy.js +55 -0
  44. package/dist/senses/bluebubbles/semantic-receipts.js +1818 -0
  45. package/dist/senses/cli/tui-store.js +1 -0
  46. package/dist/senses/cli.js +1 -0
  47. package/dist/senses/habit-lifecycle-guard.js +24 -0
  48. package/dist/senses/ingress-evidence.js +2 -0
  49. package/dist/senses/pipeline.js +90 -24
  50. package/dist/senses/private-runtime-worker.js +21 -20
  51. package/dist/senses/private-runtime.js +79 -29
  52. package/dist/senses/shared-turn.js +1 -0
  53. package/dist/senses/teams.js +1 -0
  54. 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 response = await fetch(url, {
294
- method: "POST",
295
- headers: { "Content-Type": "application/json" },
296
- body: JSON.stringify(body),
297
- signal: AbortSignal.timeout(channelConfig.requestTimeoutMs),
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
- (0, runtime_1.emitNervesEvent)({
302
- level: "error",
303
- component: "senses",
304
- event: "senses.bluebubbles_send_error",
305
- message: "bluebubbles send failed",
306
- meta: {
307
- status: response.status,
308
- reason: errorText || "unknown",
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 payload = await parseJsonBody(response);
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",
@@ -91,6 +91,7 @@ function recordBlueBubblesInbound(agentName, event, source) {
91
91
  chatIdentifier: event.chat.chatIdentifier ?? null,
92
92
  sessionKey: event.chat.sessionKey,
93
93
  textForAgent: event.textForAgent,
94
+ fromMe: event.fromMe,
94
95
  source,
95
96
  }) + "\n", "utf-8");
96
97
  }