@rulvar/cli 1.22.0 → 1.24.0
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/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{io-RtHEIn0r.js → io-CL4wldEQ.js} +106 -15
- package/package.json +7 -7
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as resumeCommand, c as driveRun, d as renderEventLine, f as DEFAULT_STORE_DIR, g as looksLikeFile, h as loadWorkflowModule, i as inspectCommand, l as reportOutcome, m as loadCliConfig, n as HELP, o as runCommand, p as assembleEngine, r as runCli, s as runsLsCommand, t as processIo, u as attachProgress } from "./io-
|
|
1
|
+
import { a as resumeCommand, c as driveRun, d as renderEventLine, f as DEFAULT_STORE_DIR, g as looksLikeFile, h as loadWorkflowModule, i as inspectCommand, l as reportOutcome, m as loadCliConfig, n as HELP, o as runCommand, p as assembleEngine, r as runCli, s as runsLsCommand, t as processIo, u as attachProgress } from "./io-CL4wldEQ.js";
|
|
2
2
|
import { ConfigError, InvalidResolutionError, JournalCompatibilityError, LeaseHeldError, Replayer, RulvarError, buildDeriverRegistry, costReportFromJournal, maskSecrets, normalizeEntry, scanJournalCompatibility, validateSchemaSpec } from "@rulvar/core";
|
|
3
3
|
//#region src/server.ts
|
|
4
4
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConfigError, FileModelKnowledgeStore, INBOX_PROPOSAL_TTL_DAYS, JsonlFileStore, claimExpired, claimExpiry, compilePermissionPreset, costReportFromJournal, createEngine, parseModelRef, priceUsdOf, proposalStatement, remeasureQueue, resolvePricing, runProfile, sanitizeTerminalText } from "@rulvar/core";
|
|
1
|
+
import { ConfigError, FileModelKnowledgeStore, INBOX_PROPOSAL_TTL_DAYS, JsonlFileStore, claimExpired, claimExpiry, compilePermissionPreset, costReportFromJournal, createEngine, hashRunArgs, parseModelRef, priceUsdOf, proposalStatement, remeasureQueue, resolvePricing, runProfile, sanitizeTerminalText } from "@rulvar/core";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { existsSync, statSync } from "node:fs";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
@@ -190,18 +190,21 @@ function renderEventLine(event) {
|
|
|
190
190
|
}
|
|
191
191
|
function rawEventLine(event) {
|
|
192
192
|
const replayMark = event.replayed === true ? "replay " : "";
|
|
193
|
+
const str = (value) => typeof value === "string" ? value : "";
|
|
194
|
+
const num = (value) => typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
195
|
+
const who = str(event.agentType) || "(anon)";
|
|
193
196
|
switch (event.type) {
|
|
194
|
-
case "run:start": return `run ${event.runId} ${event.resumed ? "resumed" : "started"} (workflow ${event.workflow})`;
|
|
195
|
-
case "phase:start": return `phase ${event.phase}`;
|
|
196
|
-
case "agent:start": return `${replayMark}agent ${
|
|
197
|
-
case "agent:end": return `${replayMark}agent ${
|
|
198
|
-
case "agent:error": return `agent ${
|
|
199
|
-
case "agent:queued": return `agent ${
|
|
200
|
-
case "tool:start": return `${replayMark}tool ${event.toolName}`;
|
|
201
|
-
case "tool:end": return `${replayMark}tool ${event.toolName} ${event.outcome} (${event.durationMs}ms)`;
|
|
202
|
-
case "approval:pending": return `approval pending: tool ${event.toolName} (entry ${event.entryRef})`;
|
|
203
|
-
case "log": return event.level === "debug" ? void 0 : `${event.level}: ${event.msg}`;
|
|
204
|
-
case "run:end": return `run ${event.runId} ${event.status}`;
|
|
197
|
+
case "run:start": return `run ${str(event.runId)} ${event.resumed === true ? "resumed" : "started"} (workflow ${str(event.workflow)})`;
|
|
198
|
+
case "phase:start": return `phase ${str(event.phase)}`;
|
|
199
|
+
case "agent:start": return `${replayMark}agent ${who}${str(event.label) === "" ? "" : ` [${str(event.label)}]`} ${str(event.role)} on ${str(event.model)}`;
|
|
200
|
+
case "agent:end": return `${replayMark}agent ${who} ${str(event.status)} (${money(num(event.costUsd))}, ${String(num(event.usage?.inputTokens) + num(event.usage?.outputTokens))} tok)`;
|
|
201
|
+
case "agent:error": return `agent ${who} error: ${str(event.error?.message)}${event.willRetry === true ? " (will retry)" : ""}`;
|
|
202
|
+
case "agent:queued": return `agent ${who} queued`;
|
|
203
|
+
case "tool:start": return `${replayMark}tool ${str(event.toolName)}`;
|
|
204
|
+
case "tool:end": return `${replayMark}tool ${str(event.toolName)} ${str(event.outcome)} (${String(num(event.durationMs))}ms)`;
|
|
205
|
+
case "approval:pending": return `approval pending: tool ${str(event.toolName)} (entry ${String(num(event.entryRef))})`;
|
|
206
|
+
case "log": return event.level === "debug" ? void 0 : `${str(event.level)}: ${str(event.msg)}`;
|
|
207
|
+
case "run:end": return `run ${str(event.runId)} ${str(event.status)}`;
|
|
205
208
|
default: return;
|
|
206
209
|
}
|
|
207
210
|
}
|
|
@@ -300,6 +303,35 @@ async function driveRun(options) {
|
|
|
300
303
|
handle = options.engine.resume(handle.runId, options.workflow, { args: options.args });
|
|
301
304
|
}
|
|
302
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Renders the `resume --dry-run` preview (the v1.23.0 review): the
|
|
308
|
+
* replay accounting from `handle.preview`, then what a real resume
|
|
309
|
+
* would do. The engine's replay-strict mode guarantees zero journal or
|
|
310
|
+
* meta writes and zero adapter calls. A preview that stops at a
|
|
311
|
+
* would-be-live call is a SUCCESSFUL preview (the miss IS the answer),
|
|
312
|
+
* so the exit code is 0 either way; only structural failures (missing
|
|
313
|
+
* run, unregistered workflow, args refusal) exit nonzero via their
|
|
314
|
+
* typed errors.
|
|
315
|
+
*/
|
|
316
|
+
async function reportDryRun(handle, io) {
|
|
317
|
+
const outcome = await handle.result;
|
|
318
|
+
const preview = await handle.preview;
|
|
319
|
+
io.err("dry-run preview (zero journal or meta writes, zero adapter calls):");
|
|
320
|
+
io.err(` hits: ${preview.hits} misses: ${preview.misses} reruns: ${preview.reruns} skipped: ${preview.skipped}`);
|
|
321
|
+
io.err(preview.orphaned.length === 0 ? " orphaned effect roots: none" : ` orphaned effect roots (entryRefs): ${preview.orphaned.join(", ")}`);
|
|
322
|
+
if (preview.invalidResolutions.length === 0) io.err(" invalid resolutions: none");
|
|
323
|
+
else for (const invalid of preview.invalidResolutions) io.err(` invalid resolution at seq ${invalid.seq}: ${invalid.detail}`);
|
|
324
|
+
if (outcome.error?.code === "journal_miss") {
|
|
325
|
+
io.err(` stopped at the first would-be-live call: ${outcome.error.message}`);
|
|
326
|
+
io.err(" a real resume would perform new paid work from this point");
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
io.err(` would settle: ${outcome.status}`);
|
|
330
|
+
if (outcome.error !== void 0) io.err(` error: ${outcome.error.message}`);
|
|
331
|
+
for (const pending of outcome.pending) io.err(` pending: ${pending.key} (entry ${pending.entryRef})`);
|
|
332
|
+
if (outcome.value !== void 0) io.out(JSON.stringify(outcome.value, null, 2));
|
|
333
|
+
return 0;
|
|
334
|
+
}
|
|
303
335
|
/** Renders the settled outcome; returns the process exit code. */
|
|
304
336
|
function reportOutcome(outcome, io) {
|
|
305
337
|
io.err(`status: ${outcome.status}`);
|
|
@@ -363,7 +395,12 @@ const GRAMMAR = {
|
|
|
363
395
|
resume: {
|
|
364
396
|
command: "resume",
|
|
365
397
|
positionals: ["<runId>"],
|
|
366
|
-
flags: [
|
|
398
|
+
flags: [
|
|
399
|
+
ARGS,
|
|
400
|
+
STORE,
|
|
401
|
+
{ name: "dry-run" },
|
|
402
|
+
{ name: "allow-args-change" }
|
|
403
|
+
]
|
|
367
404
|
},
|
|
368
405
|
"runs ls": {
|
|
369
406
|
command: "runs ls",
|
|
@@ -642,10 +679,52 @@ async function runCommand(argv, context) {
|
|
|
642
679
|
args
|
|
643
680
|
}), context.io);
|
|
644
681
|
}
|
|
682
|
+
/**
|
|
683
|
+
* The resume args safety gate (the v1.23.0 review): a run's logical
|
|
684
|
+
* identity includes its genesis args, so a resume that silently drops,
|
|
685
|
+
* adds, or changes them is refused BEFORE the engine starts (zero
|
|
686
|
+
* provider calls, zero journal writes). `--allow-args-change` is the
|
|
687
|
+
* explicit override for every divergence class; legacy runs recorded
|
|
688
|
+
* before the binding existed require it (or explicit `--args`) because
|
|
689
|
+
* nothing can be verified against them.
|
|
690
|
+
*/
|
|
691
|
+
function enforceArgsBinding(input) {
|
|
692
|
+
const { meta, argsGiven, args, allowChange, io } = input;
|
|
693
|
+
if (meta.argsProvided === void 0) {
|
|
694
|
+
if (!argsGiven && !allowChange) throw new ConfigError(`run '${meta.runId}' predates the args binding (rulvar < 1.24.0), so the CLI cannot tell whether it was started with --args, and resuming without them silently changes the logical run if any were used at start. Re-supply the original --args, or acknowledge explicitly with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
695
|
+
if (argsGiven) io.err(`warning: run '${meta.runId}' predates the args binding; the supplied --args cannot be verified against the original invocation`);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (meta.argsProvided) {
|
|
699
|
+
if (!argsGiven) {
|
|
700
|
+
if (!allowChange) throw new ConfigError(`run '${meta.runId}' was started WITH args, but this resume supplies none; the workflow would see undefined and every args-dependent call would become new paid work instead of a replay. Re-supply the original --args, or force the change with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
701
|
+
io.err(`warning: resuming '${meta.runId}' without its genesis args (--allow-args-change)`);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
if (meta.argsHash === void 0) {
|
|
705
|
+
io.err(`warning: run '${meta.runId}' recorded args presence but no hash (the genesis args were not JCS-serializable); the supplied --args cannot be verified`);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const supplied = hashRunArgs(args);
|
|
709
|
+
if (supplied !== meta.argsHash) {
|
|
710
|
+
if (!allowChange) throw new ConfigError(`--args does not match the args run '${meta.runId}' was started with (recorded hash ${meta.argsHash.slice(0, 12)}, supplied ${supplied?.slice(0, 12) ?? "none"}); changed args silently change the logical run and re-pay every args-dependent call. Force deliberately with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
711
|
+
io.err(`warning: resuming '${meta.runId}' with changed args (--allow-args-change)`);
|
|
712
|
+
}
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (argsGiven) {
|
|
716
|
+
if (!allowChange) throw new ConfigError(`run '${meta.runId}' was started WITHOUT args, but this resume supplies some; added args silently change the logical run. Drop --args, or force the change with --allow-args-change; ${usageOf(GRAMMAR.resume)}`);
|
|
717
|
+
io.err(`warning: resuming no-args run '${meta.runId}' with args (--allow-args-change)`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
645
720
|
async function resumeCommand(argv, context) {
|
|
646
721
|
const parsed = parseCommand(GRAMMAR.resume, argv);
|
|
647
722
|
const runId = parsed.positionals[0];
|
|
648
|
-
const
|
|
723
|
+
const rawArgs = parsed.values.args;
|
|
724
|
+
const args = parseArgsJson(rawArgs);
|
|
725
|
+
const argsGiven = rawArgs !== void 0;
|
|
726
|
+
const dryRun = parsed.values["dry-run"] === true;
|
|
727
|
+
const allowChange = parsed.values["allow-args-change"] === true;
|
|
649
728
|
const store = parsed.values.store;
|
|
650
729
|
const assembled = assembleEngine({
|
|
651
730
|
config: await loadCliConfig(context.cwd),
|
|
@@ -654,10 +733,21 @@ async function resumeCommand(argv, context) {
|
|
|
654
733
|
});
|
|
655
734
|
const meta = (await assembled.store.listRuns()).find((m) => m.runId === runId);
|
|
656
735
|
if (meta === void 0) throw new ConfigError(`run '${runId}' not found in the store`);
|
|
736
|
+
enforceArgsBinding({
|
|
737
|
+
meta,
|
|
738
|
+
argsGiven,
|
|
739
|
+
args,
|
|
740
|
+
allowChange,
|
|
741
|
+
io: context.io
|
|
742
|
+
});
|
|
657
743
|
const name = meta.workflowName;
|
|
658
744
|
const workflow = name === void 0 ? void 0 : assembled.workflows[name];
|
|
659
745
|
if (workflow === void 0) throw new ConfigError(`run '${runId}' was started from workflow '${name ?? "(unknown)"}'; register it under that name in rulvar.config.mjs workflows to resume (resume requires the in-process workflow value)`);
|
|
660
|
-
const first = assembled.engine.resume(runId, workflow, {
|
|
746
|
+
const first = assembled.engine.resume(runId, workflow, {
|
|
747
|
+
args,
|
|
748
|
+
...dryRun ? { dryRun: true } : {}
|
|
749
|
+
});
|
|
750
|
+
if (dryRun) return await reportDryRun(first, context.io);
|
|
661
751
|
return reportOutcome(await driveRun({
|
|
662
752
|
engine: assembled.engine,
|
|
663
753
|
workflow,
|
|
@@ -698,6 +788,7 @@ async function inspectCommand(argv, context) {
|
|
|
698
788
|
const entries = await assembled.store.load(runId);
|
|
699
789
|
context.io.out(`run ${meta.runId}: ${meta.status} (updated ${meta.updatedAt})`);
|
|
700
790
|
if (meta.workflowName !== void 0) context.io.out(`workflow: ${meta.workflowName}`);
|
|
791
|
+
if (meta.argsProvided !== void 0) context.io.out(meta.argsProvided ? `args at genesis: provided${meta.argsHash === void 0 ? " (no hash: not JCS-serializable)" : ` (hash ${meta.argsHash})`}` : "args at genesis: none");
|
|
701
792
|
const byKind = /* @__PURE__ */ new Map();
|
|
702
793
|
let openSuspensions = 0;
|
|
703
794
|
const resolvedRefs = /* @__PURE__ */ new Set();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "Rulvar shell: run/resume/runs/inspect/plan/kb commands, TUI progress, createServer, createWorker, OTel exporter.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,17 +22,17 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rulvar/core": "1.
|
|
25
|
+
"@rulvar/core": "1.24.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.20.0",
|
|
29
29
|
"tsdown": "^0.22.3",
|
|
30
30
|
"typescript": "~6.0.3",
|
|
31
|
-
"@rulvar/
|
|
32
|
-
"@rulvar/
|
|
33
|
-
"@rulvar/
|
|
34
|
-
"@rulvar/evals": "1.
|
|
35
|
-
"@rulvar/
|
|
31
|
+
"@rulvar/testing": "1.24.0",
|
|
32
|
+
"@rulvar/store-sqlite": "1.24.0",
|
|
33
|
+
"@rulvar/planner": "1.24.0",
|
|
34
|
+
"@rulvar/evals": "1.24.0",
|
|
35
|
+
"@rulvar/plan": "1.24.0"
|
|
36
36
|
},
|
|
37
37
|
"bin": {
|
|
38
38
|
"rulvar": "./dist/cli.js"
|