@rulvar/core 1.220.0 → 1.221.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.
Files changed (2) hide show
  1. package/dist/index.js +109 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12604,6 +12604,102 @@ async function runAgent(options) {
12604
12604
  resolved: options.resolved
12605
12605
  }, ...options.fallbacks ?? []];
12606
12606
  const loopCursor = { index: 0 };
12607
+ /**
12608
+ * The drained-finalization grant (RV2204, the third parity rerun).
12609
+ * The exposure drain is terminal mid-work: spend only grows, so a
12610
+ * seat refused with no live holder left can never dispatch an
12611
+ * ordinary turn again. The third parity rerun killed three workers
12612
+ * ~30 turns into research with evidence pools of 17 and 22 under a
12613
+ * floor of 24 and a CONFIGURED finalization window: the drain came
12614
+ * before the window, and the window's play needs the very wire the
12615
+ * drain refuses. With `limits.finalizationReserve.maxOutputTokens`
12616
+ * declared, a drained seat that already did work now spends ONE
12617
+ * clamped finalization turn before its typed terminal: the output
12618
+ * clamp shrinks the turn's exposure estimate (the full estimate was
12619
+ * refused; the clamped one prices the summary allowance instead of
12620
+ * the whole per-turn cap), the finalization-window allowlist rides
12621
+ * as the turn's only tools so outstanding record_evidence calls can
12622
+ * land in parallel, and the executed calls persist through the
12623
+ * ordinary tool machinery. Best effort, exactly like the tool-budget
12624
+ * reserve turn: a refusal of even the clamped estimate warns and
12625
+ * keeps the typed drained terminal; a seat with NO completed turns
12626
+ * keeps dying free (the RV2002 zero-cost doctrine: nothing to
12627
+ * summarize, nothing paid).
12628
+ */
12629
+ let drainFinalizationRan = false;
12630
+ const runDrainFinalization = async (refusal) => {
12631
+ const allow = limits.finalizationWindow?.allow;
12632
+ const allowedTools = allow === void 0 ? void 0 : options.tools?.contracts.filter((contract) => allow.includes(contract.name));
12633
+ const toolsRide = allowedTools !== void 0 && allowedTools.length > 0;
12634
+ const drainMessages = [...messages, {
12635
+ role: "user",
12636
+ parts: [{
12637
+ type: "text",
12638
+ text: `The run's in-flight exposure pool is drained; no further ordinary turns can dispatch (${refusal}). This is your single finalization turn` + (toolsRide ? ": record any outstanding evidence with the allowed tools IN THIS turn (parallel calls), then close" : `: close`) + ` with your best final summary of the work already done.`
12639
+ }]
12640
+ }];
12641
+ let grant;
12642
+ try {
12643
+ grant = await dispatchPhase({
12644
+ role: primaryRole,
12645
+ chain: loopChain,
12646
+ cursor: loopCursor,
12647
+ requestFor: (target) => {
12648
+ let req = buildRequest(target.resolved, projectHistory(drainMessages, providerOf(target.adapter)), limits, toolsRide ? allowedTools : void 0);
12649
+ const reserveMax = limits.finalizationReserve?.maxOutputTokens;
12650
+ if (reserveMax !== void 0) req = {
12651
+ ...req,
12652
+ maxOutputTokens: Math.min(req.maxOutputTokens ?? reserveMax, reserveMax)
12653
+ };
12654
+ req = applyCachePolicy(req, target, options.cache);
12655
+ return applyOutputBudget(req, target, options.budget);
12656
+ },
12657
+ streamOptionsFor: (target) => {
12658
+ const drainStreamOptions = {
12659
+ idleTimeoutMs: limits.streamIdleTimeoutMs,
12660
+ signals: options.signal === void 0 ? [] : [options.signal],
12661
+ onUsage: (delta) => options.budget?.onUsage(delta, target.resolved.ref)
12662
+ };
12663
+ if (options.budget?.signal !== void 0) drainStreamOptions.budgetSignal = options.budget.signal;
12664
+ if (options.stream === true) drainStreamOptions.onDelta = (delta) => events?.emit({
12665
+ type: "agent:stream",
12666
+ delta
12667
+ });
12668
+ return drainStreamOptions;
12669
+ }
12670
+ });
12671
+ } catch (grantThrown) {
12672
+ if (!(grantThrown instanceof BudgetExhaustedError)) throw grantThrown;
12673
+ events?.emit({
12674
+ type: "log",
12675
+ level: "warn",
12676
+ msg: `the drained-finalization turn was skipped: ${grantThrown.message}`
12677
+ });
12678
+ return;
12679
+ }
12680
+ const { outcome: grantOutcome, target: grantTarget } = grant;
12681
+ servedBy = grantTarget.resolved.ref;
12682
+ usageApprox = usageApprox || grantOutcome.usageApprox;
12683
+ messages.push(assistantMsg(grantOutcome.turn, liftRetainedParts(grantOutcome.providerMetadata, grantTarget.adapter)));
12684
+ if (toolsRide && grantOutcome.turn.toolCalls.length > 0) try {
12685
+ await runToolCalls(grantOutcome.turn.toolCalls, []);
12686
+ } catch (toolThrown) {
12687
+ events?.emit({
12688
+ type: "log",
12689
+ level: "warn",
12690
+ msg: `a drained-finalization tool call failed: ${toolThrown instanceof Error ? toolThrown.message : String(toolThrown)}`
12691
+ });
12692
+ }
12693
+ events?.emit({
12694
+ type: "log",
12695
+ level: "info",
12696
+ msg: "the drained seat spent its finalization turn",
12697
+ data: {
12698
+ toolCalls: grantOutcome.turn.toolCalls.length,
12699
+ outputTokens: grantOutcome.usage.outputTokens
12700
+ }
12701
+ });
12702
+ };
12607
12703
  loop: while (status === "ok" && !finishedViaTool) {
12608
12704
  if (limits.timeoutMs !== void 0 && now() - startedAt >= limits.timeoutMs) {
12609
12705
  status = "limit";
@@ -12657,8 +12753,12 @@ async function runAgent(options) {
12657
12753
  });
12658
12754
  } catch (thrown) {
12659
12755
  if (!(thrown instanceof BudgetExhaustedError)) throw thrown;
12660
- status = "error";
12661
12756
  const typedReason = thrown.data?.reason;
12757
+ if (typedReason === "exposure-drained" && limits.finalizationReserve !== void 0 && turns > 1 && !drainFinalizationRan) {
12758
+ drainFinalizationRan = true;
12759
+ await runDrainFinalization(thrown.message);
12760
+ }
12761
+ status = "error";
12662
12762
  agentError = {
12663
12763
  kind: "budget",
12664
12764
  retryable: false,
@@ -24792,10 +24892,16 @@ function preflightEstimate(input) {
24792
24892
  spawn: label
24793
24893
  });
24794
24894
  }
