@rulvar/cli 1.7.0 → 1.9.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as runCli, t as processIo } from "./io-CizJX0rK.js";
2
+ import { r as runCli, t as processIo } from "./io-CmDfbnun.js";
3
3
  //#region src/cli.ts
4
4
  /**
5
5
  * The `rulvar` bin entry: thin wrapper over runCli with process io.
@@ -1,4 +1,4 @@
1
- import { CURRENT_HASH_VERSION, ConfigError, DedupIndex, InMemoryStore, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LineageIndex, PlanInvariantError, ROOT_ACCOUNT, ReplayPlanHashMismatch, TerminationAccount, approachSigCoarse, buildTerminationInitValue, canonicalIsolationTag, canonicalizeLadder, checkpointRefFor, countsAgainstLimit, createEngine, defineWorkflow, deriverV2, evaluateReuse, foldTermination, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, makeOrchestratorWorkflow, nodeLinkKey, normalizeApproachTag, orchestrate, planNodeScope, profileRegistrySnapshotHash, tool, validateTerminationLimits } from "@rulvar/core";
1
+ import { AdmissionRejectedError, BudgetExhaustedError, CURRENT_HASH_VERSION, ConfigError, DedupIndex, InMemoryStore, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LineageIndex, PlanInvariantError, ROOT_ACCOUNT, ReplayPlanHashMismatch, TerminationAccount, approachSigCoarse, buildTerminationInitValue, canonicalIsolationTag, canonicalizeLadder, checkpointRefFor, countsAgainstLimit, createEngine, defineWorkflow, deriverV2, evaluateReuse, foldTermination, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, makeOrchestratorWorkflow, nodeLinkKey, normalizeApproachTag, orchestrate, planNodeScope, profileRegistrySnapshotHash, tool, validateTerminationLimits } from "@rulvar/core";
2
2
  //#region ../plan/dist/index.js
3
3
  /**
4
4
  * Plan scope substrate (M7-T01): TaskPlan as engine-owned typed data, the
@@ -3053,17 +3053,51 @@ function planRunner(options) {
3053
3053
  ...ladder === void 0 ? {} : ladderDispatchFields(node, spec, ladder)
3054
3054
  };
3055
3055
  };
3056
+ /**
3057
+ * A dispatch refused by budget/admission facts that changed AFTER the
3058
+ * op was admitted (another child consumed the shared parent between
3059
+ * turns, a rung ceiling re-resolved differently) lands as a terminal
3060
+ * plan decision: the node fails loudly instead of sitting ready
3061
+ * forever, the other ready nodes still dispatch, and the wake digest
3062
+ * carries the failure to the orchestrator (the v1.7.0 follow-up
3063
+ * review's P1: no stranded node without a dispatch root or terminal
3064
+ * decision). Everything else (engine bugs) still propagates.
3065
+ */
3066
+ const landDispatchRejection = async (node, from, thrown) => {
3067
+ const message = thrown instanceof Error ? thrown.message : String(thrown);
3068
+ io.emit({
3069
+ type: "log",
3070
+ level: "warn",
3071
+ msg: `plan node ${node.nodeId} failed dispatch admission and lands terminally failed: ${message}`,
3072
+ data: {
3073
+ nodeId: node.nodeId,
3074
+ logicalTaskId: node.logicalTaskId
3075
+ }
3076
+ });
3077
+ await appendPlanDecision("dispatch-rejected", [{
3078
+ kind: "set_node_status",
3079
+ nodeId: node.nodeId,
3080
+ from,
3081
+ to: "failed",
3082
+ cause: "dispatch-rejected",
3083
+ causeRef: Math.max(planCursor, 0)
3084
+ }], Math.max(planCursor, 0));
3085
+ };
3086
+ const isDispatchRejection = (thrown) => thrown instanceof AdmissionRejectedError || thrown instanceof BudgetExhaustedError;
3056
3087
  /** Dispatches every ready node under its plan/NodeId scope. */
