@ouro.bot/cli 0.1.0-alpha.720 → 0.1.0-alpha.721

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 CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.721",
6
+ "changes": [
7
+ "Make Slugger context hydration and native habit traces inspectable and coverage-gated."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.720",
6
12
  "changes": [
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.isHabitRunTrigger = isHabitRunTrigger;
37
37
  exports.flightRecorderLatestPath = flightRecorderLatestPath;
38
+ exports.decisionsFromHabitRunTraceSteps = decisionsFromHabitRunTraceSteps;
38
39
  exports.isFlightRecorderResume = isFlightRecorderResume;
39
40
  exports.readFlightRecorderResume = readFlightRecorderResume;
40
41
  exports.writeFlightRecorderResume = writeFlightRecorderResume;
@@ -86,6 +87,17 @@ function atomicWriteJson(filePath, value) {
86
87
  function cappedArray(values) {
87
88
  return values.map((value) => (0, session_events_1.capStructuredRecordString)(value));
88
89
  }
90
+ function decisionsFromHabitRunTraceSteps(traceSteps = []) {
91
+ const decisions = [];
92
+ for (const step of traceSteps) {
93
+ for (const decision of step.decisions ?? []) {
94
+ const trimmed = decision.trim();
95
+ if (trimmed.length > 0)
96
+ decisions.push((0, session_events_1.capStructuredRecordString)(trimmed));
97
+ }
98
+ }
99
+ return uniqueStrings(decisions);
100
+ }
89
101
  function normalizeEvent(input) {
90
102
  const recordedAt = input.recordedAt ?? new Date().toISOString();
91
103
  return {
@@ -604,6 +616,55 @@ function isHabitSurfaceAttemptArray(value) {
604
616
  && (entry.rawStatus === undefined || typeof entry.rawStatus === "string")
605
617
  && (entry.error === undefined || typeof entry.error === "string"));
606
618
  }
619
+ function isHabitRunTraceStepKind(value) {
620
+ return value === "trigger"
621
+ || value === "habit_definition"
622
+ || value === "fetch"
623
+ || value === "snapshot"
624
+ || value === "render"
625
+ || value === "decision"
626
+ || value === "produced_ref"
627
+ || value === "surface_attempt"
628
+ || value === "send"
629
+ || value === "ledger"
630
+ || value === "error"
631
+ || value === "complete";
632
+ }
633
+ function isHabitRunTraceStepStatus(value) {
634
+ return value === "started"
635
+ || value === "succeeded"
636
+ || value === "skipped"
637
+ || value === "blocked"
638
+ || value === "failed";
639
+ }
640
+ function isHabitRunTraceRefArray(value) {
641
+ return Array.isArray(value)
642
+ && value.every((entry) => isPlainRecord(entry)
643
+ && typeof entry.kind === "string"
644
+ && entry.kind.trim().length > 0
645
+ && typeof entry.locator === "string"
646
+ && entry.locator.trim().length > 0
647
+ && (entry.label === undefined || typeof entry.label === "string"));
648
+ }
649
+ function isHabitRunTraceStepArray(value) {
650
+ return Array.isArray(value)
651
+ && value.every((entry) => isPlainRecord(entry)
652
+ && entry.schemaVersion === 1
653
+ && typeof entry.stepId === "string"
654
+ && entry.stepId.trim().length > 0
655
+ && isHabitRunTraceStepKind(entry.kind)
656
+ && isHabitRunTraceStepStatus(entry.status)
657
+ && typeof entry.at === "string"
658
+ && entry.at.trim().length > 0
659
+ && typeof entry.summary === "string"
660
+ && entry.summary.trim().length > 0
661
+ && (entry.refs === undefined || isHabitRunTraceRefArray(entry.refs))
662
+ && (entry.producedRefs === undefined || isProducedRefArray(entry.producedRefs))
663
+ && (entry.decisions === undefined || isStringArray(entry.decisions))
664
+ && (entry.surfaceAttempt === undefined || isHabitSurfaceAttemptArray([entry.surfaceAttempt]))
665
+ && (entry.error === undefined || typeof entry.error === "string")
666
+ && (entry.meta === undefined || isPlainRecord(entry.meta)));
667
+ }
607
668
  function isHabitReturnRouteArray(value) {
608
669
  return Array.isArray(value)
609
670
  && value.every((entry) => isPlainRecord(entry)
@@ -633,10 +694,11 @@ function isHabitToolPolicy(value) {
633
694
  && typeof value.outwardMessagingAllowed === "boolean";
634
695
  }
635
696
  function defaultHabitRunSummarySnapshot(receipt) {
697
+ const decisions = decisionsFromHabitRunTraceSteps(receipt.traceSteps);
636
698
  if (receipt.errors.length > 0) {
637
699
  return {
638
700
  summary: `Habit ${receipt.habitName} finished with errors: ${receipt.errors.join("; ")}`,
639
- decisions: [],
701
+ decisions,
640
702
  nextLikelyStep: null,
641
703
  };
642
704
  }
@@ -644,7 +706,7 @@ function defaultHabitRunSummarySnapshot(receipt) {
644
706
  if (surface) {
645
707
  return {
646
708
  summary: `Habit ${receipt.habitName} surfaced via ${surface.recipient}/${surface.channel}.`,
647
- decisions: [],
709
+ decisions,
648
710
  nextLikelyStep: null,
649
711
  };
650
712
  }
@@ -652,13 +714,13 @@ function defaultHabitRunSummarySnapshot(receipt) {
652
714
  if (produced) {
653
715
  return {
654
716
  summary: `Habit ${receipt.habitName} produced ${produced.kind}: ${produced.locator}.`,
655
- decisions: [],
717
+ decisions,
656
718
  nextLikelyStep: null,
657
719
  };
658
720
  }
659
721
  return {
660
722
  summary: `Habit ${receipt.habitName} finished with ${receipt.outcome}.`,
661
- decisions: [],
723
+ decisions,
662
724
  nextLikelyStep: null,
663
725
  };
664
726
  }
@@ -674,7 +736,7 @@ function normalizeHabitRunSummarySnapshot(value, fallback) {
674
736
  : fallback.nextLikelyStep;
675
737
  return {
676
738
  summary: (0, session_events_1.capStructuredRecordString)(summary),
677
- decisions: cappedArray(isStringArray(snapshot.decisions) ? snapshot.decisions : fallback.decisions),
739
+ decisions: cappedArray(isStringArray(snapshot.decisions) && snapshot.decisions.length > 0 ? snapshot.decisions : fallback.decisions),
678
740
  nextLikelyStep: nextLikelyStep === null ? null : (0, session_events_1.capStructuredRecordString)(nextLikelyStep),
679
741
  };
680
742
  }
@@ -711,6 +773,7 @@ function isHabitRunReceipt(value) {
711
773
  && (value.summarySnapshot === undefined || isPlainRecord(value.summarySnapshot))
712
774
  && isProducedRefArray(value.producedRefs)
713
775
  && isHabitSurfaceAttemptArray(value.surfaceAttempts)
776
+ && (value.traceSteps === undefined || isHabitRunTraceStepArray(value.traceSteps))
714
777
  && isStringArray(value.errors);
715
778
  }
716
779
  function isLegacyHabitRunReceipt(value) {
@@ -780,11 +843,53 @@ function normalizeLegacyHabitRunReceipt(receipt) {
780
843
  summarySnapshot: defaultHabitRunSummarySnapshot(receipt),
781
844
  producedRefs: receipt.producedRefs,
782
845
  surfaceAttempts: receipt.surfaceAttempts,
846
+ traceSteps: [],
783
847
  errors: receipt.errors,
784
848
  };
785
849
  }
850
+ function capHabitRunTraceRefs(refs) {
851
+ if (!refs || refs.length === 0)
852
+ return undefined;
853
+ return refs.map((ref) => ({
854
+ kind: (0, session_events_1.capStructuredRecordString)(ref.kind),
855
+ locator: (0, session_events_1.capStructuredRecordString)(ref.locator),
856
+ ...(ref.label ? { label: (0, session_events_1.capStructuredRecordString)(ref.label) } : {}),
857
+ }));
858
+ }
859
+ function capHabitRunTraceStep(step) {
860
+ const refs = capHabitRunTraceRefs(step.refs);
861
+ const producedRefs = step.producedRefs?.map((ref) => ({ ...ref, locator: (0, session_events_1.capStructuredRecordString)(ref.locator) }));
862
+ const decisions = step.decisions ? cappedArray(step.decisions) : undefined;
863
+ const surfaceAttempt = step.surfaceAttempt
864
+ ? {
865
+ ...step.surfaceAttempt,
866
+ recipient: (0, session_events_1.capStructuredRecordString)(step.surfaceAttempt.recipient),
867
+ channel: (0, session_events_1.capStructuredRecordString)(step.surfaceAttempt.channel),
868
+ ...(step.surfaceAttempt.rawStatus ? { rawStatus: (0, session_events_1.capStructuredRecordString)(step.surfaceAttempt.rawStatus) } : {}),
869
+ ...(step.surfaceAttempt.error ? { error: (0, session_events_1.capStructuredRecordString)(step.surfaceAttempt.error) } : {}),
870
+ }
871
+ : undefined;
872
+ return {
873
+ schemaVersion: 1,
874
+ stepId: (0, session_events_1.capStructuredRecordString)(step.stepId),
875
+ kind: step.kind,
876
+ status: step.status,
877
+ at: (0, session_events_1.capStructuredRecordString)(step.at),
878
+ summary: (0, session_events_1.capStructuredRecordString)(step.summary),
879
+ ...(refs ? { refs } : {}),
880
+ ...(producedRefs && producedRefs.length > 0 ? { producedRefs } : {}),
881
+ ...(decisions && decisions.length > 0 ? { decisions } : {}),
882
+ ...(surfaceAttempt ? { surfaceAttempt } : {}),
883
+ ...(step.error ? { error: (0, session_events_1.capStructuredRecordString)(step.error) } : {}),
884
+ ...(step.meta ? { meta: (0, session_events_1.capStructuredRecordStringLeaves)(step.meta) } : {}),
885
+ };
886
+ }
887
+ function normalizeHabitRunTraceSteps(traceSteps) {
888
+ return (traceSteps ?? []).map((step) => capHabitRunTraceStep(step));
889
+ }
786
890
  function capHabitRunReceipt(receipt) {
787
- const fallbackSnapshot = defaultHabitRunSummarySnapshot(receipt);
891
+ const traceSteps = normalizeHabitRunTraceSteps(receipt.traceSteps);
892
+ const fallbackSnapshot = defaultHabitRunSummarySnapshot({ ...receipt, traceSteps });
788
893
  return {
789
894
  ...receipt,
790
895
  habitName: (0, session_events_1.capStructuredRecordString)(receipt.habitName),
@@ -822,6 +927,7 @@ function capHabitRunReceipt(receipt) {
822
927
  ...(attempt.rawStatus ? { rawStatus: (0, session_events_1.capStructuredRecordString)(attempt.rawStatus) } : {}),
823
928
  ...(attempt.error ? { error: (0, session_events_1.capStructuredRecordString)(attempt.error) } : {}),
824
929
  })),
930
+ traceSteps,
825
931
  errors: receipt.errors.map((error) => (0, session_events_1.capStructuredRecordString)(error)),
826
932
  };
827
933
  }
@@ -460,10 +460,11 @@ function computeNextRunAt(habit, endedAt) {
460
460
  }
461
461
  function defaultSummarySnapshot(input) {
462
462
  const errors = input.errors ?? [];
463
+ const decisions = (0, flight_recorder_1.decisionsFromHabitRunTraceSteps)(input.traceSteps);
463
464
  if (errors.length > 0) {
464
465
  return {
465
466
  summary: `Habit ${input.habit.name} finished with errors: ${errors.join("; ")}`,
466
- decisions: [],
467
+ decisions,
467
468
  nextLikelyStep: null,
468
469
  };
469
470
  }
@@ -471,7 +472,7 @@ function defaultSummarySnapshot(input) {
471
472
  if (surfaced) {
472
473
  return {
473
474
  summary: `Habit ${input.habit.name} surfaced via ${surfaced.recipient}/${surfaced.channel}.`,
474
- decisions: [],
475
+ decisions,
475
476
  nextLikelyStep: null,
476
477
  };
477
478
  }
@@ -479,13 +480,13 @@ function defaultSummarySnapshot(input) {
479
480
  if (produced) {
480
481
  return {
481
482
  summary: `Habit ${input.habit.name} produced ${produced.kind}: ${produced.locator}.`,
482
- decisions: [],
483
+ decisions,
483
484
  nextLikelyStep: null,
484
485
  };
485
486
  }
486
487
  return {
487
488
  summary: `Habit ${input.habit.name} finished with ${input.outcome}.`,
488
- decisions: [],
489
+ decisions,
489
490
  nextLikelyStep: null,
490
491
  };
491
492
  }
@@ -512,6 +513,7 @@ function buildHabitRunReceipt(input) {
512
513
  summarySnapshot: input.summarySnapshot ?? defaultSummarySnapshot(input),
513
514
  producedRefs: input.producedRefs ?? [],
514
515
  surfaceAttempts: input.surfaceAttempts ?? [],
516
+ traceSteps: input.traceSteps ?? [],
515
517
  errors: input.errors ?? [],
516
518
  };
517
519
  (0, runtime_1.emitNervesEvent)({
@@ -572,6 +574,7 @@ function completeHabitRun(input) {
572
574
  toolPolicy: input.toolPolicy,
573
575
  producedRefs,
574
576
  surfaceAttempts: input.surfaceAttempts,
577
+ traceSteps: input.traceSteps,
575
578
  errors: input.errors,
576
579
  summarySnapshot: input.summarySnapshot,
577
580
  });
@@ -86,6 +86,8 @@ function createMailboxHttpReadHooks(options) {
86
86
  readAgentHabitRun: options.readAgentHabitRun ?? ((agentName, runId) => (0, mailbox_read_1.readHabitRunReceiptView)(agentRoot(agentName), runId)),
87
87
  readAgentHabitRunSummaries: options.readAgentHabitRunSummaries ?? ((agentName, habitOptions) => (0, mailbox_read_1.readHabitSessionSummaryListView)(agentRoot(agentName), habitOptions)),
88
88
  readAgentHabitRunSummary: options.readAgentHabitRunSummary ?? ((agentName, selector) => (0, mailbox_read_1.readHabitSessionSummaryView)(agentRoot(agentName), selector)),
89
+ readAgentContextPackets: options.readAgentContextPackets ?? ((agentName, packetOptions) => (0, mailbox_read_1.readContextPacketListView)(agentRoot(agentName), packetOptions)),
90
+ readAgentContextPacket: options.readAgentContextPacket ?? ((agentName, packetId) => (0, mailbox_read_1.readContextPacketView)(agentRoot(agentName), packetId)),
89
91
  readAgentMail: options.readAgentMail ?? ((agentName) => (0, mailbox_read_1.readMailView)(agentName)),
90
92
  readAgentMailMessage: options.readAgentMailMessage ?? ((agentName, messageId) => (0, mailbox_read_1.readMailMessageView)(agentName, messageId)),
91
93
  readDaemonHealth: options.readDaemonHealth ?? (() => (0, mailbox_read_1.readDaemonHealthDeep)(options.healthPath)),
@@ -321,6 +321,29 @@ async function handleAgentRoute(request, response, context) {
321
321
  (0, mailbox_http_response_1.writeJson)(response, 200, view);
322
322
  return;
323
323
  }
324
+ if (surface === "context-packets") {
325
+ const limit = parseHabitRunLimit(request.url);
326
+ if (limit === null) {
327
+ (0, mailbox_http_response_1.writeJson)(response, 400, { ok: false, error: "limit must be an integer between 1 and 100" });
328
+ return;
329
+ }
330
+ const view = limit === undefined
331
+ ? options.hooks.readAgentContextPackets(agent)
332
+ : options.hooks.readAgentContextPackets(agent, { limit });
333
+ (0, mailbox_http_response_1.writeJson)(response, 200, view);
334
+ return;
335
+ }
336
+ if (surface.startsWith("context-packets/")) {
337
+ const rawPacketId = surface.slice("context-packets/".length);
338
+ const packetId = decodePathSegment(rawPacketId);
339
+ const view = packetId ? options.hooks.readAgentContextPacket(agent, packetId) : null;
340
+ if (!view) {
341
+ (0, mailbox_http_response_1.writeJson)(response, 404, { ok: false, error: `context packet '${rawPacketId}' not found` });
342
+ return;
343
+ }
344
+ (0, mailbox_http_response_1.writeJson)(response, 200, view);
345
+ return;
346
+ }
324
347
  if (surface === "mail") {
325
348
  (0, mailbox_http_response_1.writeJson)(response, 200, await options.hooks.readAgentMail(agent));
326
349
  return;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readSelfFixView = exports.readMailboxContinuity = exports.readOrientationView = exports.readObligationDetailView = exports.readNoteDecisionView = exports.readSentinelView = exports.readContextLossGauntletView = exports.readChangesView = exports.readNeedsMeView = exports.readNotesView = exports.readLogView = exports.readHabitView = exports.readHabitRunView = exports.readHabitRunReceiptView = exports.readHabitSessionSummaryView = exports.readHabitSessionSummaryListView = exports.readFriendView = exports.readDeskPrefs = exports.readDaemonHealthDeep = exports.readCodingDeep = exports.readBridgeInventory = exports.readAttentionView = exports.readMailView = exports.readMailMessageView = exports.readSessionTranscript = exports.readSessionInventory = exports.readMailboxMachineState = exports.readMailboxAgentState = exports.readObligationSummary = void 0;
3
+ exports.readSelfFixView = exports.readMailboxContinuity = exports.readOrientationView = exports.readObligationDetailView = exports.readNoteDecisionView = exports.readSentinelView = exports.readContextLossGauntletView = exports.readChangesView = exports.readNeedsMeView = exports.readNotesView = exports.readLogView = exports.readContextPacketView = exports.readContextPacketListView = exports.readHabitView = exports.readHabitRunView = exports.readHabitRunReceiptView = exports.readHabitSessionSummaryView = exports.readHabitSessionSummaryListView = exports.readFriendView = exports.readDeskPrefs = exports.readDaemonHealthDeep = exports.readCodingDeep = exports.readBridgeInventory = exports.readAttentionView = exports.readMailView = exports.readMailMessageView = exports.readSessionTranscript = exports.readSessionInventory = exports.readMailboxMachineState = exports.readMailboxAgentState = exports.readObligationSummary = void 0;
4
4
  var agent_machine_1 = require("./readers/agent-machine");
5
5
  Object.defineProperty(exports, "readObligationSummary", { enumerable: true, get: function () { return agent_machine_1.readObligationSummary; } });
6
6
  Object.defineProperty(exports, "readMailboxAgentState", { enumerable: true, get: function () { return agent_machine_1.readMailboxAgentState; } });
@@ -23,6 +23,8 @@ Object.defineProperty(exports, "readHabitSessionSummaryView", { enumerable: true
23
23
  Object.defineProperty(exports, "readHabitRunReceiptView", { enumerable: true, get: function () { return runtime_readers_1.readHabitRunReceiptView; } });
24
24
  Object.defineProperty(exports, "readHabitRunView", { enumerable: true, get: function () { return runtime_readers_1.readHabitRunView; } });
25
25
  Object.defineProperty(exports, "readHabitView", { enumerable: true, get: function () { return runtime_readers_1.readHabitView; } });
26
+ Object.defineProperty(exports, "readContextPacketListView", { enumerable: true, get: function () { return runtime_readers_1.readContextPacketListView; } });
27
+ Object.defineProperty(exports, "readContextPacketView", { enumerable: true, get: function () { return runtime_readers_1.readContextPacketView; } });
26
28
  Object.defineProperty(exports, "readLogView", { enumerable: true, get: function () { return runtime_readers_1.readLogView; } });
27
29
  Object.defineProperty(exports, "readNotesView", { enumerable: true, get: function () { return runtime_readers_1.readNotesView; } });
28
30
  Object.defineProperty(exports, "readNeedsMeView", { enumerable: true, get: function () { return runtime_readers_1.readNeedsMeView; } });
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.readCodingDeep = readCodingDeep;
37
37
  exports.readAttentionView = readAttentionView;
38
+ exports.readContextPacketListView = readContextPacketListView;
39
+ exports.readContextPacketView = readContextPacketView;
38
40
  exports.readBridgeInventory = readBridgeInventory;
39
41
  exports.readDaemonHealthDeep = readDaemonHealthDeep;
40
42
  exports.readNotesView = readNotesView;
@@ -56,6 +58,7 @@ const habit_session_summary_1 = require("../../habits/habit-session-summary");
56
58
  const identity_1 = require("../../identity");
57
59
  const daemon_health_1 = require("../../daemon/daemon-health");
58
60
  const flight_recorder_1 = require("../../../arc/flight-recorder");
61
+ const context_packet_ledger_1 = require("../../../senses/context-packet-ledger");
59
62
  const shared_1 = require("./shared");
60
63
  const agent_machine_1 = require("./agent-machine");
61
64
  const sessions_1 = require("./sessions");
@@ -217,6 +220,12 @@ function readAttentionView(agentName, options = {}) {
217
220
  returnObligations,
218
221
  };
219
222
  }
223
+ function readContextPacketListView(agentRoot, options = {}) {
224
+ return (0, context_packet_ledger_1.listSenseContextPacketSummaries)(agentRoot, options);
225
+ }
226
+ function readContextPacketView(agentRoot, packetId) {
227
+ return (0, context_packet_ledger_1.readSenseContextPacketView)(agentRoot, packetId);
228
+ }
220
229
  /* v8 ignore start — defensive parsing */
221
230
  function readBridgeInventory(agentRoot) {
222
231
  const bridgesDir = path.join(agentRoot, "state", "bridges");
@@ -44,6 +44,7 @@ const run_ledger_1 = require("../heart/run-ledger");
44
44
  const spend_ledger_1 = require("./spend-ledger");
45
45
  const habit_policy_1 = require("./habit-policy");
46
46
  const runtime_1 = require("../nerves/runtime");
47
+ const flight_recorder_1 = require("../arc/flight-recorder");
47
48
  function sha256Hex(input) {
48
49
  return (0, node_crypto_1.createHash)("sha256").update(input).digest("hex");
49
50
  }
@@ -127,13 +128,33 @@ function producedRefsFor(payload) {
127
128
  const snapshotId = stringField(refreshRecord(payload) ?? {}, "snapshotId");
128
129
  return snapshotId ? [{ kind: "none", locator: `state/rsvp/snapshots/${snapshotId}.json` }] : [];
129
130
  }
131
+ function outboundAction(payload) {
132
+ const refresh = refreshRecord(payload);
133
+ const decision = refresh?.outboundDecision;
134
+ if (!decision || typeof decision !== "object" || Array.isArray(decision))
135
+ return "unknown";
136
+ return stringField(decision, "action") ?? "unknown";
137
+ }
130
138
  function surfaceAttemptsFor(payload, requestedSend, errorMessage) {
131
139
  if (payload.sendAllowed === true) {
140
+ const delivery = (refreshRecord(payload)?.delivery ?? {});
141
+ const rawStatus = stringField(delivery, "status") ?? stringField(delivery, "result");
142
+ if (rawStatus === "failed" || rawStatus === "error") {
143
+ return [{
144
+ recipient: "rsvp",
145
+ channel: "bluebubbles",
146
+ reason: "status",
147
+ result: "failed",
148
+ rawStatus,
149
+ error: stringField(delivery, "error") ?? stringField(delivery, "message") ?? rawStatus,
150
+ }];
151
+ }
132
152
  return [{
133
153
  recipient: "rsvp",
134
154
  channel: "bluebubbles",
135
155
  reason: "status",
136
156
  result: "sent",
157
+ ...(rawStatus ? { rawStatus } : {}),
137
158
  }];
138
159
  }
139
160
  if (requestedSend && errorMessage) {
@@ -153,6 +174,140 @@ function payloadError(payload) {
153
174
  }
154
175
  return null;
155
176
  }
177
+ function traceStatusForSurfaceAttempt(attempt) {
178
+ if (!attempt)
179
+ return "skipped";
180
+ if (attempt.result === "failed")
181
+ return "failed";
182
+ if (attempt.result === "blocked" || attempt.result === "unavailable")
183
+ return "blocked";
184
+ return "succeeded";
185
+ }
186
+ function nativeRsvpTraceSteps(input) {
187
+ const refresh = refreshRecord(input.payload);
188
+ const snapshotId = stringField(refresh ?? {}, "snapshotId");
189
+ const reportText = stringField(refresh ?? {}, "reportText");
190
+ const action = outboundAction(input.payload);
191
+ const firstAttempt = input.surfaceAttempts[0];
192
+ const snapshotLocator = snapshotId ? `state/rsvp/snapshots/${snapshotId}.json` : null;
193
+ const decisions = [
194
+ `mode=${input.mode}`,
195
+ `sendAllowed=${String(input.sendAllowed)}`,
196
+ `outboundAction=${action}`,
197
+ ];
198
+ return [
199
+ {
200
+ schemaVersion: 1,
201
+ stepId: "trigger",
202
+ kind: "trigger",
203
+ status: "succeeded",
204
+ at: input.startedAt,
205
+ summary: `${input.trigger} triggered ${input.habitName}.`,
206
+ },
207
+ {
208
+ schemaVersion: 1,
209
+ stepId: "habit-definition",
210
+ kind: "habit_definition",
211
+ status: "succeeded",
212
+ at: input.startedAt,
213
+ summary: `Loaded habit definition for ${input.habitName}.`,
214
+ refs: [{ kind: "habit_definition", locator: `habits/${input.habitName}.md`, label: input.habitName }],
215
+ },
216
+ {
217
+ schemaVersion: 1,
218
+ stepId: "fetch-refresh",
219
+ kind: "fetch",
220
+ status: input.errors.length > 0 ? "failed" : "succeeded",
221
+ at: input.endedAt,
222
+ summary: `Ran RSVP refresh in ${input.mode} mode.`,
223
+ refs: [{ kind: "source", locator: `rsvp/${input.source}` }],
224
+ },
225
+ {
226
+ schemaVersion: 1,
227
+ stepId: "snapshot",
228
+ kind: "snapshot",
229
+ status: snapshotLocator ? "succeeded" : "skipped",
230
+ at: input.endedAt,
231
+ summary: snapshotLocator ? "Recorded the RSVP snapshot reference." : "No RSVP snapshot reference was returned.",
232
+ ...(snapshotLocator ? { refs: [{ kind: "snapshot", locator: snapshotLocator }] } : {}),
233
+ },
234
+ {
235
+ schemaVersion: 1,
236
+ stepId: "render",
237
+ kind: "render",
238
+ status: reportText ? "succeeded" : "skipped",
239
+ at: input.endedAt,
240
+ summary: reportText ? "Rendered RSVP update text." : "No rendered RSVP update text was returned.",
241
+ },
242
+ {
243
+ schemaVersion: 1,
244
+ stepId: "decision",
245
+ kind: "decision",
246
+ status: input.errors.length > 0 ? "blocked" : "succeeded",
247
+ at: input.endedAt,
248
+ summary: `RSVP outbound decision: ${action}.`,
249
+ decisions,
250
+ },
251
+ {
252
+ schemaVersion: 1,
253
+ stepId: "produced-ref",
254
+ kind: "produced_ref",
255
+ status: input.producedRefs.length > 0 ? "succeeded" : "skipped",
256
+ at: input.endedAt,
257
+ summary: input.producedRefs.length > 0 ? "Recorded produced RSVP refs." : "No separate produced refs were recorded.",
258
+ ...(input.producedRefs.length > 0 ? { producedRefs: input.producedRefs } : {}),
259
+ },
260
+ {
261
+ schemaVersion: 1,
262
+ stepId: "surface-attempt",
263
+ kind: "surface_attempt",
264
+ status: traceStatusForSurfaceAttempt(firstAttempt),
265
+ at: input.endedAt,
266
+ summary: firstAttempt ? `Surface attempt ${firstAttempt.result} via ${firstAttempt.channel}.` : "No surface attempt was needed.",
267
+ ...(firstAttempt ? { surfaceAttempt: firstAttempt } : {}),
268
+ },
269
+ {
270
+ schemaVersion: 1,
271
+ stepId: "send",
272
+ kind: "send",
273
+ status: input.sendAllowed
274
+ ? traceStatusForSurfaceAttempt(firstAttempt)
275
+ : "skipped",
276
+ at: input.endedAt,
277
+ summary: input.sendAllowed ? "Live send was allowed for this run." : "Live send was not allowed for this run.",
278
+ ...(firstAttempt ? { surfaceAttempt: firstAttempt } : {}),
279
+ },
280
+ {
281
+ schemaVersion: 1,
282
+ stepId: "ledger",
283
+ kind: "ledger",
284
+ status: "succeeded",
285
+ at: input.endedAt,
286
+ summary: "Recorded native RSVP run ledger evidence.",
287
+ refs: [
288
+ { kind: "ledger", locator: "state/run-ledger/runs.jsonl" },
289
+ { kind: "ledger", locator: "state/rsvp/spend-ledger.json" },
290
+ ],
291
+ },
292
+ {
293
+ schemaVersion: 1,
294
+ stepId: "error",
295
+ kind: "error",
296
+ status: input.errors.length > 0 ? "failed" : "skipped",
297
+ at: input.endedAt,
298
+ summary: input.errors.length > 0 ? "RSVP refresh returned an error." : "No RSVP error happened.",
299
+ ...(input.errors[0] ? { error: input.errors[0] } : {}),
300
+ },
301
+ {
302
+ schemaVersion: 1,
303
+ stepId: "complete",
304
+ kind: "complete",
305
+ status: input.lifecycle === "completed" ? "succeeded" : "failed",
306
+ at: input.endedAt,
307
+ summary: `Native RSVP habit ${input.lifecycle}.`,
308
+ },
309
+ ];
310
+ }
156
311
  function recordNativeRunLedger(input) {
157
312
  const target = {
158
313
  habitName: input.habitName,
@@ -247,6 +402,7 @@ async function runNativeRsvpHabit(input) {
247
402
  const lifecycle = errorMessage ? "error" : "completed";
248
403
  const surfaceAttempts = surfaceAttemptsFor(payload, policy.sendAllowed, errorMessage);
249
404
  const errors = errorMessage ? [errorMessage] : [];
405
+ const producedRefs = producedRefsFor(payload);
250
406
  recordNativeRunLedger({
251
407
  agentRoot,
252
408
  agent: input.agent,
@@ -258,6 +414,20 @@ async function runNativeRsvpHabit(input) {
258
414
  endedAt,
259
415
  lifecycle,
260
416
  });
417
+ const traceSteps = nativeRsvpTraceSteps({
418
+ habitName: input.habitName,
419
+ trigger: input.trigger,
420
+ mode: habit.rsvp.mode,
421
+ source: habit.rsvp.source,
422
+ sendAllowed: policy.sendAllowed,
423
+ startedAt,
424
+ endedAt,
425
+ payload,
426
+ producedRefs,
427
+ surfaceAttempts,
428
+ errors,
429
+ lifecycle,
430
+ });
261
431
  const completion = (0, habit_session_1.completeHabitRun)({
262
432
  agentRoot,
263
433
  habit,
@@ -268,17 +438,15 @@ async function runNativeRsvpHabit(input) {
268
438
  operationId: `rsvp-native:${input.trigger}:${input.occurrenceId ?? runId}`,
269
439
  permissionEnvelope: permissionEnvelope(policy.sendAllowed),
270
440
  toolPolicy: toolPolicy(policy.sendAllowed),
271
- producedRefs: producedRefsFor(payload),
441
+ producedRefs,
272
442
  surfaceAttempts,
443
+ traceSteps,
273
444
  errors,
274
445
  summarySnapshot: {
275
446
  summary: errorMessage
276
447
  ? `Native RSVP habit failed: ${errorMessage}`
277
448
  : "Native RSVP habit completed.",
278
- decisions: [
279
- `mode=${habit.rsvp.mode}`,
280
- `sendAllowed=${String(policy.sendAllowed)}`,
281
- ],
449
+ decisions: (0, flight_recorder_1.decisionsFromHabitRunTraceSteps)(traceSteps),
282
450
  nextLikelyStep: null,
283
451
  },
284
452
  });
@@ -45,6 +45,23 @@ function messageToContextInput(event, chatGuidHash) {
45
45
  },
46
46
  };
47
47
  }
48
+ function normalizeKnownMessageText(value) {
49
+ return value.replace(/\s+/g, " ").trim().toLowerCase();
50
+ }
51
+ function hasKnownMessageText(candidate, knownTexts) {
52
+ if (knownTexts.size === 0)
53
+ return false;
54
+ const body = normalizeKnownMessageText(candidate.textForAgent || candidate.text);
55
+ if (!body)
56
+ return true;
57
+ if (knownTexts.has(body))
58
+ return true;
59
+ for (const known of knownTexts) {
60
+ if (body.length >= 12 && known.includes(body))
61
+ return true;
62
+ }
63
+ return false;
64
+ }
48
65
  async function buildBlueBubblesContextPacket(input) {
49
66
  if (!Number.isFinite(input.event.timestamp))
50
67
  return null;
@@ -53,6 +70,9 @@ async function buildBlueBubblesContextPacket(input) {
53
70
  const event = input.event;
54
71
  const chatGuidHash = blueBubblesContextChatKeyHash(event);
55
72
  const anchorTimestamp = new Date(event.timestamp).toISOString();
73
+ const knownTexts = new Set((input.knownMessageTexts ?? [])
74
+ .map(normalizeKnownMessageText)
75
+ .filter((value) => value.length > 0));
56
76
  const candidates = await input.client.listRecentMessages({
57
77
  beforeTimestamp: event.timestamp,
58
78
  limit: exports.BLUEBUBBLES_CONTEXT_PACKET_LIMIT,
@@ -65,7 +85,8 @@ async function buildBlueBubblesContextPacket(input) {
65
85
  .filter((candidate) => isSameBlueBubblesChat(candidate, event))
66
86
  .filter((candidate) => candidate.messageGuid !== event.messageGuid)
67
87
  .filter((candidate) => candidate.timestamp <= event.timestamp)
68
- .filter((candidate) => event.timestamp - candidate.timestamp <= exports.BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS);
88
+ .filter((candidate) => event.timestamp - candidate.timestamp <= exports.BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS)
89
+ .filter((candidate) => !hasKnownMessageText(candidate, knownTexts));
69
90
  if (history.length === 0)
70
91
  return null;
71
92
  const packet = (0, context_packets_1.buildSenseContextPacket)({
@@ -623,12 +623,18 @@ function insertEphemeralContextMessages(messages, contextMessages) {
623
623
  ...messages.slice(insertionIndex),
624
624
  ];
625
625
  }
626
+ function knownProviderMessageTexts(messages) {
627
+ return messages
628
+ .map((message) => extractMessageText(message.content))
629
+ .map((text) => text.trim())
630
+ .filter((text) => text.length > 0);
631
+ }
626
632
  async function buildBlueBubblesContextMessages(input) {
627
633
  if (input.event.kind !== "message")
628
- return [];
634
+ return { messages: [], contextPacketIds: [] };
629
635
  const event = input.event;
630
636
  if (!Number.isFinite(event.timestamp))
631
- return [];
637
+ return { messages: [], contextPacketIds: [] };
632
638
  const chatGuidHash = (0, context_packet_1.blueBubblesContextChatKeyHash)(event);
633
639
  const anchorTimestamp = new Date(event.timestamp).toISOString();
634
640
  try {
@@ -636,9 +642,10 @@ async function buildBlueBubblesContextMessages(input) {
636
642
  agentName: input.agentName,
637
643
  client: input.client,
638
644
  event,
645
+ knownMessageTexts: knownProviderMessageTexts(input.knownMessages ?? []),
639
646
  });
640
647
  if (!result)
641
- return [];
648
+ return { messages: [], contextPacketIds: [] };
642
649
  const { packet, rendered, historyCount } = result;
643
650
  (0, context_packet_ledger_1.writeSenseContextPacket)(input.agentRoot, packet);
644
651
  (0, runtime_1.emitNervesEvent)({
@@ -651,7 +658,10 @@ async function buildBlueBubblesContextMessages(input) {
651
658
  contextMessages: historyCount,
652
659
  },
653
660
  });
654
- return [{ role: "system", content: rendered.text }];
661
+ return {
662
+ messages: [{ role: "system", content: rendered.text }],
663
+ contextPacketIds: [packet.packetId],
664
+ };
655
665
  }
656
666
  catch (error) {
657
667
  (0, runtime_1.emitNervesEvent)({
@@ -671,9 +681,12 @@ async function buildBlueBubblesContextMessages(input) {
671
681
  maxAgeMs: 24 * 60 * 60 * 1000,
672
682
  });
673
683
  if (!fallback)
674
- return [];
684
+ return { messages: [], contextPacketIds: [] };
675
685
  const rendered = (0, context_packets_1.renderSenseContextPacketForPrompt)(fallback);
676
- return [{ role: "system", content: rendered.text }];
686
+ return {
687
+ messages: [{ role: "system", content: rendered.text }],
688
+ contextPacketIds: [fallback.packetId],
689
+ };
677
690
  }
678
691
  }
679
692
  function createReplyTargetController(event) {
@@ -1468,6 +1481,10 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
1468
1481
  if (typeof timeoutTimer.unref === "function") {
1469
1482
  timeoutTimer.unref();
1470
1483
  }
1484
+ let preparedBlueBubblesContext = {
1485
+ messages: [],
1486
+ contextPacketIds: [],
1487
+ };
1471
1488
  const turnPromise = (0, pipeline_1.handleInboundTurn)({
1472
1489
  channel: "bluebubbles",
1473
1490
  sessionKey: event.chat.sessionKey,
@@ -1494,35 +1511,44 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source, opt
1494
1511
  enforceTrustGate: trust_gate_1.enforceTrustGate,
1495
1512
  drainPending: pending_1.drainPending,
1496
1513
  drainDeferredReturns: (deferredFriendId) => (0, pending_1.drainDeferredReturns)(resolvedDeps.getAgentName(), deferredFriendId),
1497
- runAgent: async (msgs, cb, channel, sig, opts) => {
1498
- const contextMessages = await buildBlueBubblesContextMessages({
1514
+ prepareRunAgentOptions: async ({ messages, runAgentOptions }) => {
1515
+ preparedBlueBubblesContext = await buildBlueBubblesContextMessages({
1499
1516
  agentName,
1500
1517
  agentRoot: (0, identity_1.getAgentRoot)(),
1501
1518
  client,
1502
1519
  event,
1520
+ knownMessages: messages,
1503
1521
  });
1504
- return resolvedDeps.runAgent(insertEphemeralContextMessages(msgs, contextMessages), cb, channel, sig, {
1505
- ...opts,
1506
- toolContext: {
1507
- /* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
1508
- signin: async () => undefined,
1509
- ...opts?.toolContext,
1510
- summarize,
1511
- bluebubblesReplyTarget: {
1512
- setSelection: (selection) => replyTarget.setSelection(selection),
1513
- },
1514
- codingFeedback: {
1515
- send: async (message) => {
1516
- await client.sendText({
1517
- chat: event.chat,
1518
- text: message,
1519
- replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
1520
- });
1521
- },
1522
+ if (preparedBlueBubblesContext.contextPacketIds.length === 0)
1523
+ return undefined;
1524
+ return {
1525
+ contextPacketIds: [
1526
+ ...(runAgentOptions.contextPacketIds ?? []),
1527
+ ...preparedBlueBubblesContext.contextPacketIds,
1528
+ ],
1529
+ };
1530
+ },
1531
+ runAgent: async (msgs, cb, channel, sig, opts) => resolvedDeps.runAgent(insertEphemeralContextMessages(msgs, preparedBlueBubblesContext.messages), cb, channel, sig, {
1532
+ ...opts,
1533
+ toolContext: {
1534
+ /* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
1535
+ signin: async () => undefined,
1536
+ ...opts?.toolContext,
1537
+ summarize,
1538
+ bluebubblesReplyTarget: {
1539
+ setSelection: (selection) => replyTarget.setSelection(selection),
1540
+ },
1541
+ codingFeedback: {
1542
+ send: async (message) => {
1543
+ await client.sendText({
1544
+ chat: event.chat,
1545
+ text: message,
1546
+ replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
1547
+ });
1522
1548
  },
1523
1549
  },
1524
- });
1525
- },
1550
+ },
1551
+ }),
1526
1552
  postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
1527
1553
  const prepared = resolvedDeps.postTurnTrim(turnMessages, usage, hooks);
1528
1554
  resolvedDeps.deferPostTurnPersist(sessionPathArg, prepared, usage, state);
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.writeSenseContextPacket = writeSenseContextPacket;
37
37
  exports.readSenseContextLedger = readSenseContextLedger;
38
+ exports.listSenseContextPacketSummaries = listSenseContextPacketSummaries;
39
+ exports.readSenseContextPacketView = readSenseContextPacketView;
38
40
  exports.readLatestVisibleSenseContextPacket = readLatestVisibleSenseContextPacket;
39
41
  exports.compactSenseContextPacketReceipts = compactSenseContextPacketReceipts;
40
42
  const fs = __importStar(require("node:fs"));
@@ -44,6 +46,9 @@ const context_packets_1 = require("./context-packets");
44
46
  function contextPacketRoot(agentRoot, sense) {
45
47
  return path.join(agentRoot, "state", "senses", "context-packets", sense);
46
48
  }
49
+ function contextPacketBaseRoot(agentRoot) {
50
+ return path.join(agentRoot, "state", "senses", "context-packets");
51
+ }
47
52
  function packetPath(agentRoot, packet) {
48
53
  const month = packet.anchorTimestamp.slice(0, 7) || "unknown";
49
54
  return path.join(contextPacketRoot(agentRoot, packet.sense), month, `${packet.packetId}.json`);
@@ -181,6 +186,127 @@ function readSenseContextLedger(agentRoot, sense) {
181
186
  });
182
187
  return rows;
183
188
  }
189
+ function safeContextPacketLimit(limit) {
190
+ return Number.isInteger(limit) && typeof limit === "number" && limit >= 1 && limit <= 100 ? limit : 20;
191
+ }
192
+ function discoverContextPacketSenses(agentRoot) {
193
+ const root = contextPacketBaseRoot(agentRoot);
194
+ if (!fs.existsSync(root))
195
+ return [];
196
+ return fs.readdirSync(root)
197
+ .filter((entry) => {
198
+ try {
199
+ return fs.statSync(path.join(root, entry)).isDirectory();
200
+ }
201
+ catch {
202
+ return false;
203
+ }
204
+ })
205
+ .sort();
206
+ }
207
+ function summarizeLedgerRow(row) {
208
+ return {
209
+ packetId: row.packetId,
210
+ sense: row.sense,
211
+ agent: row.agent,
212
+ chatKeyHash: row.chatKeyHash,
213
+ anchorMessageGuid: row.anchorMessageGuid,
214
+ anchorTimestamp: row.anchorTimestamp,
215
+ createdAt: row.createdAt,
216
+ messageCount: row.messageCount,
217
+ sourceRefs: row.sourceRefs,
218
+ bodyHashes: row.bodyHashes,
219
+ rawBodyStored: row.rawBodyStored,
220
+ privacyClass: row.privacyClass,
221
+ omittedMessages: row.omittedMessages,
222
+ truncatedMessages: row.truncatedMessages,
223
+ };
224
+ }
225
+ function readAllContextPacketRows(agentRoot, sense) {
226
+ const senses = sense ? [sense] : discoverContextPacketSenses(agentRoot);
227
+ return senses.flatMap((entry) => readSenseContextLedger(agentRoot, entry));
228
+ }
229
+ function timestampSortValue(value) {
230
+ const parsed = Date.parse(value);
231
+ return Number.isFinite(parsed) ? parsed : 0;
232
+ }
233
+ function listSenseContextPacketSummaries(agentRoot, options = {}) {
234
+ const limit = safeContextPacketLimit(options.limit);
235
+ const rows = readAllContextPacketRows(agentRoot, options.sense)
236
+ .sort((left, right) => timestampSortValue(right.createdAt || right.anchorTimestamp) - timestampSortValue(left.createdAt || left.anchorTimestamp));
237
+ return {
238
+ totalCount: rows.length,
239
+ limit,
240
+ items: rows.slice(0, limit).map(summarizeLedgerRow),
241
+ };
242
+ }
243
+ function isSafeContextPacketId(packetId) {
244
+ return /^scp_[A-Za-z0-9_-]+$/.test(packetId);
245
+ }
246
+ function pathInsideContextPacketRoot(agentRoot, sense, filePath) {
247
+ const root = path.resolve(contextPacketRoot(agentRoot, sense));
248
+ const resolved = path.resolve(filePath);
249
+ return resolved === root || resolved.startsWith(`${root}${path.sep}`);
250
+ }
251
+ function redactPreview(value) {
252
+ return value
253
+ .replace(/\bpassword=[^\s]+/gi, "password=[redacted]")
254
+ .replace(/\b(token|api[_-]?key|secret)=[^\s]+/gi, "$1=[redacted]");
255
+ }
256
+ function viewMessage(message) {
257
+ const { chatGuid: _chatGuid, ...sourceRef } = message.sourceRef;
258
+ return {
259
+ timestamp: message.timestamp,
260
+ authorLabel: message.authorLabel,
261
+ bodyHash: message.bodyHash,
262
+ bodyPreview: redactPreview(message.bodyPreview),
263
+ sourceRef,
264
+ renderedSourceRef: message.renderedSourceRef,
265
+ };
266
+ }
267
+ function emitContextPacketReadWarning(packetId, reason) {
268
+ (0, runtime_1.emitNervesEvent)({
269
+ level: "warn",
270
+ component: "senses",
271
+ event: "senses.context_packet_read_error",
272
+ message: "failed to read sense context packet view",
273
+ meta: { packetId, reason },
274
+ });
275
+ }
276
+ function readSenseContextPacketView(agentRoot, packetId) {
277
+ if (!isSafeContextPacketId(packetId))
278
+ return null;
279
+ const row = readAllContextPacketRows(agentRoot).find((entry) => entry.packetId === packetId);
280
+ if (!row)
281
+ return null;
282
+ if (!pathInsideContextPacketRoot(agentRoot, row.sense, row.packetPath)) {
283
+ emitContextPacketReadWarning(packetId, "packet path escaped context-packet root");
284
+ return null;
285
+ }
286
+ if (!fs.existsSync(row.packetPath))
287
+ return null;
288
+ try {
289
+ const packet = JSON.parse(fs.readFileSync(row.packetPath, "utf-8"));
290
+ if (packet.packetId !== packetId || !Array.isArray(packet.messages))
291
+ return null;
292
+ return {
293
+ row: summarizeLedgerRow(row),
294
+ packet: {
295
+ packetId,
296
+ sense: typeof packet.sense === "string" ? packet.sense : row.sense,
297
+ agent: typeof packet.agent === "string" ? packet.agent : row.agent,
298
+ privacyClass: packet.privacyClass === "private-runtime" ? packet.privacyClass : "private-runtime",
299
+ messages: packet.messages.map((message) => viewMessage(message)),
300
+ omittedMessages: typeof packet.omittedMessages === "number" ? packet.omittedMessages : row.omittedMessages,
301
+ truncatedMessages: typeof packet.truncatedMessages === "number" ? packet.truncatedMessages : row.truncatedMessages,
302
+ },
303
+ };
304
+ }
305
+ catch (error) {
306
+ emitContextPacketReadWarning(packetId, error instanceof Error ? error.message : String(error));
307
+ return null;
308
+ }
309
+ }
184
310
  function timestampMs(value) {
185
311
  return Date.parse(value);
186
312
  }
@@ -369,6 +369,36 @@ let _lastSessionKey = null;
369
369
  function runAgentOptionContextPacketIds(options) {
370
370
  return Array.isArray(options.contextPacketIds) ? options.contextPacketIds : [];
371
371
  }
372
+ function mergeRunAgentContextPacketIds(left, right) {
373
+ const merged = [
374
+ ...(Array.isArray(left) ? left : []),
375
+ ...(Array.isArray(right) ? right : []),
376
+ ].filter((value) => typeof value === "string" && value.trim().length > 0);
377
+ return merged.length > 0 ? [...new Set(merged)] : undefined;
378
+ }
379
+ function mergePreparedRunAgentOptions(base, prepared) {
380
+ if (!prepared)
381
+ return base;
382
+ const contextPacketIds = mergeRunAgentContextPacketIds(base.contextPacketIds, prepared.contextPacketIds);
383
+ const { contextPacketIds: _baseContextPacketIds, ...baseOptions } = base;
384
+ const { contextPacketIds: _preparedContextPacketIds, ...preparedOptions } = prepared;
385
+ const merged = {
386
+ ...baseOptions,
387
+ ...preparedOptions,
388
+ ...(contextPacketIds ? { contextPacketIds } : {}),
389
+ };
390
+ if (prepared.toolContext) {
391
+ const toolContext = {
392
+ ...base.toolContext,
393
+ ...prepared.toolContext,
394
+ };
395
+ merged.toolContext = {
396
+ ...toolContext,
397
+ signin: toolContext.signin ?? (async () => undefined),
398
+ };
399
+ }
400
+ return merged;
401
+ }
372
402
  function buildInboundRunLedgerInput(input) {
373
403
  const target = {
374
404
  channel: input.channel,
@@ -822,7 +852,7 @@ async function handleInboundTurn(input) {
822
852
  const existingToolContext = input.runAgentOptions?.toolContext;
823
853
  const currentUserMessage = existingToolContext?.currentUserMessage
824
854
  ?? latestUserAuthoredText(input.messages, input.continuityIngressTexts);
825
- const runAgentOptions = {
855
+ let runAgentOptions = {
826
856
  ...input.runAgentOptions,
827
857
  ...(orientationFrame ? { orientationFrame } : {}),
828
858
  ...(liveLatencyMode ? { skipKeptNotes: true } : {}),
@@ -856,6 +886,12 @@ async function handleInboundTurn(input) {
856
886
  ...(orientationFrame ? { orientationFrame } : {}),
857
887
  },
858
888
  };
889
+ runAgentOptions = mergePreparedRunAgentOptions(runAgentOptions, await input.prepareRunAgentOptions?.({
890
+ messages: sessionMessages,
891
+ currentUserMessages,
892
+ resolvedContext,
893
+ runAgentOptions,
894
+ }));
859
895
  const runLedger = prepareInboundRunLedger({
860
896
  channel: input.channel,
861
897
  senseType: input.capabilities.senseType,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.720",
3
+ "version": "0.1.0-alpha.721",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",