@ouro.bot/cli 0.1.0-alpha.666 → 0.1.0-alpha.667
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 +7 -0
- package/dist/arc/flight-recorder.js +58 -2
- package/dist/heart/daemon/cli-exec.js +90 -1
- package/dist/heart/daemon/cli-help.js +12 -1
- package/dist/heart/daemon/cli-parse.js +102 -0
- package/dist/heart/habits/habit-parser.js +8 -0
- package/dist/heart/habits/habit-runtime-state.js +17 -3
- package/dist/heart/habits/habit-session-summary.js +318 -0
- package/dist/heart/habits/habit-session.js +62 -7
- package/dist/heart/mailbox/mailbox-http-hooks.js +27 -1
- package/dist/heart/mailbox/mailbox-http-routes.js +82 -1
- package/dist/heart/mailbox/mailbox-read.js +3 -1
- package/dist/heart/mailbox/readers/runtime-readers.js +31 -0
- package/dist/repertoire/tools-session.js +126 -0
- package/dist/senses/habit-turn-message.js +41 -3
- package/dist/senses/inner-dialog-worker.js +113 -1
- package/dist/senses/inner-dialog.js +24 -12
- package/dist/senses/pipeline.js +2 -2
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
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.667",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Add stateful habit session summaries, summary tooling, mailbox APIs, nerves review, and habit history visibility.",
|
|
8
|
+
"Harden habit summary receipts as the canonical summary snapshot, validate agent-scoped Mailbox routes, await post-turn persistence, and preserve session-summary recovery through malformed locators and projected session edge cases."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
4
11
|
{
|
|
5
12
|
"version": "0.1.0-alpha.666",
|
|
6
13
|
"changes": [
|
|
@@ -397,6 +397,52 @@ function isHabitToolPolicy(value) {
|
|
|
397
397
|
&& isStringArray(value.deniedTools)
|
|
398
398
|
&& typeof value.outwardMessagingAllowed === "boolean";
|
|
399
399
|
}
|
|
400
|
+
function defaultHabitRunSummarySnapshot(receipt) {
|
|
401
|
+
if (receipt.errors.length > 0) {
|
|
402
|
+
return {
|
|
403
|
+
summary: `Habit ${receipt.habitName} finished with errors: ${receipt.errors.join("; ")}`,
|
|
404
|
+
decisions: [],
|
|
405
|
+
nextLikelyStep: null,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
const surface = receipt.surfaceAttempts.find((attempt) => attempt.result !== "blocked" && attempt.result !== "failed" && attempt.result !== "unavailable");
|
|
409
|
+
if (surface) {
|
|
410
|
+
return {
|
|
411
|
+
summary: `Habit ${receipt.habitName} surfaced via ${surface.recipient}/${surface.channel}.`,
|
|
412
|
+
decisions: [],
|
|
413
|
+
nextLikelyStep: null,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
const produced = receipt.producedRefs.find((ref) => ref.kind !== "none");
|
|
417
|
+
if (produced) {
|
|
418
|
+
return {
|
|
419
|
+
summary: `Habit ${receipt.habitName} produced ${produced.kind}: ${produced.locator}.`,
|
|
420
|
+
decisions: [],
|
|
421
|
+
nextLikelyStep: null,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
summary: `Habit ${receipt.habitName} finished with ${receipt.outcome}.`,
|
|
426
|
+
decisions: [],
|
|
427
|
+
nextLikelyStep: null,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function normalizeHabitRunSummarySnapshot(value, fallback) {
|
|
431
|
+
const snapshot = isPlainRecord(value) ? value : {};
|
|
432
|
+
const summary = typeof snapshot.summary === "string" && snapshot.summary.trim().length > 0
|
|
433
|
+
? snapshot.summary
|
|
434
|
+
: fallback.summary;
|
|
435
|
+
const nextLikelyStep = snapshot.nextLikelyStep === null
|
|
436
|
+
? null
|
|
437
|
+
: typeof snapshot.nextLikelyStep === "string" && snapshot.nextLikelyStep.trim().length > 0
|
|
438
|
+
? snapshot.nextLikelyStep
|
|
439
|
+
: fallback.nextLikelyStep;
|
|
440
|
+
return {
|
|
441
|
+
summary: (0, session_events_1.capStructuredRecordString)(summary),
|
|
442
|
+
decisions: cappedArray(isStringArray(snapshot.decisions) ? snapshot.decisions : fallback.decisions),
|
|
443
|
+
nextLikelyStep: nextLikelyStep === null ? null : (0, session_events_1.capStructuredRecordString)(nextLikelyStep),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
400
446
|
function isHabitRunReceipt(value) {
|
|
401
447
|
if (!isPlainRecord(value))
|
|
402
448
|
return false;
|
|
@@ -423,9 +469,11 @@ function isHabitRunReceipt(value) {
|
|
|
423
469
|
&& typeof value.pendingLocator === "string"
|
|
424
470
|
&& typeof value.runtimeStateLocator === "string"
|
|
425
471
|
&& typeof value.receiptLocator === "string"
|
|
472
|
+
&& (value.operationId === undefined || value.operationId === null || typeof value.operationId === "string")
|
|
426
473
|
&& (value.nextRunAt === null || typeof value.nextRunAt === "string")
|
|
427
474
|
&& isHabitPermissionEnvelope(value.permissionEnvelope)
|
|
428
475
|
&& isHabitToolPolicy(value.toolPolicy)
|
|
476
|
+
&& (value.summarySnapshot === undefined || isPlainRecord(value.summarySnapshot))
|
|
429
477
|
&& isProducedRefArray(value.producedRefs)
|
|
430
478
|
&& isHabitSurfaceAttemptArray(value.surfaceAttempts)
|
|
431
479
|
&& isStringArray(value.errors);
|
|
@@ -479,6 +527,7 @@ function normalizeLegacyHabitRunReceipt(receipt) {
|
|
|
479
527
|
pendingLocator: `state/habit-sessions/${receipt.runId}/pending`,
|
|
480
528
|
runtimeStateLocator: `state/habits/${receipt.habitName}.json`,
|
|
481
529
|
receiptLocator: `arc/flight-recorder/habit-receipts/${receipt.runId}.json`,
|
|
530
|
+
operationId: null,
|
|
482
531
|
nextRunAt: null,
|
|
483
532
|
permissionEnvelope: {
|
|
484
533
|
schemaVersion: 1,
|
|
@@ -493,12 +542,14 @@ function normalizeLegacyHabitRunReceipt(receipt) {
|
|
|
493
542
|
deniedTools: sawSurface ? [] : ["send_message", "surface"],
|
|
494
543
|
outwardMessagingAllowed: sawSurface,
|
|
495
544
|
},
|
|
545
|
+
summarySnapshot: defaultHabitRunSummarySnapshot(receipt),
|
|
496
546
|
producedRefs: receipt.producedRefs,
|
|
497
547
|
surfaceAttempts: receipt.surfaceAttempts,
|
|
498
548
|
errors: receipt.errors,
|
|
499
549
|
};
|
|
500
550
|
}
|
|
501
551
|
function capHabitRunReceipt(receipt) {
|
|
552
|
+
const fallbackSnapshot = defaultHabitRunSummarySnapshot(receipt);
|
|
502
553
|
return {
|
|
503
554
|
...receipt,
|
|
504
555
|
habitName: (0, session_events_1.capStructuredRecordString)(receipt.habitName),
|
|
@@ -507,6 +558,7 @@ function capHabitRunReceipt(receipt) {
|
|
|
507
558
|
pendingLocator: (0, session_events_1.capStructuredRecordString)(receipt.pendingLocator),
|
|
508
559
|
runtimeStateLocator: (0, session_events_1.capStructuredRecordString)(receipt.runtimeStateLocator),
|
|
509
560
|
receiptLocator: (0, session_events_1.capStructuredRecordString)(receipt.receiptLocator),
|
|
561
|
+
operationId: receipt.operationId ? (0, session_events_1.capStructuredRecordString)(receipt.operationId) : null,
|
|
510
562
|
permissionEnvelope: {
|
|
511
563
|
...receipt.permissionEnvelope,
|
|
512
564
|
returnRoutes: receipt.permissionEnvelope.returnRoutes.map((route) => ({
|
|
@@ -526,6 +578,7 @@ function capHabitRunReceipt(receipt) {
|
|
|
526
578
|
deniedTools: cappedArray(receipt.toolPolicy.deniedTools),
|
|
527
579
|
outwardMessagingAllowed: receipt.toolPolicy.outwardMessagingAllowed,
|
|
528
580
|
},
|
|
581
|
+
summarySnapshot: normalizeHabitRunSummarySnapshot(receipt.summarySnapshot, fallbackSnapshot),
|
|
529
582
|
producedRefs: receipt.producedRefs.map((ref) => ({ ...ref, locator: (0, session_events_1.capStructuredRecordString)(ref.locator) })),
|
|
530
583
|
surfaceAttempts: receipt.surfaceAttempts.map((attempt) => ({
|
|
531
584
|
...attempt,
|
|
@@ -562,7 +615,7 @@ function readHabitRunReceipt(agentRoot, runId) {
|
|
|
562
615
|
message: "flight recorder habit receipt read",
|
|
563
616
|
meta: { agentRoot, runId },
|
|
564
617
|
});
|
|
565
|
-
return receipt;
|
|
618
|
+
return capHabitRunReceipt(receipt);
|
|
566
619
|
}
|
|
567
620
|
catch (error) {
|
|
568
621
|
warnMalformedHabitReceipt(agentRoot, runId, error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error));
|
|
@@ -593,7 +646,10 @@ function writeHabitRunReceipt(agentRoot, receipt) {
|
|
|
593
646
|
recordedAt: safeReceipt.endedAt,
|
|
594
647
|
summary: `habit ${safeReceipt.habitName} finished with ${safeReceipt.outcome}`,
|
|
595
648
|
producedRefs: safeReceipt.producedRefs,
|
|
596
|
-
meta: {
|
|
649
|
+
meta: {
|
|
650
|
+
receiptPath: path.join("arc", "flight-recorder", "habit-receipts", `${safeReceipt.runId}.json`),
|
|
651
|
+
operationId: safeReceipt.operationId ?? null,
|
|
652
|
+
},
|
|
597
653
|
});
|
|
598
654
|
(0, runtime_1.emitNervesEvent)({
|
|
599
655
|
component: "mind",
|
|
@@ -96,6 +96,7 @@ const cli_help_1 = require("./cli-help");
|
|
|
96
96
|
const plugin_cli_1 = require("./plugin-cli");
|
|
97
97
|
const cli_desk_1 = require("./cli-desk");
|
|
98
98
|
const migrate_to_desk_1 = require("./migrate-to-desk");
|
|
99
|
+
const core_2 = require("../../nerves/review/core");
|
|
99
100
|
const cli_render_1 = require("./cli-render");
|
|
100
101
|
const cli_defaults_1 = require("./cli-defaults");
|
|
101
102
|
const agent_config_check_1 = require("./agent-config-check");
|
|
@@ -150,6 +151,58 @@ function returnCliFailure(deps, message, exitCode = 1) {
|
|
|
150
151
|
deps.writeStdout(message);
|
|
151
152
|
return message;
|
|
152
153
|
}
|
|
154
|
+
function renderHabitSummaryCli(summary) {
|
|
155
|
+
return [
|
|
156
|
+
`${summary.runId} habit=${summary.habitName} outcome=${summary.status} completedAt=${summary.completedAt}`,
|
|
157
|
+
summary.operationId ? `operation=${summary.operationId}` : null,
|
|
158
|
+
`summary=${summary.summary}`,
|
|
159
|
+
summary.nextLikelyStep ? `next=${summary.nextLikelyStep}` : null,
|
|
160
|
+
summary.decisions.length > 0 ? `decisions=${summary.decisions.join("; ")}` : null,
|
|
161
|
+
`pending=${summary.pending.count}${summary.pending.files.length > 0 ? ` (${summary.pending.files.join(", ")})` : ""}`,
|
|
162
|
+
`messages=${summary.messagesSent.length}`,
|
|
163
|
+
`tools=${summary.toolsUsed.length > 0 ? summary.toolsUsed.join(",") : "none"}`,
|
|
164
|
+
summary.producedRefs.length > 0 ? `refs=${summary.producedRefs.map((ref) => `${ref.kind}:${ref.locator}`).join(",")}` : null,
|
|
165
|
+
summary.errors.length > 0 ? `errors=${summary.errors.join("; ")}` : null,
|
|
166
|
+
summary.warnings.length > 0 ? `warnings=${summary.warnings.join("; ")}` : null,
|
|
167
|
+
`receipt=${summary.sources.receipt}`,
|
|
168
|
+
`session=${summary.sources.session}`,
|
|
169
|
+
`runtime=${summary.sources.runtimeState}`,
|
|
170
|
+
].filter((line) => Boolean(line)).join("\n");
|
|
171
|
+
}
|
|
172
|
+
function executeNervesReviewCommand(command, deps) {
|
|
173
|
+
let sinceMs;
|
|
174
|
+
if (command.since) {
|
|
175
|
+
const parsed = (0, core_2.parseDuration)(command.since);
|
|
176
|
+
if (parsed === null) {
|
|
177
|
+
const message = `nerves-review: --since '${command.since}' is not a valid duration (e.g. 5m, 2h, 1d)`;
|
|
178
|
+
deps.setExitCode?.(2);
|
|
179
|
+
deps.writeStdout(message);
|
|
180
|
+
return message;
|
|
181
|
+
}
|
|
182
|
+
sinceMs = parsed;
|
|
183
|
+
}
|
|
184
|
+
const logsDir = (0, identity_1.getAgentDaemonLogsDir)(command.agent);
|
|
185
|
+
const filePath = path.join(logsDir, `${command.process}.ndjson`);
|
|
186
|
+
const entries = (0, core_2.reviewNerveEvents)(filePath, {
|
|
187
|
+
componentSubstring: command.component,
|
|
188
|
+
eventSubstring: command.event,
|
|
189
|
+
level: command.level,
|
|
190
|
+
sinceMs,
|
|
191
|
+
limit: command.limit,
|
|
192
|
+
nowMs: Date.now(),
|
|
193
|
+
});
|
|
194
|
+
const message = entries.length === 0
|
|
195
|
+
? `(no matching nerves events in ${filePath})`
|
|
196
|
+
: entries.map((entry) => command.json ? entry.raw : (0, core_2.formatNerveEntry)(entry)).join("\n");
|
|
197
|
+
deps.writeStdout(message);
|
|
198
|
+
(0, runtime_1.emitNervesEvent)({
|
|
199
|
+
component: "daemon",
|
|
200
|
+
event: "daemon.nerves_review_cli_read",
|
|
201
|
+
message: "nerves review CLI read local log events",
|
|
202
|
+
meta: { agent: command.agent, process: command.process, count: entries.length, json: command.json },
|
|
203
|
+
});
|
|
204
|
+
return message;
|
|
205
|
+
}
|
|
153
206
|
function summarizeDaemonStartupFailure(result) {
|
|
154
207
|
if (result.startupFailureReason && result.startupFailureReason.trim().length > 0) {
|
|
155
208
|
return result.startupFailureReason;
|
|
@@ -366,6 +419,8 @@ function agentResolutionFailureMode(command) {
|
|
|
366
419
|
case "habit.create":
|
|
367
420
|
case "habit.runs":
|
|
368
421
|
case "habit.inspect":
|
|
422
|
+
case "habit.summary":
|
|
423
|
+
case "nerves-review":
|
|
369
424
|
case "thoughts":
|
|
370
425
|
case "attention.list":
|
|
371
426
|
case "attention.show":
|
|
@@ -5684,6 +5739,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
5684
5739
|
if (command.kind === "hook") {
|
|
5685
5740
|
await refreshHookSentinel(command, deps);
|
|
5686
5741
|
}
|
|
5742
|
+
if (command.kind === "nerves-review") {
|
|
5743
|
+
return executeNervesReviewCommand(command, deps);
|
|
5744
|
+
}
|
|
5687
5745
|
if (args.length === 0) {
|
|
5688
5746
|
const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : (0, cli_defaults_1.defaultListDiscoveredAgents)());
|
|
5689
5747
|
/* v8 ignore start -- the interactive home shell is exercised extensively in daemon-cli tests; V8 miscounts this orchestrator because it chains through recursive command handoffs and early chat health exits @preserve */
|
|
@@ -6843,10 +6901,11 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
6843
6901
|
return result.summary;
|
|
6844
6902
|
}
|
|
6845
6903
|
// ── habit subcommands (local, no daemon socket needed) ──
|
|
6846
|
-
if (command.kind === "habit.list" || command.kind === "habit.create" || command.kind === "habit.runs" || command.kind === "habit.inspect") {
|
|
6904
|
+
if (command.kind === "habit.list" || command.kind === "habit.create" || command.kind === "habit.runs" || command.kind === "habit.inspect" || command.kind === "habit.summary") {
|
|
6847
6905
|
const { parseHabitFile, renderHabitFile } = await Promise.resolve().then(() => __importStar(require("../habits/habit-parser")));
|
|
6848
6906
|
const { applyHabitRuntimeState } = await Promise.resolve().then(() => __importStar(require("../habits/habit-runtime-state")));
|
|
6849
6907
|
const { listHabitRunReceipts, readHabitRunReceipt } = await Promise.resolve().then(() => __importStar(require("../../arc/flight-recorder")));
|
|
6908
|
+
const { readHabitSessionSummary } = await Promise.resolve().then(() => __importStar(require("../habits/habit-session-summary")));
|
|
6850
6909
|
/* v8 ignore start -- production default: uses real bundle root @preserve */
|
|
6851
6910
|
const bundleRoot = deps.agentBundleRoot ?? path.join(deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
|
|
6852
6911
|
/* v8 ignore stop */
|
|
@@ -6923,6 +6982,36 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
6923
6982
|
});
|
|
6924
6983
|
return message;
|
|
6925
6984
|
}
|
|
6985
|
+
if (command.kind === "habit.summary") {
|
|
6986
|
+
const summary = readHabitSessionSummary(bundleRoot, {
|
|
6987
|
+
...(command.runId ? { runId: command.runId } : {}),
|
|
6988
|
+
...(command.habitName ? { habitName: command.habitName } : {}),
|
|
6989
|
+
...(command.operationId ? { operationId: command.operationId } : {}),
|
|
6990
|
+
...(command.which ? { which: command.which } : {}),
|
|
6991
|
+
});
|
|
6992
|
+
if (!summary) {
|
|
6993
|
+
const message = "error: habit summary not found";
|
|
6994
|
+
deps.writeStdout(message);
|
|
6995
|
+
deps.setExitCode?.(1);
|
|
6996
|
+
(0, runtime_1.emitNervesEvent)({
|
|
6997
|
+
level: "warn",
|
|
6998
|
+
component: "daemon",
|
|
6999
|
+
event: "daemon.habit_summary_cli_read_missing",
|
|
7000
|
+
message: "habit run summary not found from CLI",
|
|
7001
|
+
meta: { agent: command.agent, runId: command.runId, habitName: command.habitName, operationId: command.operationId, which: command.which },
|
|
7002
|
+
});
|
|
7003
|
+
return message;
|
|
7004
|
+
}
|
|
7005
|
+
const message = command.json ? `${JSON.stringify(summary, null, 2)}\n` : renderHabitSummaryCli(summary);
|
|
7006
|
+
deps.writeStdout(message);
|
|
7007
|
+
(0, runtime_1.emitNervesEvent)({
|
|
7008
|
+
component: "daemon",
|
|
7009
|
+
event: "daemon.habit_summary_cli_read",
|
|
7010
|
+
message: "habit run summary read from CLI",
|
|
7011
|
+
meta: { agent: command.agent, runId: summary.runId, habitName: summary.habitName, json: command.json },
|
|
7012
|
+
});
|
|
7013
|
+
return message;
|
|
7014
|
+
}
|
|
6926
7015
|
// habit.create
|
|
6927
7016
|
const filePath = path.join(habitsDir, `${command.name}.md`);
|
|
6928
7017
|
if (fs.existsSync(filePath)) {
|
|
@@ -135,7 +135,7 @@ exports.COMMAND_REGISTRY = {
|
|
|
135
135
|
description: "Manage agent habits",
|
|
136
136
|
usage: "ouro habit <subcommand> [--agent <name>]",
|
|
137
137
|
example: "ouro habit list",
|
|
138
|
-
subcommands: ["list", "create", "poke"],
|
|
138
|
+
subcommands: ["list", "create", "runs", "inspect", "summary", "poke"],
|
|
139
139
|
},
|
|
140
140
|
desk: {
|
|
141
141
|
category: "Tasks",
|
|
@@ -158,6 +158,12 @@ exports.COMMAND_REGISTRY = {
|
|
|
158
158
|
example: "ouro work sentinel --agent slugger --format json",
|
|
159
159
|
subcommands: ["card", "gauntlet", "sentinel"],
|
|
160
160
|
},
|
|
161
|
+
"nerves-review": {
|
|
162
|
+
category: "Internal",
|
|
163
|
+
description: "Read-only review of recent nerves events from an agent log stream",
|
|
164
|
+
usage: "ouro nerves-review [--agent <name>] [--process <name>] [--component <substr>] [--event <substr>] [--level <level>] [--since <duration>] [--limit <n>] [--json]",
|
|
165
|
+
example: "ouro nerves-review --agent slugger --component daemon --event habit --since 30m --json",
|
|
166
|
+
},
|
|
161
167
|
"work card": {
|
|
162
168
|
category: "Tasks",
|
|
163
169
|
description: "Show the agent's durable Work Card compiled from arc records.",
|
|
@@ -395,6 +401,11 @@ const SUBCOMMAND_HELP = {
|
|
|
395
401
|
usage: "ouro account ensure [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
|
|
396
402
|
example: "ouro account ensure --agent <agent> --owner-email you@example.com --source hey",
|
|
397
403
|
},
|
|
404
|
+
"habit summary": {
|
|
405
|
+
description: "Read a habit run summary from receipts and session artifacts without contacting the daemon",
|
|
406
|
+
usage: "ouro habit summary [--agent <name>] (--run-id <id>|--habit <name>|--operation-id <id>) [--which latest|previous|latest-success|latest-failure] [--json]",
|
|
407
|
+
example: "ouro habit summary --agent slugger --operation-id habit:standup --which latest --json",
|
|
408
|
+
},
|
|
398
409
|
"mail import-mbox": {
|
|
399
410
|
description: "Import a HEY or other MBOX export into an existing delegated Mailroom source grant",
|
|
400
411
|
usage: "ouro mail import-mbox (--file <path>|--discover) [--owner-email <email>] [--source <label>] [--agent <name>] [--foreground]",
|
|
@@ -110,6 +110,7 @@ function usage() {
|
|
|
110
110
|
" ouro habit create [--agent <name>] <name> [--cadence <interval>]",
|
|
111
111
|
" ouro habit runs [--agent <name>] [--limit <n>]",
|
|
112
112
|
" ouro habit inspect [--agent <name>] <runId>",
|
|
113
|
+
" ouro habit summary [--agent <name>] (--run-id <id>|--habit <name>|--operation-id <id>) [--which latest|previous|latest-success|latest-failure] [--json]",
|
|
113
114
|
" ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
|
|
114
115
|
" ouro bluebubbles replay [--agent <name>] --message-guid <guid> [--event-type new-message|updated-message] [--json]",
|
|
115
116
|
" ouro friend list [--agent <name>]",
|
|
@@ -118,6 +119,7 @@ function usage() {
|
|
|
118
119
|
" ouro friend update <id> --trust <level> [--agent <name>]",
|
|
119
120
|
" ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
|
|
120
121
|
" ouro work card|gauntlet|sentinel [refresh] [--agent <name>] [--format text|json|--json]",
|
|
122
|
+
" ouro nerves-review [--agent <name>] [--process <name>] [--component <substr>] [--event <substr>] [--level <level>] [--since <duration>] [--limit <n>] [--json]",
|
|
121
123
|
" ouro inner [--agent <name>]",
|
|
122
124
|
" ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
123
125
|
" ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
@@ -261,6 +263,53 @@ function parseHabitCommand(args) {
|
|
|
261
263
|
throw new Error(`Usage\n${usage()}`);
|
|
262
264
|
return { kind: "habit.inspect", ...(agent ? { agent } : {}), runId: positional[0] };
|
|
263
265
|
}
|
|
266
|
+
if (sub === "summary") {
|
|
267
|
+
let runId;
|
|
268
|
+
let habitName;
|
|
269
|
+
let operationId;
|
|
270
|
+
let which;
|
|
271
|
+
let json = false;
|
|
272
|
+
const options = rest.slice(1);
|
|
273
|
+
for (let i = 0; i < options.length; i += 1) {
|
|
274
|
+
const option = options[i];
|
|
275
|
+
if (option === "--json") {
|
|
276
|
+
json = true;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if ((option === "--run-id" || option === "--habit" || option === "--operation-id" || option === "--which") && options[i + 1]) {
|
|
280
|
+
const value = options[++i];
|
|
281
|
+
if (option === "--run-id")
|
|
282
|
+
runId = value;
|
|
283
|
+
if (option === "--habit")
|
|
284
|
+
habitName = value;
|
|
285
|
+
if (option === "--operation-id")
|
|
286
|
+
operationId = value;
|
|
287
|
+
if (option === "--which") {
|
|
288
|
+
if (!["latest", "previous", "latest-success", "latest-failure"].includes(value)) {
|
|
289
|
+
throw new Error("--which must be latest, previous, latest-success, or latest-failure");
|
|
290
|
+
}
|
|
291
|
+
which = value;
|
|
292
|
+
}
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
throw new Error(`Usage\n${usage()}`);
|
|
296
|
+
}
|
|
297
|
+
if (runId !== undefined && (habitName !== undefined || operationId !== undefined || which !== undefined)) {
|
|
298
|
+
throw new Error("--run-id cannot be combined with --habit, --operation-id, or --which");
|
|
299
|
+
}
|
|
300
|
+
if (runId === undefined && habitName === undefined && operationId === undefined) {
|
|
301
|
+
throw new Error("provide --run-id, --habit, or --operation-id");
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
kind: "habit.summary",
|
|
305
|
+
...(agent ? { agent } : {}),
|
|
306
|
+
...(runId ? { runId } : {}),
|
|
307
|
+
...(habitName ? { habitName } : {}),
|
|
308
|
+
...(operationId ? { operationId } : {}),
|
|
309
|
+
...(which ? { which } : {}),
|
|
310
|
+
json,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
264
313
|
throw new Error(`Usage\n${usage()}`);
|
|
265
314
|
}
|
|
266
315
|
function parseLinkCommand(args, kind = "friend.link") {
|
|
@@ -1537,6 +1586,57 @@ function parseBlueBubblesCommand(args) {
|
|
|
1537
1586
|
...(json ? { json: true } : {}),
|
|
1538
1587
|
};
|
|
1539
1588
|
}
|
|
1589
|
+
function parseNervesReviewCommand(args) {
|
|
1590
|
+
const { agent, rest } = extractAgentFlag(args);
|
|
1591
|
+
let processName = "daemon";
|
|
1592
|
+
let component;
|
|
1593
|
+
let event;
|
|
1594
|
+
let level;
|
|
1595
|
+
let since;
|
|
1596
|
+
let limit;
|
|
1597
|
+
let json = false;
|
|
1598
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
1599
|
+
const token = rest[i];
|
|
1600
|
+
const next = rest[i + 1];
|
|
1601
|
+
if (token === "--json") {
|
|
1602
|
+
json = true;
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
if ((token === "--process" || token === "--component" || token === "--event" || token === "--level" || token === "--since" || token === "--limit") && next) {
|
|
1606
|
+
if (token === "--process")
|
|
1607
|
+
processName = next;
|
|
1608
|
+
if (token === "--component")
|
|
1609
|
+
component = next;
|
|
1610
|
+
if (token === "--event")
|
|
1611
|
+
event = next;
|
|
1612
|
+
if (token === "--level")
|
|
1613
|
+
level = next;
|
|
1614
|
+
if (token === "--since")
|
|
1615
|
+
since = next;
|
|
1616
|
+
if (token === "--limit") {
|
|
1617
|
+
const parsed = Number.parseInt(next, 10);
|
|
1618
|
+
if (!Number.isInteger(parsed) || String(parsed) !== next || parsed < 1 || parsed > 1000) {
|
|
1619
|
+
throw new Error("--limit must be an integer between 1 and 1000");
|
|
1620
|
+
}
|
|
1621
|
+
limit = parsed;
|
|
1622
|
+
}
|
|
1623
|
+
i += 1;
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
throw new Error(`Usage\n${usage()}`);
|
|
1627
|
+
}
|
|
1628
|
+
return {
|
|
1629
|
+
kind: "nerves-review",
|
|
1630
|
+
...(agent ? { agent } : {}),
|
|
1631
|
+
process: processName,
|
|
1632
|
+
...(component ? { component } : {}),
|
|
1633
|
+
...(event ? { event } : {}),
|
|
1634
|
+
...(level ? { level } : {}),
|
|
1635
|
+
...(since ? { since } : {}),
|
|
1636
|
+
...(limit ? { limit } : {}),
|
|
1637
|
+
json,
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1540
1640
|
// ── Main dispatch ──
|
|
1541
1641
|
function parseOuroCommand(args) {
|
|
1542
1642
|
const [head, second] = args;
|
|
@@ -1704,6 +1804,8 @@ function parseOuroCommand(args) {
|
|
|
1704
1804
|
return parseMessageCommand(args.slice(1));
|
|
1705
1805
|
if (head === "poke")
|
|
1706
1806
|
return parsePokeCommand(args.slice(1));
|
|
1807
|
+
if (head === "nerves-review")
|
|
1808
|
+
return parseNervesReviewCommand(args.slice(1));
|
|
1707
1809
|
if (head === "link")
|
|
1708
1810
|
return parseLinkCommand(args.slice(1));
|
|
1709
1811
|
if (head === "mcp-serve")
|
|
@@ -104,6 +104,11 @@ function parseSurface(raw) {
|
|
|
104
104
|
extra: record ? parseStringArray(record.extra) : [],
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
|
+
function parseContinuity(raw) {
|
|
108
|
+
const record = objectRecord(raw);
|
|
109
|
+
const mode = record ? record.mode : null;
|
|
110
|
+
return { mode: mode === "stateful" ? "stateful" : "fresh" };
|
|
111
|
+
}
|
|
107
112
|
function extractFrontmatterAndBody(content) {
|
|
108
113
|
const lines = content.split(/\r?\n/);
|
|
109
114
|
if (lines[0]?.trim() !== "---") {
|
|
@@ -137,6 +142,7 @@ function parseHabitFile(content, filePath) {
|
|
|
137
142
|
tools: undefined,
|
|
138
143
|
origin: null,
|
|
139
144
|
surface: { family: true, originator: true, extra: [] },
|
|
145
|
+
continuity: { mode: "fresh" },
|
|
140
146
|
body: content.trim(),
|
|
141
147
|
};
|
|
142
148
|
}
|
|
@@ -154,6 +160,7 @@ function parseHabitFile(content, filePath) {
|
|
|
154
160
|
const tools = parseToolsField(frontmatter.tools);
|
|
155
161
|
const origin = parseOrigin(frontmatter.origin);
|
|
156
162
|
const surface = parseSurface(frontmatter.surface);
|
|
163
|
+
const continuity = parseContinuity(frontmatter.continuity);
|
|
157
164
|
return {
|
|
158
165
|
name: stem,
|
|
159
166
|
title,
|
|
@@ -164,6 +171,7 @@ function parseHabitFile(content, filePath) {
|
|
|
164
171
|
tools,
|
|
165
172
|
origin,
|
|
166
173
|
surface,
|
|
174
|
+
continuity,
|
|
167
175
|
body,
|
|
168
176
|
};
|
|
169
177
|
}
|
|
@@ -47,6 +47,9 @@ function habitRuntimeStateDir(agentRoot) {
|
|
|
47
47
|
function isNonEmptyString(value) {
|
|
48
48
|
return typeof value === "string" && value.trim().length > 0;
|
|
49
49
|
}
|
|
50
|
+
function nullableCursor(value) {
|
|
51
|
+
return isNonEmptyString(value) ? value.trim() : null;
|
|
52
|
+
}
|
|
50
53
|
function stripLegacyLastRunFromDefinition(definitionPath) {
|
|
51
54
|
const content = fs.readFileSync(definitionPath, "utf-8");
|
|
52
55
|
const lines = content.split(/\r?\n/);
|
|
@@ -72,23 +75,34 @@ function applyHabitRuntimeState(agentRoot, habit) {
|
|
|
72
75
|
return habit;
|
|
73
76
|
return { ...habit, lastRun: runtimeLastRun };
|
|
74
77
|
}
|
|
75
|
-
function writeHabitLastRun(agentRoot, habitName, lastRun, updatedAt = lastRun) {
|
|
78
|
+
function writeHabitLastRun(agentRoot, habitName, lastRun, updatedAt = lastRun, options = {}) {
|
|
76
79
|
const record = {
|
|
77
80
|
schemaVersion: 1,
|
|
78
81
|
name: habitName,
|
|
79
82
|
lastRun,
|
|
80
83
|
updatedAt,
|
|
84
|
+
activeOperationId: nullableCursor(options.activeOperationId),
|
|
85
|
+
latestRunId: nullableCursor(options.latestRunId),
|
|
86
|
+
latestReceiptLocator: nullableCursor(options.latestReceiptLocator),
|
|
81
87
|
};
|
|
82
88
|
(0, json_store_1.writeJsonFile)(habitRuntimeStateDir(agentRoot), habitName, record);
|
|
83
89
|
(0, runtime_1.emitNervesEvent)({
|
|
84
90
|
component: "daemon",
|
|
85
91
|
event: "daemon.habit_runtime_state_write",
|
|
86
92
|
message: "wrote habit runtime state",
|
|
87
|
-
meta: {
|
|
93
|
+
meta: {
|
|
94
|
+
agentRoot,
|
|
95
|
+
habitName,
|
|
96
|
+
lastRun,
|
|
97
|
+
updatedAt,
|
|
98
|
+
activeOperationId: record.activeOperationId,
|
|
99
|
+
latestRunId: record.latestRunId,
|
|
100
|
+
latestReceiptLocator: record.latestReceiptLocator,
|
|
101
|
+
},
|
|
88
102
|
});
|
|
89
103
|
}
|
|
90
104
|
function recordHabitRun(agentRoot, habitName, lastRun, options = {}) {
|
|
91
|
-
writeHabitLastRun(agentRoot, habitName, lastRun);
|
|
105
|
+
writeHabitLastRun(agentRoot, habitName, lastRun, lastRun, options);
|
|
92
106
|
if (!options.definitionPath)
|
|
93
107
|
return;
|
|
94
108
|
try {
|