3057
3088
  const scheduleReady = async () => {
3058
3089
  for (const node of Object.values(fold.plan.nodes)) {
3059
3090
  if (node.status === "running" && !dispatched.has(node.nodeId)) {
3060
3091
  const spec = fold.specs[node.nodeId];
3061
- if (spec !== void 0) {
3092
+ if (spec !== void 0) try {
3062
3093
  const { handle } = await io.dispatch(buildDispatchSpec(node, spec), nodeScopeOf(node.nodeId), {
3063
3094
  nodeId: node.nodeId,
3064
3095
  logicalTaskId: node.logicalTaskId
3065
3096
  });
3066
3097
  dispatched.set(node.nodeId, handle);
3098
+ } catch (thrown) {
3099
+ if (!isDispatchRejection(thrown)) throw thrown;
3100
+ await landDispatchRejection(node, "running", thrown);
3067
3101
  }
3068
3102
  continue;
3069
3103
  }
@@ -3090,10 +3124,17 @@ function planRunner(options) {
3090
3124
  }
3091
3125
  const spec = fold.specs[node.nodeId];
3092
3126
  if (spec === void 0) continue;
3093
- const { handle } = await io.dispatch(buildDispatchSpec(node, spec), nodeScopeOf(node.nodeId), {
3094
- nodeId: node.nodeId,
3095
- logicalTaskId: node.logicalTaskId
3096
- });
3127
+ let handle;
3128
+ try {
3129
+ ({handle} = await io.dispatch(buildDispatchSpec(node, spec), nodeScopeOf(node.nodeId), {
3130
+ nodeId: node.nodeId,
3131
+ logicalTaskId: node.logicalTaskId
3132
+ }));
3133
+ } catch (thrown) {
3134
+ if (!isDispatchRejection(thrown)) throw thrown;
3135
+ await landDispatchRejection(node, "ready", thrown);
3136
+ continue;
3137
+ }
3097
3138
  dispatched.set(node.nodeId, handle);
3098
3139
  await appendPlanDecision("child-result", [{
3099
3140
  kind: "set_node_status",
@@ -3174,6 +3215,14 @@ function planRunner(options) {
3174
3215
  return { entryRef: entry.seq };
3175
3216
  },
3176
3217
  planRevise: async (request) => writeLock.runExclusive(async () => {
3218
+ const withVerdictDetail = (outcomes, admissions) => outcomes.map((outcome, opIndex) => {
3219
+ if (outcome.kind !== "dropped" || outcome.reason !== "admission_denied") return outcome;
3220
+ const verdict = admissions.find((admission) => admission.opIndex === opIndex)?.decision.verdict;
3221
+ return verdict !== void 0 && verdict.kind === "reject" ? {
3222
+ ...outcome,
3223
+ verdictReason: verdict.reason
3224
+ } : outcome;
3225
+ });
3177
3226
  await io.flush();
3178
3227
  absorbPlan();
3179
3228
  absorbGuardVerdicts();
@@ -3203,7 +3252,7 @@ function planRunner(options) {
3203
3252
  }
3204
3253
  await scheduleReady();
3205
3254
  return {
3206
- outcomes: value.outcomes,
3255
+ outcomes: withVerdictDetail(value.outcomes, value.admissions),
3207
3256
  assignedNodeIds: value.assignedNodeIds,
3208
3257
  planHashAfter: value.planHashAfter,
3209
3258
  droppedAll: value.outcomes.every((outcome) => outcome.kind === "dropped"),
@@ -3217,6 +3266,7 @@ function planRunner(options) {
3217
3266
  const freshNotes = /* @__PURE__ */ new Map();
3218
3267
  const frozen = io.snapshot().some((candidate) => candidate.kind === "decision" && candidate.value?.decisionType === "orchestrator_budget_cap");
3219
3268
  const claimedThisRevision = /* @__PURE__ */ new Set();
3269
+ let pendingReserveUsd = 0;
3220
3270
  const evaluation = rebasePlanRevision(request, {
3221
3271
  state: fold,
3222
3272
  frozen,
@@ -3283,13 +3333,47 @@ function planRunner(options) {
3283
3333
  depth: 1
3284
3334
  }
3285
3335
  };
3336
+ const profile = io.profiles[op.spec.agentType];
3337
+ const childAccount = rootScope === "" ? planNodeScope(nodeId) : `${rootScope}/${planNodeScope(nodeId)}`;
3338
+ const ladder = canonicalLadderOf(profile);
3339
+ const dispatchCeilingUsd = (() => {
3340
+ if (ladder !== void 0) {
3341
+ const startTier = clampStartTier(ladder, op.spec.model_hint?.startTier);
3342
+ const raises = op.lineage === void 0 ? 0 : requireAccount().rungIndexOf(op.lineage.continues);
3343
+ const rung = ladder.rungs[executingRungOf(ladder, startTier, raises)];
3344
+ if (rung?.maxCostUsd !== void 0) return rung.maxCostUsd;
3345
+ }
3346
+ return op.spec.budgetUsd;
3347
+ })();
3348
+ if (profile?.estCost !== void 0 && dispatchCeilingUsd !== void 0 && profile.estCost > dispatchCeilingUsd) return {
3349
+ verdict: {
3350
+ kind: "reject",
3351
+ reason: {
3352
+ code: "reserve_exceeds_budget",
3353
+ agentType: op.spec.agentType,
3354
+ childAccount,
3355
+ estCostUsd: profile.estCost,
3356
+ resolvedReserveUsd: Math.min(profile.estCost, dispatchCeilingUsd),
3357
+ childCeilingUsd: dispatchCeilingUsd,
3358
+ minimumBudgetUsd: profile.estCost,
3359
+ message: `agent profile '${op.spec.agentType}' declares estCost ${profile.estCost.toFixed(4)} USD, which cannot fit the child ceiling ${dispatchCeilingUsd.toFixed(4)} USD of account '${childAccount}'; raise budgetUsd to at least ${profile.estCost.toFixed(4)} or pick a cheaper profile`
3360
+ }
3361
+ },
3362
+ statsBefore: {
3363
+ spawnsBefore: 0,
3364
+ childrenOfParentBefore: 0,
3365
+ depth: 1
3366
+ }
3367
+ };
3286
3368
  const admitted = io.admission.admit({
3287
3369
  origin: "spawn_agent",
3288
3370
  name: op.spec.agentType,
3289
- childScope: rootScope === "" ? planNodeScope(nodeId) : `${rootScope}/${planNodeScope(nodeId)}`,
3371
+ childScope: childAccount,
3290
3372
  parentAccountScope: ROOT_ACCOUNT,
3291
3373
  nodeKey: planScope,
3292
3374
  ...op.spec.budgetUsd === void 0 ? {} : { budgetUsd: op.spec.budgetUsd },
3375
+ ...profile?.estCost === void 0 ? {} : { estCostUsd: profile.estCost },
3376
+ ...pendingReserveUsd === 0 ? {} : { pendingReserveUsd },
3293
3377
  ...op.lineage === void 0 ? {} : { lineage: op.lineage },
3294
3378
  ...(op.approach ?? op.spec.approach) === void 0 ? {} : { approach: op.approach ?? op.spec.approach },
3295
3379
  signature: {
@@ -3298,6 +3382,10 @@ function planRunner(options) {
3298
3382
  },
3299
3383
  ladderLength: ladderLengthOf(io.profiles[op.spec.agentType])
3300
3384
  }, { commitReserve: false });
3385
+ if (admitted.verdict.kind === "admit") pendingReserveUsd += io.admission.projectedDispatchReserveUsd({
3386
+ ...profile?.estCost === void 0 ? {} : { estCostUsd: profile.estCost },
3387
+ ...op.spec.budgetUsd === void 0 ? {} : { budgetUsd: op.spec.budgetUsd }
3388
+ });
3301
3389
  const note = freshNotes.get(opIndex);
3302
3390
  if (note !== void 0 && admitted.verdict.kind === "admit") return {
3303
3391
  ...admitted,
@@ -3311,12 +3399,13 @@ function planRunner(options) {
3311
3399
  admitUnpark: (op, node) => {
3312
3400
  const spec = fold.specs[op.nodeId];
3313
3401
  const wasDispatched = nodeRootOf(op.nodeId) !== void 0;
3314
- return io.admission.admit({
3402
+ const unparkAdmitted = io.admission.admit({
3315
3403
  origin: "spawn_agent",
3316
3404
  name: spec?.agentType ?? "unknown",
3317
3405
  childScope: nodeScopeOf(op.nodeId),
3318
3406
  parentAccountScope: ROOT_ACCOUNT,
3319
3407
  nodeKey: planScope,
3408
+ ...pendingReserveUsd === 0 ? {} : { pendingReserveUsd },
3320
3409
  ...wasDispatched ? { lineage: {
3321
3410
  continues: node.logicalTaskId,
3322
3411
  causeRef: node.checkpointRef ?? Math.max(planCursor, 1),
@@ -3328,6 +3417,8 @@ function planRunner(options) {
3328
3417
  },
3329
3418
  ladderLength: ladderLengthOf(io.profiles[spec?.agentType ?? ""])
3330
3419
  }, { commitReserve: false });
3420
+ if (unparkAdmitted.verdict.kind === "admit") pendingReserveUsd += io.admission.projectedDispatchReserveUsd({});
3421
+ return unparkAdmitted;
3331
3422
  },
3332
3423
  lineageCheck: (continues) => {
3333
3424
  const index = io.admission.lineage();
@@ -3362,19 +3453,6 @@ function planRunner(options) {
3362
3453
  consumedRevisionSeqs.add(entry.seq);
3363
3454
  await io.flush();
3364
3455
  absorbPlan();
3365
- await drainGuardVerdicts();
3366
- await landReuseLinks(entry);
3367
- await landCancelAbandons(value, entry.seq);
3368
- await landRevisionEscalations(value);
3369
- for (const outcome of evaluation.outcomes) {
3370
- if (outcome.kind === "dropped") continue;
3371
- const applied = outcome.kind === "applied" ? outcome.op : outcome.applied;
3372
- if ((applied.op === "cancel_task" || applied.op === "park_task") && applied.requestOnly === true) {
3373
- const handle = dispatched.get(applied.nodeId);
3374
- if (handle !== void 0) io.cancel(handle, applied.op === "cancel_task" ? applied.reason : "park_task");
3375
- }
3376
- }
3377
- await scheduleReady();
3378
3456
  const appliedCount = evaluation.outcomes.filter((o) => o.kind !== "dropped").length;
3379
3457
  io.emit({
3380
3458
  type: "plan:revised",
@@ -3391,8 +3469,21 @@ function planRunner(options) {
3391
3469
  remaining: debit.balanceAfter,
3392
3470
  phi: requireAccount().phi()
3393
3471
  });
3472
+ await drainGuardVerdicts();
3473
+ await landReuseLinks(entry);
3474
+ await landCancelAbandons(value, entry.seq);
3475
+ await landRevisionEscalations(value);
3476
+ for (const outcome of evaluation.outcomes) {
3477
+ if (outcome.kind === "dropped") continue;
3478
+ const applied = outcome.kind === "applied" ? outcome.op : outcome.applied;
3479
+ if ((applied.op === "cancel_task" || applied.op === "park_task") && applied.requestOnly === true) {
3480
+ const handle = dispatched.get(applied.nodeId);
3481
+ if (handle !== void 0) io.cancel(handle, applied.op === "cancel_task" ? applied.reason : "park_task");
3482
+ }
3483
+ }
3484
+ await scheduleReady();
3394
3485
  return {
3395
- outcomes: evaluation.outcomes,
3486
+ outcomes: withVerdictDetail(evaluation.outcomes, evaluation.admissions),
3396
3487
  assignedNodeIds: evaluation.assignedNodeIds,
3397
3488
  planHashAfter: evaluation.planHashAfter,
3398
3489
  droppedAll: evaluation.droppedAll,
@@ -3486,8 +3577,8 @@ function planRunner(options) {
3486
3577
  maxDepth: options?.limits?.maxDepth ?? 1,
3487
3578
  kMax: kMaxOf(io.profiles),
3488
3579
  runBudgetUsdCeiling: io.runCeilingUsd ?? 0,
3489
- orchestratorCapUsd: 0,
3490
- finalizeReserveUsd: 0
3580
+ orchestratorCapUsd: io.orchestratorCapUsd ?? 0,
3581
+ finalizeReserveUsd: io.finalizeReserveUsd ?? 0
3491
3582
  });
3492
3583
  const value = buildTerminationInitValue(limits, profileRegistrySnapshotHash(io.profiles));
3493
3584
  await io.append({
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-CizJX0rK.js";
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-CmDfbnun.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
  /**
@@ -557,7 +557,7 @@ async function kbInboxCommand(argv, context) {
557
557
  if (flags.positionals.length > 0) throw new ConfigError("usage: rulvar kb inbox [--store PATH]");
558
558
  let plan;
559
559
  try {
560
- plan = await import("./dist-DIqBj4U-.js");
560
+ plan = await import("./dist-A5zLp_kr.js");
561
561
  } catch {
562
562
  throw new ConfigError("rulvar kb inbox requires @rulvar/plan (the RunLedger fold behind the LedgerExport seam)");
563
563
  }
@@ -700,7 +700,7 @@ async function kbGateCommand(argv, context) {
700
700
  ].includes(confidence)) throw new ConfigError(`--confidence must be high, medium or low, got '${String(values.confidence)}'`);
701
701
  let plan;
702
702
  try {
703
- plan = await import("./dist-DIqBj4U-.js");
703
+ plan = await import("./dist-A5zLp_kr.js");
704
704
  } catch {
705
705
  throw new ConfigError("rulvar kb gate requires @rulvar/plan (the RunLedger fold behind the LedgerExport seam)");
706
706
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/cli",
3
- "version": "1.7.0",
3
+ "version": "1.9.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.7.0"
25
+ "@rulvar/core": "1.9.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/store-sqlite": "1.7.0",
32
- "@rulvar/testing": "1.7.0",
33
- "@rulvar/planner": "1.7.0",
34
- "@rulvar/evals": "1.7.0",
35
- "@rulvar/plan": "1.7.0"
31
+ "@rulvar/testing": "1.9.0",
32
+ "@rulvar/store-sqlite": "1.9.0",
33
+ "@rulvar/plan": "1.9.0",
34
+ "@rulvar/evals": "1.9.0",
35
+ "@rulvar/planner": "1.9.0"
36
36
  },
37
37
  "bin": {
38
38
  "rulvar": "./dist/cli.js"