@ouro.bot/cli 0.1.0-alpha.705 → 0.1.0-alpha.707
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/daemon-entry.js +20 -0
- package/dist/heart/daemon/daemon.js +26 -1
- package/dist/rsvp/cli.js +48 -2
- package/dist/rsvp/native-habit-runner.js +321 -0
- 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.707",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Build RSVP BlueBubbles send clients from explicit agent runtime config during native refresh and smoke runs."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.706",
|
|
12
|
+
"changes": [
|
|
13
|
+
"Route native RSVP habit checks through typed refresh execution instead of private-runtime wake policy."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
4
16
|
{
|
|
5
17
|
"version": "0.1.0-alpha.705",
|
|
6
18
|
"changes": [
|
|
@@ -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,
|
package/dist/rsvp/cli.js
CHANGED
|
@@ -211,6 +211,50 @@ function blueBubblesChatFor(config) {
|
|
|
211
211
|
participantHandles: [],
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
|
+
function numberField(record, key, fallback) {
|
|
215
|
+
const value = record?.[key];
|
|
216
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
217
|
+
}
|
|
218
|
+
function stringArrayField(record, key) {
|
|
219
|
+
const value = record?.[key];
|
|
220
|
+
if (!Array.isArray(value))
|
|
221
|
+
return [];
|
|
222
|
+
return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
223
|
+
}
|
|
224
|
+
function recordField(record, key) {
|
|
225
|
+
const value = record[key];
|
|
226
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
227
|
+
? value
|
|
228
|
+
: null;
|
|
229
|
+
}
|
|
230
|
+
function rsvpBlueBubblesClientConfig(nativeConfig, machineRuntimeConfig) {
|
|
231
|
+
if (!machineRuntimeConfig.ok) {
|
|
232
|
+
throw new Error(`BlueBubbles runtime config unavailable at ${machineRuntimeConfig.itemPath}: ${machineRuntimeConfig.error}`);
|
|
233
|
+
}
|
|
234
|
+
const bluebubbles = recordField(machineRuntimeConfig.config, "bluebubbles");
|
|
235
|
+
const bluebubblesChannel = recordField(machineRuntimeConfig.config, "bluebubblesChannel");
|
|
236
|
+
const serverUrl = localText(bluebubbles?.serverUrl);
|
|
237
|
+
const password = localText(bluebubbles?.password);
|
|
238
|
+
if (!serverUrl) {
|
|
239
|
+
throw new Error("bluebubbles.serverUrl is required in this machine's agent-vault runtime config. Run `ouro connect bluebubbles --agent <agent>`.");
|
|
240
|
+
}
|
|
241
|
+
if (!password) {
|
|
242
|
+
throw new Error("bluebubbles.password is required in this machine's agent-vault runtime config. Run `ouro connect bluebubbles --agent <agent>`.");
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
config: {
|
|
246
|
+
serverUrl,
|
|
247
|
+
password,
|
|
248
|
+
accountId: localText(bluebubbles?.accountId) || localText(nativeConfig.bluebubblesRoute.accountId) || "default",
|
|
249
|
+
ownHandles: stringArrayField(bluebubbles, "ownHandles"),
|
|
250
|
+
},
|
|
251
|
+
channelConfig: {
|
|
252
|
+
port: numberField(bluebubblesChannel, "port", 18790),
|
|
253
|
+
webhookPath: localText(bluebubblesChannel?.webhookPath) || "/bluebubbles-webhook",
|
|
254
|
+
requestTimeoutMs: numberField(bluebubblesChannel, "requestTimeoutMs", 30_000),
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
214
258
|
function normalizeDelivery(value) {
|
|
215
259
|
const record = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
216
260
|
return {
|
|
@@ -552,7 +596,8 @@ async function executeRefresh(command, deps) {
|
|
|
552
596
|
const sendAllowed = wantsLiveSend;
|
|
553
597
|
let delivery;
|
|
554
598
|
if (sendAllowed) {
|
|
555
|
-
const
|
|
599
|
+
const bluebubbles = rsvpBlueBubblesClientConfig(configResult.config, machineRuntimeConfig);
|
|
600
|
+
const sent = await (0, client_1.createBlueBubblesClient)(bluebubbles.config, bluebubbles.channelConfig).sendText({
|
|
556
601
|
chat: blueBubblesChatFor(configResult.config),
|
|
557
602
|
text: reportText,
|
|
558
603
|
tempGuid: outboundDecision.idempotencyKey,
|
|
@@ -658,7 +703,8 @@ async function executeSmoke(command, deps) {
|
|
|
658
703
|
const sendAllowed = wantsLiveSend;
|
|
659
704
|
let delivery;
|
|
660
705
|
if (sendAllowed) {
|
|
661
|
-
const
|
|
706
|
+
const bluebubbles = rsvpBlueBubblesClientConfig(configResult.config, await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(command.agent, machineIdForCli(), { preserveCachedOnFailure: true }));
|
|
707
|
+
const sent = await (0, client_1.createBlueBubblesClient)(bluebubbles.config, bluebubbles.channelConfig).sendText({
|
|
662
708
|
chat: blueBubblesChatFor(configResult.config),
|
|
663
709
|
text: answer.text,
|
|
664
710
|
});
|
|
@@ -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
|
+
}
|