24795
- if (limits.finalizationReserve !== void 0 && limits.maxToolCalls === void 0 && limits.toolUnits === void 0) say({
24895
+ if (limits.finalizationReserve !== void 0 && limits.maxToolCalls === void 0 && limits.toolUnits === void 0 && input.run?.maxInFlightExposureUsd === void 0) say({
24796
24896
  severity: "warning",
24797
24897
  code: "inert-finalization-reserve",
24798
- message: `spawn '${label}' sets finalizationReserve without maxToolCalls or toolUnits: no tool budget limiter exists for it to fire on`,
24898
+ message: `spawn '${label}' sets finalizationReserve without maxToolCalls or toolUnits: no tool budget limiter exists for it to fire on, and no in-flight exposure cap is declared for the drained-finalization grant (RV2204) to spend it at`,
24899
+ spawn: label
24900
+ });
24901
+ if (limits.finalizationWindow !== void 0 && limits.finalizationReserve?.maxOutputTokens === void 0 && input.run?.maxInFlightExposureUsd !== void 0) say({
24902
+ severity: "info",
24903
+ code: "drained-finalization-unfunded",
24904
+ message: `spawn '${label}' declares finalizationWindow under an in-flight exposure cap but no finalizationReserve.maxOutputTokens: a mid-work exposure drain refuses the wire the window needs, and without the reserve's clamped turn the seat dies with its window unplayed (RV2204); declare the reserve to fund one drained-finalization turn`,
24799
24905
  spawn: label
24800
24906
  });
24801
24907
  if (limits.toolBudgetNotices === true && limits.maxToolCalls === void 0) say({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/core",
3
- "version": "1.220.0",
3
+ "version": "1.221.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",