@ouro.bot/cli 0.1.0-alpha.705 → 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,12 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.706",
6
+ "changes": [
7
+ "Route native RSVP habit checks through typed refresh execution instead of private-runtime wake policy."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.705",
6
12
  "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,
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.705",
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",