@ouro.bot/cli 0.1.0-alpha.665 → 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 +13 -0
- package/dist/arc/flight-recorder.js +324 -5
- package/dist/heart/core.js +167 -4
- package/dist/heart/cross-chat-delivery.js +3 -2
- package/dist/heart/daemon/cli-exec.js +139 -1
- package/dist/heart/daemon/cli-help.js +13 -2
- package/dist/heart/daemon/cli-parse.js +138 -2
- package/dist/heart/daemon/daemon-entry.js +24 -5
- package/dist/heart/daemon/daemon.js +10 -1
- package/dist/heart/habits/habit-parser.js +8 -0
- package/dist/heart/habits/habit-runtime-state.js +17 -3
- package/dist/heart/habits/habit-scheduler.js +24 -5
- package/dist/heart/habits/habit-session-summary.js +318 -0
- package/dist/heart/habits/habit-session.js +618 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +29 -1
- package/dist/heart/mailbox/mailbox-http-routes.js +122 -1
- package/dist/heart/mailbox/mailbox-read.js +5 -1
- package/dist/heart/mailbox/readers/runtime-readers.js +87 -0
- package/dist/mailbox-ui/assets/index-CaTIFDmv.js +1 -0
- package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
- package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
- package/dist/mailbox-ui/index.html +3 -2
- package/dist/repertoire/tools-notes.js +50 -0
- package/dist/repertoire/tools-record.js +13 -0
- package/dist/repertoire/tools-session.js +140 -0
- package/dist/repertoire/tools-surface.js +11 -0
- package/dist/repertoire/tools.js +7 -0
- package/dist/senses/habit-turn-message.js +41 -3
- package/dist/senses/inner-dialog-worker.js +264 -68
- package/dist/senses/inner-dialog.js +29 -15
- package/dist/senses/pipeline.js +2 -11
- package/dist/senses/surface-tool.js +2 -1
- package/package.json +1 -1
- package/dist/mailbox-ui/assets/index-BZ60na8O.js +0 -61
- package/dist/mailbox-ui/assets/index-DG6Xf5uL.css +0 -1
|
@@ -13,11 +13,12 @@ function buildPendingEnvelope(request, agentName, now) {
|
|
|
13
13
|
timestamp: now,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
function queueForLater(request, deps, detail) {
|
|
16
|
+
function queueForLater(request, deps, detail, rawStatus = "queued_for_later") {
|
|
17
17
|
deps.queuePending(buildPendingEnvelope(request, deps.agentName, (deps.now ?? Date.now)()));
|
|
18
18
|
return {
|
|
19
19
|
status: "queued_for_later",
|
|
20
20
|
detail,
|
|
21
|
+
...(rawStatus !== "queued_for_later" ? { rawStatus } : {}),
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
function isExplicitlyAuthorized(request) {
|
|
@@ -94,7 +95,7 @@ async function deliverCrossChatMessage(request, deps) {
|
|
|
94
95
|
});
|
|
95
96
|
return result;
|
|
96
97
|
}
|
|
97
|
-
const result = queueForLater(request, deps, direct.detail.trim() || "live delivery unavailable right now; queued for the next active turn");
|
|
98
|
+
const result = queueForLater(request, deps, direct.detail.trim() || "live delivery unavailable right now; queued for the next active turn", direct.status);
|
|
98
99
|
(0, runtime_1.emitNervesEvent)({
|
|
99
100
|
component: "engine",
|
|
100
101
|
event: "engine.cross_chat_delivery_end",
|
|
@@ -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;
|
|
@@ -364,6 +417,10 @@ function agentResolutionFailureMode(command) {
|
|
|
364
417
|
case "friend.update":
|
|
365
418
|
case "habit.list":
|
|
366
419
|
case "habit.create":
|
|
420
|
+
case "habit.runs":
|
|
421
|
+
case "habit.inspect":
|
|
422
|
+
case "habit.summary":
|
|
423
|
+
case "nerves-review":
|
|
367
424
|
case "thoughts":
|
|
368
425
|
case "attention.list":
|
|
369
426
|
case "attention.show":
|
|
@@ -5682,6 +5739,9 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
5682
5739
|
if (command.kind === "hook") {
|
|
5683
5740
|
await refreshHookSentinel(command, deps);
|
|
5684
5741
|
}
|
|
5742
|
+
if (command.kind === "nerves-review") {
|
|
5743
|
+
return executeNervesReviewCommand(command, deps);
|
|
5744
|
+
}
|
|
5685
5745
|
if (args.length === 0) {
|
|
5686
5746
|
const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : (0, cli_defaults_1.defaultListDiscoveredAgents)());
|
|
5687
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 */
|
|
@@ -6841,9 +6901,11 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
6841
6901
|
return result.summary;
|
|
6842
6902
|
}
|
|
6843
6903
|
// ── habit subcommands (local, no daemon socket needed) ──
|
|
6844
|
-
if (command.kind === "habit.list" || command.kind === "habit.create") {
|
|
6904
|
+
if (command.kind === "habit.list" || command.kind === "habit.create" || command.kind === "habit.runs" || command.kind === "habit.inspect" || command.kind === "habit.summary") {
|
|
6845
6905
|
const { parseHabitFile, renderHabitFile } = await Promise.resolve().then(() => __importStar(require("../habits/habit-parser")));
|
|
6846
6906
|
const { applyHabitRuntimeState } = await Promise.resolve().then(() => __importStar(require("../habits/habit-runtime-state")));
|
|
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")));
|
|
6847
6909
|
/* v8 ignore start -- production default: uses real bundle root @preserve */
|
|
6848
6910
|
const bundleRoot = deps.agentBundleRoot ?? path.join(deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
|
|
6849
6911
|
/* v8 ignore stop */
|
|
@@ -6874,6 +6936,82 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
6874
6936
|
deps.writeStdout(message);
|
|
6875
6937
|
return message;
|
|
6876
6938
|
}
|
|
6939
|
+
if (command.kind === "habit.runs") {
|
|
6940
|
+
const receipts = listHabitRunReceipts(bundleRoot, { limit: command.limit });
|
|
6941
|
+
const message = receipts.length === 0
|
|
6942
|
+
? "no habit runs found"
|
|
6943
|
+
: receipts.map((receipt) => [
|
|
6944
|
+
receipt.runId,
|
|
6945
|
+
`habit=${receipt.habitName}`,
|
|
6946
|
+
`trigger=${receipt.trigger}`,
|
|
6947
|
+
`outcome=${receipt.outcome}`,
|
|
6948
|
+
`endedAt=${receipt.endedAt}`,
|
|
6949
|
+
`receipt=${receipt.receiptLocator}`,
|
|
6950
|
+
].join(" ")).join("\n");
|
|
6951
|
+
deps.writeStdout(message);
|
|
6952
|
+
(0, runtime_1.emitNervesEvent)({
|
|
6953
|
+
component: "daemon",
|
|
6954
|
+
event: "daemon.habit_runs_cli_read",
|
|
6955
|
+
message: "habit run receipts listed from CLI",
|
|
6956
|
+
meta: { agent: command.agent, limit: command.limit, count: receipts.length },
|
|
6957
|
+
});
|
|
6958
|
+
return message;
|
|
6959
|
+
}
|
|
6960
|
+
if (command.kind === "habit.inspect") {
|
|
6961
|
+
const receipt = readHabitRunReceipt(bundleRoot, command.runId);
|
|
6962
|
+
if (!receipt) {
|
|
6963
|
+
const message = `error: habit run '${command.runId}' not found`;
|
|
6964
|
+
deps.writeStdout(message);
|
|
6965
|
+
deps.setExitCode?.(1);
|
|
6966
|
+
(0, runtime_1.emitNervesEvent)({
|
|
6967
|
+
level: "warn",
|
|
6968
|
+
component: "daemon",
|
|
6969
|
+
event: "daemon.habit_run_cli_read_missing",
|
|
6970
|
+
message: "habit run receipt not found from CLI",
|
|
6971
|
+
meta: { agent: command.agent, runId: command.runId },
|
|
6972
|
+
});
|
|
6973
|
+
return message;
|
|
6974
|
+
}
|
|
6975
|
+
const message = `${JSON.stringify(receipt, null, 2)}\n`;
|
|
6976
|
+
deps.writeStdout(message);
|
|
6977
|
+
(0, runtime_1.emitNervesEvent)({
|
|
6978
|
+
component: "daemon",
|
|
6979
|
+
event: "daemon.habit_run_cli_read",
|
|
6980
|
+
message: "habit run receipt read from CLI",
|
|
6981
|
+
meta: { agent: command.agent, runId: command.runId },
|
|
6982
|
+
});
|
|
6983
|
+
return message;
|
|
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
|
+
}
|
|
6877
7015
|
// habit.create
|
|
6878
7016
|
const filePath = path.join(habitsDir, `${command.name}.md`);
|
|
6879
7017
|
if (fs.existsSync(filePath)) {
|
|
@@ -127,7 +127,7 @@ exports.COMMAND_REGISTRY = {
|
|
|
127
127
|
poke: {
|
|
128
128
|
category: "Tasks",
|
|
129
129
|
description: "Poke an agent about a task or habit",
|
|
130
|
-
usage: "ouro poke <agent> --task <task-id> | --habit <name>",
|
|
130
|
+
usage: "ouro poke <agent> --task <task-id> | --habit <name> [--trigger poke|launchd|cron|overdue|manual]",
|
|
131
131
|
example: "ouro poke ouroboros --task abc123",
|
|
132
132
|
},
|
|
133
133
|
habit: {
|
|
@@ -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]",
|
|
@@ -17,6 +17,7 @@ exports.parseOuroCommand = parseOuroCommand;
|
|
|
17
17
|
const types_1 = require("../../mind/friends/types");
|
|
18
18
|
const cli_help_1 = require("./cli-help");
|
|
19
19
|
const cli_desk_1 = require("./cli-desk");
|
|
20
|
+
const flight_recorder_1 = require("../../arc/flight-recorder");
|
|
20
21
|
const vault_items_1 = require("./vault-items");
|
|
21
22
|
// ── Shared helpers ──
|
|
22
23
|
function extractAgentFlag(args) {
|
|
@@ -104,9 +105,12 @@ function usage() {
|
|
|
104
105
|
" ouro chat <agent>",
|
|
105
106
|
" ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
|
|
106
107
|
" ouro poke <agent> --task <task-id>",
|
|
107
|
-
" ouro poke <agent> --habit <name>",
|
|
108
|
+
" ouro poke <agent> --habit <name> [--trigger poke|launchd|cron|overdue|manual]",
|
|
108
109
|
" ouro habit list [--agent <name>]",
|
|
109
110
|
" ouro habit create [--agent <name>] <name> [--cadence <interval>]",
|
|
111
|
+
" ouro habit runs [--agent <name>] [--limit <n>]",
|
|
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]",
|
|
110
114
|
" ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
|
|
111
115
|
" ouro bluebubbles replay [--agent <name>] --message-guid <guid> [--event-type new-message|updated-message] [--json]",
|
|
112
116
|
" ouro friend list [--agent <name>]",
|
|
@@ -115,6 +119,7 @@ function usage() {
|
|
|
115
119
|
" ouro friend update <id> --trust <level> [--agent <name>]",
|
|
116
120
|
" ouro thoughts [--last <n>] [--json] [--follow] [--agent <name>]",
|
|
117
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]",
|
|
118
123
|
" ouro inner [--agent <name>]",
|
|
119
124
|
" ouro friend link <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
120
125
|
" ouro friend unlink <agent> --friend <id> --provider <p> --external-id <eid>",
|
|
@@ -175,6 +180,7 @@ function parsePokeCommand(args) {
|
|
|
175
180
|
let taskId;
|
|
176
181
|
let habitName;
|
|
177
182
|
let awaitName;
|
|
183
|
+
let trigger;
|
|
178
184
|
for (let i = 1; i < args.length; i += 1) {
|
|
179
185
|
if (args[i] === "--task") {
|
|
180
186
|
taskId = args[i + 1];
|
|
@@ -188,12 +194,21 @@ function parsePokeCommand(args) {
|
|
|
188
194
|
awaitName = args[i + 1];
|
|
189
195
|
i += 1;
|
|
190
196
|
}
|
|
197
|
+
if (args[i] === "--trigger") {
|
|
198
|
+
const rawTrigger = args[i + 1];
|
|
199
|
+
if (!(0, flight_recorder_1.isHabitRunTrigger)(rawTrigger))
|
|
200
|
+
throw new Error("invalid habit trigger");
|
|
201
|
+
trigger = rawTrigger;
|
|
202
|
+
i += 1;
|
|
203
|
+
}
|
|
191
204
|
}
|
|
192
205
|
// Priority order: --await > --habit > --task
|
|
193
206
|
if (awaitName)
|
|
194
207
|
return { kind: "await.poke", agent, awaitName };
|
|
195
208
|
if (habitName)
|
|
196
|
-
return { kind: "habit.poke", agent, habitName };
|
|
209
|
+
return { kind: "habit.poke", agent, habitName, trigger: trigger ?? "poke" };
|
|
210
|
+
if (trigger)
|
|
211
|
+
throw new Error(`Usage\n${usage()}`);
|
|
197
212
|
if (!taskId)
|
|
198
213
|
throw new Error(`Usage\n${usage()}`);
|
|
199
214
|
return { kind: "task.poke", agent, taskId };
|
|
@@ -227,6 +242,74 @@ function parseHabitCommand(args) {
|
|
|
227
242
|
throw new Error(`Usage\n${usage()}`);
|
|
228
243
|
return { kind: "habit.create", name, ...(agent ? { agent } : {}), ...(cadence ? { cadence } : {}) };
|
|
229
244
|
}
|
|
245
|
+
if (sub === "runs") {
|
|
246
|
+
let limit = 20;
|
|
247
|
+
const options = rest.slice(1);
|
|
248
|
+
for (let i = 0; i < options.length; i += 1) {
|
|
249
|
+
if (options[i] !== "--limit" || !options[i + 1])
|
|
250
|
+
throw new Error(`Usage\n${usage()}`);
|
|
251
|
+
const parsedLimit = Number.parseInt(options[i + 1], 10);
|
|
252
|
+
if (!Number.isInteger(parsedLimit) || String(parsedLimit) !== options[i + 1] || parsedLimit < 1 || parsedLimit > 100) {
|
|
253
|
+
throw new Error("--limit must be an integer between 1 and 100");
|
|
254
|
+
}
|
|
255
|
+
limit = parsedLimit;
|
|
256
|
+
i += 1;
|
|
257
|
+
}
|
|
258
|
+
return { kind: "habit.runs", ...(agent ? { agent } : {}), limit };
|
|
259
|
+
}
|
|
260
|
+
if (sub === "inspect") {
|
|
261
|
+
const positional = rest.slice(1);
|
|
262
|
+
if (positional.length !== 1 || !positional[0])
|
|
263
|
+
throw new Error(`Usage\n${usage()}`);
|
|
264
|
+
return { kind: "habit.inspect", ...(agent ? { agent } : {}), runId: positional[0] };
|
|
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
|
+
}
|
|
230
313
|
throw new Error(`Usage\n${usage()}`);
|
|
231
314
|
}
|
|
232
315
|
function parseLinkCommand(args, kind = "friend.link") {
|
|
@@ -1503,6 +1586,57 @@ function parseBlueBubblesCommand(args) {
|
|
|
1503
1586
|
...(json ? { json: true } : {}),
|
|
1504
1587
|
};
|
|
1505
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
|
+
}
|
|
1506
1640
|
// ── Main dispatch ──
|
|
1507
1641
|
function parseOuroCommand(args) {
|
|
1508
1642
|
const [head, second] = args;
|
|
@@ -1670,6 +1804,8 @@ function parseOuroCommand(args) {
|
|
|
1670
1804
|
return parseMessageCommand(args.slice(1));
|
|
1671
1805
|
if (head === "poke")
|
|
1672
1806
|
return parsePokeCommand(args.slice(1));
|
|
1807
|
+
if (head === "nerves-review")
|
|
1808
|
+
return parseNervesReviewCommand(args.slice(1));
|
|
1673
1809
|
if (head === "link")
|
|
1674
1810
|
return parseLinkCommand(args.slice(1));
|
|
1675
1811
|
if (head === "mcp-serve")
|
|
@@ -149,9 +149,30 @@ const processManager = new process_manager_1.DaemonProcessManager({
|
|
|
149
149
|
},
|
|
150
150
|
/* v8 ignore stop */
|
|
151
151
|
});
|
|
152
|
-
const
|
|
152
|
+
const taskScheduler = new task_scheduler_1.TaskDrivenScheduler({
|
|
153
153
|
agents: [...managedAgents],
|
|
154
154
|
});
|
|
155
|
+
const habitSchedulers = [];
|
|
156
|
+
const awaitSchedulers = [];
|
|
157
|
+
const scheduler = {
|
|
158
|
+
listJobs: () => [
|
|
159
|
+
...taskScheduler.listJobs(),
|
|
160
|
+
...habitSchedulers.flatMap((habitScheduler) => habitScheduler.listJobs()),
|
|
161
|
+
],
|
|
162
|
+
triggerJob: (jobId) => taskScheduler.triggerJob(jobId),
|
|
163
|
+
triggerHabitJob: async (jobId) => {
|
|
164
|
+
for (const habitScheduler of habitSchedulers) {
|
|
165
|
+
const result = await habitScheduler.triggerJob(jobId, "cron");
|
|
166
|
+
if (result.ok)
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
return { ok: false, message: `unknown habit job: ${jobId}` };
|
|
170
|
+
},
|
|
171
|
+
start: () => taskScheduler.start(),
|
|
172
|
+
stop: () => taskScheduler.stop(),
|
|
173
|
+
reconcile: () => taskScheduler.reconcile(),
|
|
174
|
+
recordTaskRun: (agent, taskId) => taskScheduler.recordTaskRun(agent, taskId),
|
|
175
|
+
};
|
|
155
176
|
const router = new message_router_1.FileMessageRouter();
|
|
156
177
|
const senseManager = new sense_manager_1.DaemonSenseManager({
|
|
157
178
|
agents: [...managedAgents],
|
|
@@ -201,8 +222,6 @@ const healthMonitor = new health_monitor_1.HealthMonitor({
|
|
|
201
222
|
catch { /* recovery is best-effort */ }
|
|
202
223
|
},
|
|
203
224
|
});
|
|
204
|
-
const habitSchedulers = [];
|
|
205
|
-
const awaitSchedulers = [];
|
|
206
225
|
let entryRuntimeStopping = false;
|
|
207
226
|
let stopCommandExitScheduled = false;
|
|
208
227
|
function stopEntryRuntime() {
|
|
@@ -393,8 +412,8 @@ void daemon.start().then(async () => {
|
|
|
393
412
|
agent,
|
|
394
413
|
habitsDir,
|
|
395
414
|
osCronManager,
|
|
396
|
-
onHabitFire: (habitName) => {
|
|
397
|
-
processManager.sendToAgent(agent, { type: "habit", habitName, trigger
|
|
415
|
+
onHabitFire: (habitName, trigger) => {
|
|
416
|
+
processManager.sendToAgent(agent, { type: "habit", habitName, trigger });
|
|
398
417
|
},
|
|
399
418
|
deps: {
|
|
400
419
|
readdir: (dir) => fs.readdirSync(dir),
|
|
@@ -67,6 +67,7 @@ const mailbox_read_1 = require("../mailbox/mailbox-read");
|
|
|
67
67
|
const mailbox_view_1 = require("../mailbox/mailbox-view");
|
|
68
68
|
const provider_visibility_1 = require("../provider-visibility");
|
|
69
69
|
const socket_client_1 = require("./socket-client");
|
|
70
|
+
const flight_recorder_1 = require("../../arc/flight-recorder");
|
|
70
71
|
const PIDFILE_PATH = path.join(os.homedir(), ".ouro-cli", "daemon.pids");
|
|
71
72
|
/**
|
|
72
73
|
* Defense-in-depth: detect if we're running under vitest. The pidfile lives
|
|
@@ -1236,6 +1237,10 @@ class OuroDaemon {
|
|
|
1236
1237
|
return { ok: true, summary, data: jobs };
|
|
1237
1238
|
}
|
|
1238
1239
|
case "cron.trigger": {
|
|
1240
|
+
const habitResult = await this.scheduler.triggerHabitJob?.(command.jobId);
|
|
1241
|
+
if (habitResult?.ok) {
|
|
1242
|
+
return { ok: true, message: habitResult.message };
|
|
1243
|
+
}
|
|
1239
1244
|
const result = await this.scheduler.triggerJob(command.jobId);
|
|
1240
1245
|
return { ok: result.ok, message: result.message };
|
|
1241
1246
|
}
|
|
@@ -1302,7 +1307,11 @@ class OuroDaemon {
|
|
|
1302
1307
|
};
|
|
1303
1308
|
}
|
|
1304
1309
|
case "habit.poke": {
|
|
1305
|
-
|
|
1310
|
+
const trigger = command.trigger ?? "poke";
|
|
1311
|
+
if (!(0, flight_recorder_1.isHabitRunTrigger)(trigger)) {
|
|
1312
|
+
return { ok: false, error: `invalid habit trigger: ${String(trigger)}` };
|
|
1313
|
+
}
|
|
1314
|
+
this.processManager.sendToAgent?.(command.agent, { type: "habit", habitName: command.habitName, trigger });
|
|
1306
1315
|
return {
|
|
1307
1316
|
ok: true,
|
|
1308
1317
|
message: `poked habit ${command.habitName} for ${command.agent}`,
|
|
@@ -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 {
|