@rulvar/core 1.222.0 → 1.224.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/index.d.ts CHANGED
@@ -7623,8 +7623,10 @@ interface RunOptions {
7623
7623
  * used to be per-invocation and unrecorded, so a resumed segment
7624
7624
  * silently ran without the bound the original invocation declared
7625
7625
  * (the seventeenth comparison benchmark's top FinOps gap). A run
7626
- * started without the cap stays uncapped for its whole life, and
7627
- * ResumeOptions deliberately has no field to override it.
7626
+ * started without the cap stays uncapped for its whole life unless
7627
+ * a host changes the posture through the explicit, validated,
7628
+ * journaled ResumeOptions.run override (RV2208); nothing changes it
7629
+ * silently.
7628
7630
  */
7629
7631
  maxInFlightExposureUsd?: number;
7630
7632
  /**
@@ -7637,7 +7639,9 @@ interface RunOptions {
7637
7639
  * and `allowUnpriced` (exact model refs the host KNOWS are free,
7638
7640
  * the explicit exception). Recorded in RunMeta at genesis and
7639
7641
  * restored on every resume, the exposure cap's rule (RV1504): a
7640
- * FinOps posture a resumed segment silently drops is not a posture.
7642
+ * FinOps posture a resumed segment silently drops is not a posture
7643
+ * (and unlike the two ceilings, ResumeOptions.run has no field for
7644
+ * this gate: pricing hygiene is not a per-segment decision).
7641
7645
  * Absent by default: dispatch behavior stays byte identical, and an
7642
7646
  * unpriced model keeps debiting nothing, the documented ceiling
7643
7647
  * hole this mode exists to close.
@@ -7711,6 +7715,27 @@ interface ResumeOptions {
7711
7715
  * meta/blob surfaces remain advisory (the fenced run state RFC).
7712
7716
  */
7713
7717
  lease?: Lease;
7718
+ /**
7719
+ * Ceiling overrides for the resumed segment and the run's remaining
7720
+ * life (RV2208). The RV1504 rule stands: the RunMeta-recorded
7721
+ * posture is what a bare resume restores; this field is the ONE
7722
+ * explicit way to change that posture after genesis. Each supplied
7723
+ * value is validated exactly like its RunOptions counterpart,
7724
+ * applied to this segment's budget, written back by the segment's
7725
+ * first meta write (a LATER bare resume restores the overridden
7726
+ * posture, not the genesis one), and journaled as a
7727
+ * `run_budget_override` decision naming the recorded and applied
7728
+ * values and the settled spend it was judged against. A `budgetUsd`
7729
+ * below the journal's settled spend refuses typed before ownership,
7730
+ * meta, or any append: such a ceiling would exhaust the segment
7731
+ * before its first turn and read like a fresh money death. Absent
7732
+ * fields keep the recorded values; an absent object keeps the
7733
+ * historical behavior byte for byte.
7734
+ */
7735
+ run?: {
7736
+ budgetUsd?: number;
7737
+ maxInFlightExposureUsd?: number;
7738
+ };
7714
7739
  }
7715
7740
  interface ResumeHandle<R> extends RunHandle<R> {
7716
7741
  /** Resolves at settle with the replay accounting. */
@@ -7726,7 +7751,9 @@ interface Engine {
7726
7751
  * A compiled run resumes WITHOUT wf: the engine rehydrates the
7727
7752
  * persisted source pinned by workflowHash; supplying a compiled wf
7728
7753
  * whose source hash differs from the recorded one is a typed
7729
- * ConfigError (M6-T02).
7754
+ * ConfigError (M6-T02). ResumeOptions.run (RV2208) overrides the
7755
+ * recorded budget ceilings for the run's remaining life, with a
7756
+ * journaled decision and a typed floor at the settled spend.
7730
7757
  */
7731
7758
  resume<A, R>(runId: string, wf?: Workflow<A, R> | CompiledWorkflow, options?: ResumeOptions): ResumeHandle<R>;
7732
7759
  /**
package/dist/index.js CHANGED
@@ -12719,7 +12719,8 @@ async function runAgent(options) {
12719
12719
  kind: "budget",
12720
12720
  retryable: false
12721
12721
  };
12722
- errorMessage = thrown instanceof Error ? thrown.message : String(thrown);
12722
+ const lastMessage = messages[messages.length - 1];
12723
+ errorMessage = (grantedRepairTurns > 0 && lastMessage !== void 0 && lastMessage.parts.some((part) => part.type === "tool-result" && part.name === options.terminalTool?.name && part.isError === true) ? "the granted repair turn could not be funded: " : "") + (thrown instanceof Error ? thrown.message : String(thrown));
12723
12724
  break;
12724
12725
  }
12725
12726
  turns += 1;
@@ -24228,8 +24229,32 @@ function makeOrchestratorWorkflow(goal, opts) {
24228
24229
  try {
24229
24230
  result = await runtime.runInScope(orchestratorState, () => ctx.agent(orchestratorPrompt(goal, opts?.maxSpawns, promptLines.length === 0 ? void 0 : promptLines), agentOpts));
24230
24231
  } catch (thrown) {
24232
+ if (thrown instanceof BudgetExhaustedError) {
24233
+ const repairEntryRef = thrown.data?.entryRef;
24234
+ const repairTerminal = typeof repairEntryRef === "number" ? internals.replayer.snapshot().find((entry) => entry.seq === repairEntryRef) : void 0;
24235
+ const repairMessage = repairTerminal?.error?.message ?? "";
24236
+ if (repairMessage.includes("the granted repair turn could not be funded: ")) {
24237
+ const repairDeclineKey = deriverV2.deriveKey({ kind: "orchestrator-repair-grant-declined" });
24238
+ if (!internals.replayer.snapshot().some((entry) => entry.kind === "decision" && entry.key === repairDeclineKey)) await internals.replayer.appendSinglePhase({
24239
+ scope: callingState.scope,
24240
+ key: repairDeclineKey,
24241
+ kind: "decision",
24242
+ status: "ok",
24243
+ spanId: internals.spans.mint(callingState.spanId),
24244
+ site: "orchestrator-budget",
24245
+ value: {
24246
+ decisionType: "orchestrator_repair_grant_declined",
24247
+ reason: repairMessage.slice(0, 300),
24248
+ terminalRef: repairTerminal?.seq ?? null,
24249
+ remainingUsd: internals.budget.remainingUsd() ?? null
24250
+ }
24251
+ });
24252
+ throw new FailRunError(`the orchestrator finish could not complete its granted repair: ${repairMessage}`, { data: { source: "orchestrator_finish_validation" } });
24253
+ }
24254
+ }
24231
24255
  const budgetReason = thrown instanceof BudgetExhaustedError ? thrown.data?.reason : void 0;
24232
- if (budgetReason !== "in-flight-exposure" && budgetReason !== "output-floor") throw thrown;
24256
+ const crossed = thrown instanceof BudgetExhaustedError ? thrown.data : void 0;
24257
+ if (budgetReason !== "in-flight-exposure" && budgetReason !== "output-floor" && crossed?.source !== "root" && crossed?.account !== "run") throw thrown;
24233
24258
  const exposureKey = deriverV2.deriveKey({ kind: "orchestrator-exposure-fallback" });
24234
24259
  if (!internals.replayer.snapshot().some((entry) => entry.kind === "decision" && entry.key === exposureKey)) await internals.replayer.appendSinglePhase({
24235
24260
  scope: callingState.scope,
@@ -24240,7 +24265,7 @@ function makeOrchestratorWorkflow(goal, opts) {
24240
24265
  site: "orchestrator-budget",
24241
24266
  value: {
24242
24267
  decisionType: "orchestrator_finalize_fallback",
24243
- reason: budgetReason === "output-floor" ? "budget-floor" : "exposure-abort",
24268
+ reason: budgetReason === "output-floor" ? "budget-floor" : budgetReason === "in-flight-exposure" ? "exposure-abort" : "budget-ceiling",
24244
24269
  turnsUsed: 0,
24245
24270
  foldParams: {
24246
24271
  planHash: "",
@@ -26516,8 +26541,8 @@ function createEngine(options) {
26516
26541
  });
26517
26542
  const rootSpanId = spans.mint();
26518
26543
  let budgetSeed;
26519
- const ceilingUsd = opts?.budgetUsd ?? resumeCtx?.budgetUsd;
26520
- const exposureCapUsd = opts?.maxInFlightExposureUsd ?? resumeCtx?.maxInFlightExposureUsd;
26544
+ const ceilingUsd = opts?.budgetUsd ?? resumeCtx?.budgetOverride?.budgetUsd ?? resumeCtx?.budgetUsd;
26545
+ const exposureCapUsd = opts?.maxInFlightExposureUsd ?? resumeCtx?.budgetOverride?.maxInFlightExposureUsd ?? resumeCtx?.maxInFlightExposureUsd;
26521
26546
  const strictPricing = opts?.strictPricing === void 0 ? resumeCtx?.strictPricing : opts.strictPricing === true ? {} : opts.strictPricing === false ? void 0 : {
26522
26547
  ...opts.strictPricing.maxRatesAgeDays === void 0 ? {} : { maxRatesAgeDays: opts.strictPricing.maxRatesAgeDays },
26523
26548
  ...opts.strictPricing.allowUnpriced === void 0 ? {} : { allowUnpriced: [...opts.strictPricing.allowUnpriced] }
@@ -26565,6 +26590,8 @@ function createEngine(options) {
26565
26590
  agentsSpawned: prior.agentsSpawned,
26566
26591
  accounts: accountSpendFromJournal(replayer.snapshot(), priorPriceUsd)
26567
26592
  };
26593
+ const overrideCeilingUsd = resumeCtx.budgetOverride?.budgetUsd;
26594
+ if (overrideCeilingUsd !== void 0 && budgetSeed.usd > overrideCeilingUsd) throw new ConfigError(`ResumeOptions.run.budgetUsd ${String(overrideCeilingUsd)} is below the ${budgetSeed.usd.toFixed(4)} USD the journal already records as settled spend; a ceiling below spent exhausts the resumed segment before its first turn. Pass a value at or above the recorded spend, or resume without the override`);
26568
26595
  }
26569
26596
  const controller = new AbortController();
26570
26597
  let cancelReason;
@@ -26760,6 +26787,34 @@ function createEngine(options) {
26760
26787
  let wireError;
26761
26788
  let pending = [];
26762
26789
  if (compiled !== void 0 && resumeCtx?.strict !== true) await transcripts.put(workflowSourceRef(runId), new TextEncoder().encode(compiled.source), segmentLease.current);
26790
+ if (resumeCtx?.budgetOverride !== void 0 && resumeCtx.strict !== true) {
26791
+ const override = resumeCtx.budgetOverride;
26792
+ await replayer.appendSinglePhase({
26793
+ scope: "",
26794
+ key: deriverV2.deriveKey({
26795
+ kind: "run-budget-override",
26796
+ segment: segmentsBefore + 1
26797
+ }),
26798
+ kind: "decision",
26799
+ status: "ok",
26800
+ spanId: rootSpanId,
26801
+ site: "run-budget-override",
26802
+ value: {
26803
+ decisionType: "run_budget_override",
26804
+ source: "resume-options",
26805
+ segment: segmentsBefore + 1,
26806
+ ...override.budgetUsd === void 0 ? {} : { budgetUsd: {
26807
+ recorded: resumeCtx.budgetUsd ?? null,
26808
+ applied: override.budgetUsd
26809
+ } },
26810
+ ...override.maxInFlightExposureUsd === void 0 ? {} : { maxInFlightExposureUsd: {
26811
+ recorded: resumeCtx.maxInFlightExposureUsd ?? null,
26812
+ applied: override.maxInFlightExposureUsd
26813
+ } },
26814
+ ...budgetSeed === void 0 ? {} : { settledSpendUsd: budgetSeed.usd }
26815
+ }
26816
+ });
26817
+ }
26763
26818
  await putMeta("running");
26764
26819
  bus.emit({
26765
26820
  type: "run:start",
@@ -27004,6 +27059,8 @@ function createEngine(options) {
27004
27059
  previewResolve = resolve;
27005
27060
  });
27006
27061
  const handlePromise = (async () => {
27062
+ if (resumeOptions?.run?.budgetUsd !== void 0) requireNonNegativeNumber(resumeOptions.run.budgetUsd, "ResumeOptions.run.budgetUsd");
27063
+ if (resumeOptions?.run?.maxInFlightExposureUsd !== void 0) requireNonNegativeNumber(resumeOptions.run.maxInFlightExposureUsd, "ResumeOptions.run.maxInFlightExposureUsd");
27007
27064
  const meta = await readRunMeta(journal, runId);
27008
27065
  let supplied = wf;
27009
27066
  if (supplied === void 0 && meta?.workflowSourceRef === void 0) {
@@ -27045,12 +27102,18 @@ function createEngine(options) {
27045
27102
  code: "RULVAR_LEGACY_CACHE_SEMANTICS",
27046
27103
  type: "RulvarWarning"
27047
27104
  });
27105
+ const runOverride = resumeOptions?.run;
27106
+ const budgetOverride = runOverride === void 0 || runOverride.budgetUsd === void 0 && runOverride.maxInFlightExposureUsd === void 0 ? void 0 : {
27107
+ ...runOverride.budgetUsd === void 0 ? {} : { budgetUsd: runOverride.budgetUsd },
27108
+ ...runOverride.maxInFlightExposureUsd === void 0 ? {} : { maxInFlightExposureUsd: runOverride.maxInFlightExposureUsd }
27109
+ };
27048
27110
  return run(bound, resumeOptions?.args, void 0, {
27049
27111
  runId,
27050
27112
  priorEntries,
27051
27113
  strict: resumeOptions?.dryRun ?? false,
27052
27114
  invalidate: resumeOptions?.invalidate ?? [],
27053
27115
  ...resumeOptions?.lease === void 0 ? {} : { lease: resumeOptions.lease },
27116
+ ...budgetOverride === void 0 ? {} : { budgetOverride },
27054
27117
  ...typeof meta?.budgetUsd === "number" ? { budgetUsd: meta.budgetUsd } : {},
27055
27118
  ...typeof meta?.maxInFlightExposureUsd === "number" ? { maxInFlightExposureUsd: meta.maxInFlightExposureUsd } : {},
27056
27119
  ...typeof meta?.strictPricing === "object" && meta.strictPricing !== null ? { strictPricing: meta.strictPricing } : {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.222.0",
3
+ "version": "1.224.0",
4
4
  "description": "Rulvar core: L0 contracts, journal kernel, ctx primitives, agent runtime, model router, tool system, dynamic orchestrator, InMemory and JSONL stores, event stream.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",