@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 +12 -0
- package/dist/heart/daemon/cli-exec.js +14 -0
- package/dist/heart/daemon/cli-help.js +6 -3
- package/dist/heart/daemon/cli-parse.js +17 -2
- package/dist/heart/daemon/daemon-entry.js +20 -0
- package/dist/heart/daemon/daemon.js +26 -1
- package/dist/rsvp/native-habit-runner.js +321 -0
- package/dist/senses/bluebubbles/context-packet.js +101 -0
- package/dist/senses/bluebubbles/context-smoke.js +144 -0
- package/dist/senses/bluebubbles/index.js +10 -66
- package/package.json +1 -1
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:
|
|
363
|
-
|
|
364
|
-
|
|
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(
|
|
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
|
|
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
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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:
|
|
651
|
+
contextMessages: historyCount,
|
|
708
652
|
},
|
|
709
653
|
});
|
|
710
654
|
return [{ role: "system", content: rendered.text }];
|