@ouro.bot/cli 0.1.0-alpha.704 → 0.1.0-alpha.706

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,18 @@
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.706",
6
+ "changes": [
7
+ "Route native RSVP habit checks through typed refresh execution instead of private-runtime wake policy."
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.705",
12
+ "changes": [
13
+ "Add a side-effect-safe BlueBubbles context smoke command for verifying same-thread packet ledgers."
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.704",
6
18
  "changes": [
@@ -485,6 +485,7 @@ function agentResolutionFailureMode(command) {
485
485
  case "config.models":
486
486
  case "setup":
487
487
  case "bluebubbles.replay":
488
+ case "bluebubbles.context-smoke":
488
489
  return "throw";
489
490
  default:
490
491
  return undefined;
@@ -6081,6 +6082,19 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
6081
6082
  deps.writeStdout(text);
6082
6083
  return text;
6083
6084
  }
6085
+ if (command.kind === "bluebubbles.context-smoke") {
6086
+ const { smokeBlueBubblesContext, formatBlueBubblesContextSmokeText } = await Promise.resolve().then(() => __importStar(require("../../senses/bluebubbles/context-smoke")));
6087
+ const smoke = await smokeBlueBubblesContext({
6088
+ agentName: command.agent,
6089
+ messageGuid: command.messageGuid,
6090
+ persist: command.persist,
6091
+ });
6092
+ const text = command.json
6093
+ ? JSON.stringify(smoke, null, 2)
6094
+ : formatBlueBubblesContextSmokeText(smoke);
6095
+ deps.writeStdout(text);
6096
+ return text;
6097
+ }
6084
6098
  if (isRsvpCliCommand(command)) {
6085
6099
  const { runRsvpCliCommand } = await Promise.resolve().then(() => __importStar(require("../../rsvp/cli")));
6086
6100
  return runRsvpCliCommand(command, deps);
@@ -359,9 +359,12 @@ exports.COMMAND_REGISTRY = {
359
359
  bluebubbles: {
360
360
  category: "System",
361
361
  description: "BlueBubbles integration commands",
362
- usage: "ouro bluebubbles replay [--agent <name>] --message-guid <guid> [--event-type <type>] [--json]",
363
- example: "ouro bluebubbles replay --message-guid abc123",
364
- subcommands: ["replay"],
362
+ usage: [
363
+ "ouro bluebubbles replay [--agent <name>] --message-guid <guid> [--event-type <type>] [--json]",
364
+ "ouro bluebubbles context-smoke [--agent <name>] --message-guid <guid> [--persist] [--json]",
365
+ ].join("\n"),
366
+ example: "ouro bluebubbles context-smoke --agent slugger --message-guid abc123 --persist --json",
367
+ subcommands: ["replay", "context-smoke"],
365
368
  },
366
369
  };
367
370
  const SUBCOMMAND_HELP = {
@@ -114,6 +114,7 @@ function usage() {
114
114
  " ouro habit summary [--agent <name>] (--run-id <id>|--habit <name>|--operation-id <id>) [--which latest|previous|latest-success|latest-failure] [--json]",
115
115
  " ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
116
116
  " ouro bluebubbles replay [--agent <name>] --message-guid <guid> [--event-type new-message|updated-message] [--json]",
117
+ " ouro bluebubbles context-smoke [--agent <name>] --message-guid <guid> [--persist] [--json]",
117
118
  " ouro rsvp <doctor|incident|cutover|legacy-render|replay|config|habit|import-legacy|refresh|compare|smoke> ...",
118
119
  " ouro friend list [--agent <name>]",
119
120
  " ouro friend show <id> [--agent <name>]",
@@ -1643,12 +1644,13 @@ function parseMigrateToDeskCommand(args) {
1643
1644
  }
1644
1645
  function parseBlueBubblesCommand(args) {
1645
1646
  const subcommand = args[0];
1646
- if (subcommand !== "replay") {
1647
+ if (subcommand !== "replay" && subcommand !== "context-smoke") {
1647
1648
  throw new Error(`Usage\n${usage()}`);
1648
1649
  }
1649
1650
  let agent;
1650
1651
  let messageGuid;
1651
1652
  let eventType = "new-message";
1653
+ let persist = false;
1652
1654
  let json = false;
1653
1655
  for (let i = 1; i < args.length; i += 1) {
1654
1656
  if (args[i] === "--agent" && args[i + 1]) {
@@ -1667,13 +1669,26 @@ function parseBlueBubblesCommand(args) {
1667
1669
  eventType = candidate;
1668
1670
  continue;
1669
1671
  }
1672
+ if (args[i] === "--persist") {
1673
+ persist = true;
1674
+ continue;
1675
+ }
1670
1676
  if (args[i] === "--json") {
1671
1677
  json = true;
1672
1678
  continue;
1673
1679
  }
1674
1680
  }
1675
1681
  if (!messageGuid)
1676
- throw new Error("bluebubbles replay requires --message-guid <guid>");
1682
+ throw new Error(`bluebubbles ${subcommand} requires --message-guid <guid>`);
1683
+ if (subcommand === "context-smoke") {
1684
+ return {
1685
+ kind: "bluebubbles.context-smoke",
1686
+ ...(agent ? { agent } : {}),
1687
+ messageGuid,
1688
+ ...(persist ? { persist: true } : {}),
1689
+ ...(json ? { json: true } : {}),
1690
+ };
1691
+ }
1677
1692
  return {
1678
1693
  kind: "bluebubbles.replay",
1679
1694
  ...(agent ? { agent } : {}),
@@ -63,6 +63,7 @@ const pulse_1 = require("./pulse");
63
63
  const socket_client_1 = require("./socket-client");
64
64
  const await_private_wake_1 = require("./await-private-wake");
65
65
  const habit_private_wake_1 = require("./habit-private-wake");
66
+ const habit_policy_1 = require("../../rsvp/habit-policy");
66
67
  const bundle_manifest_1 = require("../../mind/bundle-manifest");
67
68
  const mcp_canary_1 = require("./mcp-canary");
68
69
  const context_loss_sentinel_1 = require("../context-loss-sentinel");
@@ -629,6 +630,25 @@ void daemon.start().then(async () => {
629
630
  habitsDir,
630
631
  osCronManager,
631
632
  onHabitFire: (habitName, trigger, context) => {
633
+ if ((0, habit_policy_1.isRsvpHabitName)(habitName)) {
634
+ (0, socket_client_1.sendDaemonCommand)(socketPath, {
635
+ kind: "habit.poke",
636
+ agent,
637
+ habitName,
638
+ trigger,
639
+ ...(context?.occurrenceId ? { occurrenceId: context.occurrenceId } : {}),
640
+ }).catch((error) => {
641
+ emitHabitPrivateWakeDispatchError({
642
+ agent,
643
+ habitName,
644
+ trigger,
645
+ triggerSource: `habit-${trigger}`,
646
+ socketPath,
647
+ error,
648
+ });
649
+ });
650
+ return;
651
+ }
632
652
  const command = (0, habit_private_wake_1.buildHabitPrivateWakeCommand)({
633
653
  agent,
634
654
  habitName,
@@ -567,6 +567,7 @@ class OuroDaemon {
567
567
  privateRuntimePolicyDeps;
568
568
  onStopCommandComplete;
569
569
  externalEventRoot;
570
+ rsvpHabitRunner;
570
571
  constructor(options) {
571
572
  this.socketPath = options.socketPath;
572
573
  this.processManager = options.processManager;
@@ -580,6 +581,7 @@ class OuroDaemon {
580
581
  this.privateRuntimePolicyDeps = options.privateRuntimePolicyDeps ?? {};
581
582
  this.onStopCommandComplete = options.onStopCommandComplete ?? null;
582
583
  this.externalEventRoot = options.externalEventRoot ?? null;
584
+ this.rsvpHabitRunner = options.rsvpHabitRunner;
583
585
  }
584
586
  /* v8 ignore start -- default mailbox server wiring: production-only path, tests inject mailboxServerFactory stub instead. startMailboxHttpServer itself has full coverage in mailbox-http.test.ts @preserve */
585
587
  createDefaultMailboxServer() {
@@ -1226,6 +1228,8 @@ class OuroDaemon {
1226
1228
  }
1227
1229
  if (trigger === "poke" || trigger === "manual")
1228
1230
  return undefined;
1231
+ if (typeof command.occurrenceId === "string" && command.occurrenceId.trim().length > 0)
1232
+ return command.occurrenceId.trim();
1229
1233
  if (!fs.existsSync(habitPath))
1230
1234
  return { skipReason: "habit file not found" };
1231
1235
  const habit = parsedHabit ?? (0, habit_runtime_state_1.applyHabitRuntimeState)(agentRoot, (0, habit_parser_1.parseHabitFile)(fs.readFileSync(habitPath, "utf-8"), habitPath));
@@ -1236,6 +1240,24 @@ class OuroDaemon {
1236
1240
  return `${trigger}:first-run:${cadence}`;
1237
1241
  return `${trigger}:last-run:${habit.lastRun}:cadence:${cadence}`;
1238
1242
  }
1243
+ async runNativeRsvpHabit(command, trigger, occurrenceId) {
1244
+ const runner = this.rsvpHabitRunner ?? (async (input) => {
1245
+ const { runNativeRsvpHabit } = await Promise.resolve().then(() => __importStar(require("../../rsvp/native-habit-runner")));
1246
+ return runNativeRsvpHabit(input);
1247
+ });
1248
+ const result = await runner({
1249
+ agent: command.agent,
1250
+ bundlesRoot: this.bundlesRoot,
1251
+ habitName: command.habitName,
1252
+ trigger,
1253
+ ...(occurrenceId ? { occurrenceId } : {}),
1254
+ });
1255
+ return {
1256
+ ok: result.ok,
1257
+ message: result.message,
1258
+ data: result,
1259
+ };
1260
+ }
1239
1261
  buildPrivateRuntimeWorkerWakeMessage(command, decision) {
1240
1262
  const awaitName = (0, await_private_wake_1.awaitNameFromPrivateWakeCommand)(command);
1241
1263
  if (awaitName) {
@@ -1635,7 +1657,7 @@ class OuroDaemon {
1635
1657
  if (!(0, flight_recorder_1.isHabitRunTrigger)(trigger)) {
1636
1658
  return { ok: false, error: `invalid habit trigger: ${String(trigger)}` };
1637
1659
  }
1638
- if (!this.hasManagedPrivateRuntime(command.agent)) {
1660
+ if (!(0, habit_policy_1.isRsvpHabitName)(command.habitName) && !this.hasManagedPrivateRuntime(command.agent)) {
1639
1661
  return {
1640
1662
  ok: false,
1641
1663
  error: `No managed agent '${command.agent}' is registered with daemon-managed private runtime.`,
@@ -1648,6 +1670,9 @@ class OuroDaemon {
1648
1670
  message: `skipped scheduled habit ${command.habitName} for ${command.agent}: ${occurrenceId.skipReason}`,
1649
1671
  };
1650
1672
  }
1673
+ if ((0, habit_policy_1.isRsvpHabitName)(command.habitName)) {
1674
+ return this.runNativeRsvpHabit(command, trigger, occurrenceId);
1675
+ }
1651
1676
  return this.handlePrivateRuntimeWake((0, habit_private_wake_1.buildHabitPrivateWakeCommand)({
1652
1677
  agent: command.agent,
1653
1678
  habitName: command.habitName,
@@ -0,0 +1,321 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runNativeRsvpHabit = runNativeRsvpHabit;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const node_crypto_1 = require("node:crypto");
40
+ const habit_parser_1 = require("../heart/habits/habit-parser");
41
+ const habit_runtime_state_1 = require("../heart/habits/habit-runtime-state");
42
+ const habit_session_1 = require("../heart/habits/habit-session");
43
+ const run_ledger_1 = require("../heart/run-ledger");
44
+ const spend_ledger_1 = require("./spend-ledger");
45
+ const habit_policy_1 = require("./habit-policy");
46
+ const runtime_1 = require("../nerves/runtime");
47
+ function sha256Hex(input) {
48
+ return (0, node_crypto_1.createHash)("sha256").update(input).digest("hex");
49
+ }
50
+ function nowIso(now) {
51
+ const value = now ? now() : new Date();
52
+ return value instanceof Date ? value.toISOString() : value;
53
+ }
54
+ function nativeRunId(input) {
55
+ const digest = sha256Hex(JSON.stringify({
56
+ agent: input.agent,
57
+ habitName: input.habitName,
58
+ trigger: input.trigger,
59
+ occurrenceId: input.occurrenceId ?? null,
60
+ })).slice(0, 24);
61
+ return `rsvp-${input.trigger}-${digest}`;
62
+ }
63
+ function noopCliDeps(input, agentRoot) {
64
+ return {
65
+ socketPath: "",
66
+ sendCommand: async () => ({ ok: false, error: "daemon socket unavailable in native RSVP habit runner" }),
67
+ startDaemonProcess: async () => ({ pid: null }),
68
+ writeStdout: () => undefined,
69
+ checkSocketAlive: async () => false,
70
+ cleanupStaleSocket: () => undefined,
71
+ fallbackPendingMessage: () => "daemon socket unavailable in native RSVP habit runner",
72
+ bundlesRoot: input.bundlesRoot,
73
+ agentBundleRoot: agentRoot,
74
+ };
75
+ }
76
+ async function defaultRunRefresh(command, deps) {
77
+ const { runRsvpCliCommand } = await Promise.resolve().then(() => __importStar(require("./cli")));
78
+ return runRsvpCliCommand(command, deps);
79
+ }
80
+ function parseRefreshPayload(text) {
81
+ const parsed = JSON.parse(text);
82
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
83
+ throw new Error("RSVP refresh returned a non-object payload");
84
+ }
85
+ return parsed;
86
+ }
87
+ function refreshCommandFor(input, sendAllowed, mode) {
88
+ return {
89
+ kind: "rsvp.refresh",
90
+ agent: input.agent,
91
+ mode,
92
+ json: true,
93
+ ...(sendAllowed ? { allowSend: true } : { noSend: true }),
94
+ };
95
+ }
96
+ function permissionEnvelope(sendAllowed) {
97
+ return {
98
+ schemaVersion: 1,
99
+ canMessageOutward: sendAllowed,
100
+ returnRoutes: sendAllowed
101
+ ? [{ kind: "family", recipient: "family", status: "allowed" }]
102
+ : [],
103
+ deniedTools: sendAllowed ? [] : ["send_message", "surface"],
104
+ warnings: [],
105
+ };
106
+ }
107
+ function toolPolicy(sendAllowed) {
108
+ return {
109
+ requestedTools: [...habit_policy_1.RSVP_HABIT_ALLOWED_TOOLS],
110
+ grantedTools: [...habit_policy_1.RSVP_HABIT_ALLOWED_TOOLS],
111
+ deniedTools: [],
112
+ outwardMessagingAllowed: sendAllowed,
113
+ };
114
+ }
115
+ function stringField(record, key) {
116
+ const value = record[key];
117
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
118
+ }
119
+ function refreshRecord(payload) {
120
+ const value = payload.refresh;
121
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
122
+ }
123
+ function producedRefsFor(payload) {
124
+ if (payload.sendAllowed === true)
125
+ return [];
126
+ const snapshotId = stringField(refreshRecord(payload) ?? {}, "snapshotId");
127
+ return snapshotId ? [{ kind: "none", locator: `state/rsvp/snapshots/${snapshotId}.json` }] : [];
128
+ }
129
+ function surfaceAttemptsFor(payload, requestedSend, errorMessage) {
130
+ if (payload.sendAllowed === true) {
131
+ return [{
132
+ recipient: "rsvp",
133
+ channel: "bluebubbles",
134
+ reason: "status",
135
+ result: "sent",
136
+ }];
137
+ }
138
+ if (requestedSend && errorMessage) {
139
+ return [{
140
+ recipient: "rsvp",
141
+ channel: "bluebubbles",
142
+ reason: "blocked",
143
+ result: "blocked",
144
+ error: errorMessage,
145
+ }];
146
+ }
147
+ return [];
148
+ }
149
+ function payloadError(payload) {
150
+ if (payload.ok === false || typeof payload.requires === "string") {
151
+ return stringField(payload, "message") ?? "RSVP refresh did not complete";
152
+ }
153
+ return null;
154
+ }
155
+ function recordNativeRunLedger(input) {
156
+ const target = {
157
+ habitName: input.habitName,
158
+ runId: input.runId,
159
+ trigger: input.trigger,
160
+ occurrenceId: input.occurrenceId ?? null,
161
+ command: "rsvp.refresh",
162
+ };
163
+ const record = (0, run_ledger_1.createRunLedgerRecord)({
164
+ agent: input.agent,
165
+ triggerType: "habit",
166
+ sourceKind: "daemon",
167
+ senseOrHabit: input.habitName,
168
+ lifecycle: input.lifecycle,
169
+ startedAt: input.startedAt,
170
+ ...(input.endedAt ? { endedAt: input.endedAt } : {}),
171
+ ...(input.lifecycle === "completed" || input.lifecycle === "error"
172
+ ? { usage: (0, run_ledger_1.usageMetadataFromUsageData)(undefined, "none") }
173
+ : {}),
174
+ target,
175
+ idempotencyScope: target,
176
+ sessionRef: {
177
+ channel: "rsvp",
178
+ keyHash: `sha256:${sha256Hex(input.runId)}`,
179
+ },
180
+ });
181
+ (0, run_ledger_1.appendRunLedgerRecordNonFatal)(input.agentRoot, record);
182
+ try {
183
+ (0, spend_ledger_1.recordRsvpSpendLedgerRun)(input.agentRoot, record);
184
+ }
185
+ catch (error) {
186
+ (0, runtime_1.emitNervesEvent)({
187
+ level: "error",
188
+ component: "rsvp",
189
+ event: "rsvp.native_habit_spend_ledger_error",
190
+ message: "failed to record native RSVP habit spend ledger row",
191
+ meta: { runId: input.runId, lifecycle: input.lifecycle, error: String(error) },
192
+ });
193
+ }
194
+ }
195
+ async function runNativeRsvpHabit(input) {
196
+ const agentRoot = path.join(input.bundlesRoot, `${input.agent}.ouro`);
197
+ const habitPath = path.join(agentRoot, "habits", `${input.habitName}.md`);
198
+ const startedAt = nowIso(input.now);
199
+ const runId = nativeRunId(input);
200
+ (0, runtime_1.emitNervesEvent)({
201
+ component: "rsvp",
202
+ event: "rsvp.native_habit_start",
203
+ message: "native RSVP habit starting",
204
+ meta: {
205
+ agent: input.agent,
206
+ habitName: input.habitName,
207
+ trigger: input.trigger,
208
+ occurrenceId: input.occurrenceId ?? null,
209
+ runId,
210
+ },
211
+ });
212
+ const habit = (0, habit_runtime_state_1.applyHabitRuntimeState)(agentRoot, (0, habit_parser_1.parseHabitFile)(fs.readFileSync(habitPath, "utf-8"), habitPath));
213
+ if (!habit.rsvp)
214
+ throw new Error(`RSVP habit metadata is required before native execution: ${input.habitName}`);
215
+ const policy = (0, habit_policy_1.rsvpHabitRuntimePolicy)(habit.rsvp);
216
+ const command = refreshCommandFor(input, policy.sendAllowed, habit.rsvp.mode);
217
+ const deps = noopCliDeps(input, agentRoot);
218
+ recordNativeRunLedger({
219
+ agentRoot,
220
+ agent: input.agent,
221
+ habitName: input.habitName,
222
+ runId,
223
+ trigger: input.trigger,
224
+ occurrenceId: input.occurrenceId,
225
+ startedAt,
226
+ lifecycle: "started",
227
+ });
228
+ let payload;
229
+ let errorMessage = null;
230
+ try {
231
+ payload = parseRefreshPayload(await (input.runRefresh ?? defaultRunRefresh)(command, deps));
232
+ errorMessage = payloadError(payload);
233
+ }
234
+ catch (error) {
235
+ payload = {
236
+ ok: false,
237
+ command: "rsvp.refresh",
238
+ sideEffect: false,
239
+ agent: input.agent,
240
+ mode: habit.rsvp.mode,
241
+ message: error instanceof Error ? error.message : String(error),
242
+ };
243
+ errorMessage = stringField(payload, "message") ?? "RSVP refresh failed";
244
+ }
245
+ const endedAt = nowIso(input.now);
246
+ const lifecycle = errorMessage ? "error" : "completed";
247
+ const surfaceAttempts = surfaceAttemptsFor(payload, policy.sendAllowed, errorMessage);
248
+ const errors = errorMessage ? [errorMessage] : [];
249
+ recordNativeRunLedger({
250
+ agentRoot,
251
+ agent: input.agent,
252
+ habitName: input.habitName,
253
+ runId,
254
+ trigger: input.trigger,
255
+ occurrenceId: input.occurrenceId,
256
+ startedAt,
257
+ endedAt,
258
+ lifecycle,
259
+ });
260
+ const completion = (0, habit_session_1.completeHabitRun)({
261
+ agentRoot,
262
+ habit,
263
+ runId,
264
+ trigger: input.trigger,
265
+ startedAt,
266
+ endedAt,
267
+ operationId: `rsvp-native:${input.trigger}:${input.occurrenceId ?? runId}`,
268
+ permissionEnvelope: permissionEnvelope(policy.sendAllowed),
269
+ toolPolicy: toolPolicy(policy.sendAllowed),
270
+ producedRefs: producedRefsFor(payload),
271
+ surfaceAttempts,
272
+ errors,
273
+ summarySnapshot: {
274
+ summary: errorMessage
275
+ ? `Native RSVP habit failed: ${errorMessage}`
276
+ : "Native RSVP habit completed.",
277
+ decisions: [
278
+ `mode=${habit.rsvp.mode}`,
279
+ `sendAllowed=${String(policy.sendAllowed)}`,
280
+ ],
281
+ nextLikelyStep: null,
282
+ },
283
+ });
284
+ const ok = lifecycle === "completed" && completion.receiptWritten && completion.runtimeStateRecorded;
285
+ const message = ok
286
+ ? `native RSVP habit ${input.habitName} completed for ${input.agent}`
287
+ : `native RSVP habit ${input.habitName} failed for ${input.agent}: ${errorMessage ?? "runtime state was not recorded"}`;
288
+ const terminalEventMeta = {
289
+ agent: input.agent,
290
+ habitName: input.habitName,
291
+ trigger: input.trigger,
292
+ runId,
293
+ lifecycle,
294
+ receiptWritten: completion.receiptWritten,
295
+ runtimeStateRecorded: completion.runtimeStateRecorded,
296
+ };
297
+ if (lifecycle === "completed") {
298
+ (0, runtime_1.emitNervesEvent)({
299
+ component: "rsvp",
300
+ event: "rsvp.native_habit_end",
301
+ message,
302
+ meta: terminalEventMeta,
303
+ });
304
+ }
305
+ else {
306
+ (0, runtime_1.emitNervesEvent)({
307
+ level: "error",
308
+ component: "rsvp",
309
+ event: "rsvp.native_habit_error",
310
+ message,
311
+ meta: terminalEventMeta,
312
+ });
313
+ }
314
+ return {
315
+ ok,
316
+ message,
317
+ lifecycle,
318
+ runId,
319
+ payload,
320
+ };
321
+ }
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS = exports.BLUEBUBBLES_CONTEXT_PACKET_LIMIT = void 0;
4
+ exports.blueBubblesContextChatKey = blueBubblesContextChatKey;
5
+ exports.blueBubblesContextChatKeyHash = blueBubblesContextChatKeyHash;
6
+ exports.buildBlueBubblesContextPacket = buildBlueBubblesContextPacket;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const runtime_1 = require("../../nerves/runtime");
9
+ const context_packets_1 = require("../context-packets");
10
+ exports.BLUEBUBBLES_CONTEXT_PACKET_LIMIT = 40;
11
+ exports.BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS = 48 * 60 * 60 * 1000;
12
+ function sha256Hex(input) {
13
+ return (0, node_crypto_1.createHash)("sha256").update(input).digest("hex");
14
+ }
15
+ function blueBubblesContextChatKey(event) {
16
+ return event.chat.chatGuid ?? event.chat.chatIdentifier ?? event.chat.sessionKey;
17
+ }
18
+ function blueBubblesContextChatKeyHash(event) {
19
+ return sha256Hex(blueBubblesContextChatKey(event));
20
+ }
21
+ function isSameBlueBubblesChat(candidate, anchor) {
22
+ return candidate.chat.sessionKey === anchor.chat.sessionKey
23
+ || (!!candidate.chat.chatGuid && candidate.chat.chatGuid === anchor.chat.chatGuid)
24
+ || (!!candidate.chat.chatIdentifier && candidate.chat.chatIdentifier === anchor.chat.chatIdentifier);
25
+ }
26
+ function contextAuthorLabel(event) {
27
+ if (event.fromMe)
28
+ return "Slugger";
29
+ return event.sender.displayName || event.sender.externalId || "Unknown";
30
+ }
31
+ function messageToContextInput(event, chatGuidHash) {
32
+ return {
33
+ timestamp: new Date(event.timestamp).toISOString(),
34
+ authorLabel: contextAuthorLabel(event),
35
+ body: event.textForAgent || event.text,
36
+ sourceRef: {
37
+ sense: "bluebubbles",
38
+ adapter: "bluebubbles-api-v1",
39
+ service: "imessage",
40
+ chatGuid: event.chat.chatGuid,
41
+ chatGuidHash,
42
+ messageGuid: event.messageGuid,
43
+ senderExternalIdHash: sha256Hex(event.sender.externalId || event.sender.rawId || "unknown"),
44
+ observedAt: new Date(event.timestamp).toISOString(),
45
+ },
46
+ };
47
+ }
48
+ async function buildBlueBubblesContextPacket(input) {
49
+ if (!Number.isFinite(input.event.timestamp))
50
+ return null;
51
+ if (!input.client.listRecentMessages)
52
+ return null;
53
+ const event = input.event;
54
+ const chatGuidHash = blueBubblesContextChatKeyHash(event);
55
+ const anchorTimestamp = new Date(event.timestamp).toISOString();
56
+ const candidates = await input.client.listRecentMessages({
57
+ beforeTimestamp: event.timestamp,
58
+ limit: exports.BLUEBUBBLES_CONTEXT_PACKET_LIMIT,
59
+ offset: 0,
60
+ ...(event.chat.chatGuid ? { chatGuid: event.chat.chatGuid } : {}),
61
+ ...(!event.chat.chatGuid && event.chat.chatIdentifier ? { chatIdentifier: event.chat.chatIdentifier } : {}),
62
+ });
63
+ const history = candidates
64
+ .filter((candidate) => candidate.kind === "message")
65
+ .filter((candidate) => isSameBlueBubblesChat(candidate, event))
66
+ .filter((candidate) => candidate.messageGuid !== event.messageGuid)
67
+ .filter((candidate) => candidate.timestamp <= event.timestamp)
68
+ .filter((candidate) => event.timestamp - candidate.timestamp <= exports.BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS);
69
+ if (history.length === 0)
70
+ return null;
71
+ const packet = (0, context_packets_1.buildSenseContextPacket)({
72
+ agent: input.agentName,
73
+ sense: "bluebubbles",
74
+ sessionKey: event.chat.sessionKey,
75
+ chatKeyHash: chatGuidHash,
76
+ anchorMessageGuid: event.messageGuid,
77
+ anchorTimestamp,
78
+ windowBeforeMessages: exports.BLUEBUBBLES_CONTEXT_PACKET_LIMIT,
79
+ windowBeforeMs: exports.BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS,
80
+ messages: history.map((candidate) => messageToContextInput(candidate, chatGuidHash)),
81
+ });
82
+ const rendered = (0, context_packets_1.renderSenseContextPacketForPrompt)(packet, {
83
+ redactionPatterns: [/password=[^\s]+/gi],
84
+ });
85
+ (0, runtime_1.emitNervesEvent)({
86
+ component: "senses",
87
+ event: "senses.bluebubbles_context_packet_built",
88
+ message: "built bluebubbles same-chat context packet",
89
+ meta: {
90
+ packetId: packet.packetId,
91
+ messageGuid: event.messageGuid,
92
+ contextMessages: history.length,
93
+ renderedMessages: rendered.stats.renderedMessages,
94
+ },
95
+ });
96
+ return {
97
+ packet,
98
+ rendered,
99
+ historyCount: history.length,
100
+ };
101
+ }
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.smokeBlueBubblesContext = smokeBlueBubblesContext;
4
+ exports.formatBlueBubblesContextSmokeText = formatBlueBubblesContextSmokeText;
5
+ const machine_identity_1 = require("../../heart/machine-identity");
6
+ const identity_1 = require("../../heart/identity");
7
+ const runtime_credentials_1 = require("../../heart/runtime-credentials");
8
+ const runtime_1 = require("../../nerves/runtime");
9
+ const context_packet_ledger_1 = require("../context-packet-ledger");
10
+ const client_1 = require("./client");
11
+ const context_packet_1 = require("./context-packet");
12
+ const model_1 = require("./model");
13
+ function requireMessageEvent(event, messageGuid) {
14
+ if (event.kind !== "message") {
15
+ throw new Error(`BlueBubbles context smoke requires a message event; ${messageGuid} repaired to ${event.kind}`);
16
+ }
17
+ }
18
+ function smokeResult(params, writeResult) {
19
+ return {
20
+ ok: true,
21
+ sideEffect: writeResult ? "private-runtime-ledger-write" : false,
22
+ ...params,
23
+ ...(writeResult ? {
24
+ ledgerPath: writeResult.ledgerPath,
25
+ packetPath: writeResult.packetPath,
26
+ receiptPath: writeResult.receiptPath,
27
+ } : {}),
28
+ };
29
+ }
30
+ async function smokeBlueBubblesContext(params, deps = {}) {
31
+ const agentName = params.agentName.trim();
32
+ const messageGuid = params.messageGuid.trim();
33
+ if (!agentName)
34
+ throw new Error("bluebubbles context-smoke requires agentName");
35
+ if (!messageGuid)
36
+ throw new Error("bluebubbles context-smoke requires messageGuid");
37
+ const setSmokeAgentName = deps.setAgentName ?? identity_1.setAgentName;
38
+ const resetSmokeIdentity = deps.resetIdentity ?? identity_1.resetIdentity;
39
+ const normalizeEvent = deps.normalizeEvent ?? model_1.normalizeBlueBubblesEvent;
40
+ const loadMachineId = deps.loadMachineId ?? (() => (0, machine_identity_1.loadOrCreateMachineIdentity)().machineId);
41
+ const refreshMachineRuntimeConfig = deps.refreshMachineRuntimeConfig ?? runtime_credentials_1.refreshMachineRuntimeCredentialConfig;
42
+ const resolveAgentRoot = deps.getAgentRoot ?? identity_1.getAgentRoot;
43
+ const writePacket = deps.writePacket ?? context_packet_ledger_1.writeSenseContextPacket;
44
+ const createDefaultClient = deps.createDefaultClient ?? client_1.createBlueBubblesClient;
45
+ (0, runtime_1.emitNervesEvent)({
46
+ component: "senses",
47
+ event: "senses.bluebubbles_context_smoke_start",
48
+ message: "starting bluebubbles context smoke",
49
+ meta: {
50
+ agentName,
51
+ messageGuid,
52
+ persist: params.persist === true,
53
+ },
54
+ });
55
+ setSmokeAgentName(agentName);
56
+ try {
57
+ if (!deps.createClient) {
58
+ const machineId = loadMachineId();
59
+ await refreshMachineRuntimeConfig(agentName, machineId, { preserveCachedOnFailure: true });
60
+ }
61
+ const client = deps.createClient ? deps.createClient() : createDefaultClient();
62
+ const probe = normalizeEvent({
63
+ type: "new-message",
64
+ data: {
65
+ guid: messageGuid,
66
+ hasPayloadData: true,
67
+ },
68
+ });
69
+ const event = await client.repairEvent(probe);
70
+ requireMessageEvent(event, messageGuid);
71
+ const built = await (0, context_packet_1.buildBlueBubblesContextPacket)({
72
+ agentName,
73
+ client,
74
+ event,
75
+ });
76
+ if (!built) {
77
+ throw new Error(`BlueBubbles context smoke found no same-thread history before ${messageGuid}`);
78
+ }
79
+ const writeResult = params.persist
80
+ ? writePacket(resolveAgentRoot(agentName), built.packet)
81
+ : undefined;
82
+ const result = smokeResult({
83
+ agentName,
84
+ messageGuid,
85
+ packetId: built.packet.packetId,
86
+ contextMessages: built.historyCount,
87
+ renderedMessages: built.rendered.stats.renderedMessages,
88
+ renderedCharacters: built.rendered.stats.outputCharacters,
89
+ omittedMessages: built.rendered.stats.omittedMessages,
90
+ truncatedMessages: built.rendered.stats.truncatedMessages,
91
+ }, writeResult);
92
+ (0, runtime_1.emitNervesEvent)({
93
+ component: "senses",
94
+ event: "senses.bluebubbles_context_smoke_end",
95
+ message: "completed bluebubbles context smoke",
96
+ meta: {
97
+ agentName,
98
+ messageGuid,
99
+ packetId: result.packetId,
100
+ contextMessages: result.contextMessages,
101
+ sideEffect: result.sideEffect,
102
+ },
103
+ });
104
+ return result;
105
+ }
106
+ catch (error) {
107
+ (0, runtime_1.emitNervesEvent)({
108
+ level: "warn",
109
+ component: "senses",
110
+ event: "senses.bluebubbles_context_smoke_error",
111
+ message: "bluebubbles context smoke failed",
112
+ meta: {
113
+ agentName,
114
+ messageGuid,
115
+ reason: error instanceof Error ? error.message : String(error),
116
+ },
117
+ });
118
+ throw error;
119
+ }
120
+ finally {
121
+ resetSmokeIdentity();
122
+ }
123
+ }
124
+ function formatBlueBubblesContextSmokeText(result) {
125
+ const lines = [
126
+ "bluebubbles context smoke passed",
127
+ `agent: ${result.agentName}`,
128
+ `message_guid: ${result.messageGuid}`,
129
+ `context_messages: ${result.contextMessages}`,
130
+ `packet_id: ${result.packetId}`,
131
+ `rendered_messages: ${result.renderedMessages}`,
132
+ `rendered_characters: ${result.renderedCharacters}`,
133
+ `truncated_messages: ${result.truncatedMessages}`,
134
+ `omitted_messages: ${result.omittedMessages}`,
135
+ `side_effect: ${result.sideEffect}`,
136
+ ];
137
+ if (result.ledgerPath)
138
+ lines.push(`ledger_path: ${result.ledgerPath}`);
139
+ if (result.packetPath)
140
+ lines.push(`packet_path: ${result.packetPath}`);
141
+ if (result.receiptPath)
142
+ lines.push(`receipt_path: ${result.receiptPath}`);
143
+ return lines.join("\n");
144
+ }
@@ -86,6 +86,7 @@ const trust_gate_1 = require("../trust-gate");
86
86
  const pipeline_1 = require("../pipeline");
87
87
  const context_packets_1 = require("../context-packets");
88
88
  const context_packet_ledger_1 = require("../context-packet-ledger");
89
+ const context_packet_1 = require("./context-packet");
89
90
  const autonomy_budget_1 = require("../../heart/autonomy-budget");
90
91
  const bbFailoverStates = new Map();
91
92
  /**
@@ -263,8 +264,6 @@ const BLUEBUBBLES_LIVE_TURN_STALLED_MS = 90_000;
263
264
  const BLUEBUBBLES_SILENCE_WATCHDOG_MS = 75_000;
264
265
  const BLUEBUBBLES_CALLBACK_CLEANUP_TIMEOUT_MS = 2_000;
265
266
  const BLUEBUBBLES_ACTIVITY_OPERATION_TIMEOUT_MS = 20_000;
266
- const BLUEBUBBLES_CONTEXT_PACKET_LIMIT = 40;
267
- const BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS = 48 * 60 * 60 * 1000;
268
267
  const BLUEBUBBLES_CATCHUP_PAGE_SIZE = 50;
269
268
  const BLUEBUBBLES_CATCHUP_MAX_PAGES = 20;
270
269
  const BLUEBUBBLES_HEALTHY_CATCHUP_OVERLAP_MS = 90_000;
@@ -614,36 +613,6 @@ function recordBlueBubblesRecoveryFailureForBudget(agentName, event, source) {
614
613
  // Autonomy failure accounting must not turn a recovery error into a broader sense outage.
615
614
  }
616
615
  }
617
- function chatKeyForContext(event) {
618
- return event.chat.chatGuid ?? event.chat.chatIdentifier ?? event.chat.sessionKey;
619
- }
620
- function isSameBlueBubblesChat(candidate, anchor) {
621
- return candidate.chat.sessionKey === anchor.chat.sessionKey
622
- || (!!candidate.chat.chatGuid && candidate.chat.chatGuid === anchor.chat.chatGuid)
623
- || (!!candidate.chat.chatIdentifier && candidate.chat.chatIdentifier === anchor.chat.chatIdentifier);
624
- }
625
- function contextAuthorLabel(event) {
626
- if (event.fromMe)
627
- return "Slugger";
628
- return event.sender.displayName || event.sender.externalId || "Unknown";
629
- }
630
- function messageToContextInput(event, chatGuidHash) {
631
- return {
632
- timestamp: new Date(event.timestamp).toISOString(),
633
- authorLabel: contextAuthorLabel(event),
634
- body: event.textForAgent || event.text,
635
- sourceRef: {
636
- sense: "bluebubbles",
637
- adapter: "bluebubbles-api-v1",
638
- service: "imessage",
639
- chatGuid: event.chat.chatGuid,
640
- chatGuidHash,
641
- messageGuid: event.messageGuid,
642
- senderExternalIdHash: sha256Hex(event.sender.externalId || event.sender.rawId || "unknown"),
643
- observedAt: new Date(event.timestamp).toISOString(),
644
- },
645
- };
646
- }
647
616
  function insertEphemeralContextMessages(messages, contextMessages) {
648
617
  if (contextMessages.length === 0)
649
618
  return messages;
@@ -660,43 +629,18 @@ async function buildBlueBubblesContextMessages(input) {
660
629
  const event = input.event;
661
630
  if (!Number.isFinite(event.timestamp))
662
631
  return [];
663
- const chatKey = chatKeyForContext(event);
664
- const chatGuidHash = sha256Hex(chatKey);
632
+ const chatGuidHash = (0, context_packet_1.blueBubblesContextChatKeyHash)(event);
665
633
  const anchorTimestamp = new Date(event.timestamp).toISOString();
666
634
  try {
667
- const query = {
668
- beforeTimestamp: event.timestamp,
669
- limit: BLUEBUBBLES_CONTEXT_PACKET_LIMIT,
670
- offset: 0,
671
- ...(event.chat.chatGuid ? { chatGuid: event.chat.chatGuid } : {}),
672
- ...(!event.chat.chatGuid && event.chat.chatIdentifier ? { chatIdentifier: event.chat.chatIdentifier } : {}),
673
- };
674
- const candidates = input.client.listRecentMessages
675
- ? await input.client.listRecentMessages(query)
676
- : [];
677
- const history = candidates
678
- .filter((candidate) => candidate.kind === "message")
679
- .filter((candidate) => isSameBlueBubblesChat(candidate, event))
680
- .filter((candidate) => candidate.messageGuid !== event.messageGuid)
681
- .filter((candidate) => candidate.timestamp <= event.timestamp)
682
- .filter((candidate) => event.timestamp - candidate.timestamp <= BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS);
683
- if (history.length === 0)
684
- return [];
685
- const packet = (0, context_packets_1.buildSenseContextPacket)({
686
- agent: input.agentName,
687
- sense: "bluebubbles",
688
- sessionKey: event.chat.sessionKey,
689
- chatKeyHash: chatGuidHash,
690
- anchorMessageGuid: event.messageGuid,
691
- anchorTimestamp,
692
- windowBeforeMessages: BLUEBUBBLES_CONTEXT_PACKET_LIMIT,
693
- windowBeforeMs: BLUEBUBBLES_CONTEXT_PACKET_MAX_AGE_MS,
694
- messages: history.map((candidate) => messageToContextInput(candidate, chatGuidHash)),
635
+ const result = await (0, context_packet_1.buildBlueBubblesContextPacket)({
636
+ agentName: input.agentName,
637
+ client: input.client,
638
+ event,
695
639
  });
640
+ if (!result)
641
+ return [];
642
+ const { packet, rendered, historyCount } = result;
696
643
  (0, context_packet_ledger_1.writeSenseContextPacket)(input.agentRoot, packet);
697
- const rendered = (0, context_packets_1.renderSenseContextPacketForPrompt)(packet, {
698
- redactionPatterns: [/password=[^\s]+/gi],
699
- });
700
644
  (0, runtime_1.emitNervesEvent)({
701
645
  component: "senses",
702
646
  event: "senses.bluebubbles_context_packet_injected",
@@ -704,7 +648,7 @@ async function buildBlueBubblesContextMessages(input) {
704
648
  meta: {
705
649
  packetId: packet.packetId,
706
650
  messageGuid: event.messageGuid,
707
- contextMessages: history.length,
651
+ contextMessages: historyCount,
708
652
  },
709
653
  });
710
654
  return [{ role: "system", content: rendered.text }];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.704",
3
+ "version": "0.1.0-alpha.706",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",