@rulvar/core 1.100.0 → 1.102.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 +68 -14
- package/dist/index.js +186 -44
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -4719,26 +4719,47 @@ interface RunAgentOptions<S extends SchemaSpec = JsonSchema> {
|
|
|
4719
4719
|
* already promised the raised cap), never re-admitted or re-announced,
|
|
4720
4720
|
* and a restored window entry keeps the summary's
|
|
4721
4721
|
* finalizationWindowEntered truthful even when a later grant moved the
|
|
4722
|
-
* counts back out of the window.
|
|
4723
|
-
*
|
|
4724
|
-
*
|
|
4722
|
+
* counts back out of the window.
|
|
4723
|
+
*
|
|
4724
|
+
* Both hooks are AWAITED before the thing they authorize becomes
|
|
4725
|
+
* observable (RV601): a grant lifts no expiry and queues no notice
|
|
4726
|
+
* until its decision is durable, and the window regime binds no call
|
|
4727
|
+
* until its entry is. A rejected append therefore leaves the grant
|
|
4728
|
+
* unissued and the entry unrecorded, and the rejection propagates
|
|
4729
|
+
* exactly like a failed boundary checkpoint rather than being
|
|
4730
|
+
* swallowed. Pressure notices stay events and are never journaled.
|
|
4731
|
+
* Absent, the loop is byte-identical to before.
|
|
4725
4732
|
*/
|
|
4726
4733
|
toolBudgetDurability?: {
|
|
4727
4734
|
restored?: {
|
|
4728
4735
|
extensionsGranted: number;
|
|
4729
4736
|
finalizationWindowEntered: boolean;
|
|
4737
|
+
/**
|
|
4738
|
+
* The effective cap the journaled grant announced (RV602). It
|
|
4739
|
+
* anchors the resumed ceiling, because the live `maxToolCalls`
|
|
4740
|
+
* and `increment` are not part of the dispatch identity and may
|
|
4741
|
+
* legitimately drift between segments: without the anchor the two
|
|
4742
|
+
* recovery paths (pure replay, which reads the journal, and live
|
|
4743
|
+
* resume, which recomputed) disagreed, and a promise already made
|
|
4744
|
+
* to the model could be silently revoked. Validated as a
|
|
4745
|
+
* persistent inlet: a non-integer, or one below the base cap, is
|
|
4746
|
+
* ignored with a warning, leaving the count derivation as the
|
|
4747
|
+
* floor. Grants taken AFTER the restore point still measure the
|
|
4748
|
+
* current increment from this anchor.
|
|
4749
|
+
*/
|
|
4750
|
+
cap?: number;
|
|
4730
4751
|
};
|
|
4731
4752
|
onExtensionGrant?: (grant: {
|
|
4732
4753
|
grant: number;
|
|
4733
4754
|
maxExtensions: number;
|
|
4734
4755
|
toolCallsUsed: number;
|
|
4735
4756
|
cap: number;
|
|
4736
|
-
}) => void
|
|
4757
|
+
}) => Promise<void>;
|
|
4737
4758
|
onWindowEntry?: (entry: {
|
|
4738
4759
|
remaining: number;
|
|
4739
4760
|
reserveCalls: number;
|
|
4740
4761
|
budget: FinalizationWindowBudget;
|
|
4741
|
-
}) => void
|
|
4762
|
+
}) => Promise<void>;
|
|
4742
4763
|
};
|
|
4743
4764
|
/** Emits agent:stream deltas when true (telemetry only). */
|
|
4744
4765
|
stream?: boolean;
|
|
@@ -7909,6 +7930,32 @@ interface OrchestrateSynthesis {
|
|
|
7909
7930
|
* accordingly).
|
|
7910
7931
|
*/
|
|
7911
7932
|
context?: "digests" | "full";
|
|
7933
|
+
/**
|
|
7934
|
+
* The conditional synthesis gate (RV510, the ninth comparison
|
|
7935
|
+
* experiment: synthesis returned the byte-identical draft after
|
|
7936
|
+
* 101.3 s and 0.5512 USD, 57.3% of post-fan-in wall time). With
|
|
7937
|
+
* `true`, before the 'single' synthesis span starts the coordination
|
|
7938
|
+
* draft is run through the FULL declared finish contract (the same
|
|
7939
|
+
* `finishValidation.validators` that would bind the synthesis
|
|
7940
|
+
* finish): a draft that passes skips the synthesis invocation
|
|
7941
|
+
* entirely under a journaled 'orchestrator_synthesis_skip' decision
|
|
7942
|
+
* with reason 'synthesis_skipped_by_valid_draft' (the existing skip
|
|
7943
|
+
* vocabulary; the info log and the acceptance envelope carry it), and
|
|
7944
|
+
* a resume rolls the journaled skip forward with zero paid calls. A
|
|
7945
|
+
* draft that fails any validator goes to synthesis exactly as before,
|
|
7946
|
+
* with the repair budget untouched (the gate is a pre-pass, never a
|
|
7947
|
+
* journaled validation verdict). Deterministic by construction: only
|
|
7948
|
+
* the declared contract judges, never a semantic delta heuristic.
|
|
7949
|
+
* Requires `finishValidation` (a ConfigError at intake otherwise:
|
|
7950
|
+
* without a contract there is nothing to judge the draft valid by),
|
|
7951
|
+
* which transitively limits it to mode 'single'. With a configured
|
|
7952
|
+
* `budget.synthesisReserveUsd` the held money is released unconsumed
|
|
7953
|
+
* on the skip and no reserve lifecycle journals: there was no
|
|
7954
|
+
* synthesis invocation to account. Default false: the gate, the
|
|
7955
|
+
* decision entry, and the envelope field are all absent, byte for
|
|
7956
|
+
* byte.
|
|
7957
|
+
*/
|
|
7958
|
+
skipWhenDraftValid?: boolean;
|
|
7912
7959
|
}
|
|
7913
7960
|
/**
|
|
7914
7961
|
* The deterministic reconciliation envelope an 'incremental' synthesis
|
|
@@ -7943,15 +7990,22 @@ interface IncrementalSynthesisResult {
|
|
|
7943
7990
|
* notes were already paid during the run; the skipped step is the free
|
|
7944
7991
|
* deterministic reconciliation). 'synthesis_skipped_by_budget_cap': the
|
|
7945
7992
|
* orchestrator budget cap froze the plan, and a capped run settles
|
|
7946
|
-
* through the reserved finalizer, never synthesis.
|
|
7947
|
-
*
|
|
7948
|
-
*
|
|
7949
|
-
*
|
|
7950
|
-
*
|
|
7951
|
-
*
|
|
7952
|
-
*
|
|
7953
|
-
|
|
7954
|
-
|
|
7993
|
+
* through the reserved finalizer, never synthesis.
|
|
7994
|
+
* 'synthesis_skipped_by_valid_draft' (RV510): the opt-in
|
|
7995
|
+
* `synthesis.skipWhenDraftValid` gate ran the coordination draft
|
|
7996
|
+
* through the full declared finish contract and every validator
|
|
7997
|
+
* passed, so the synthesis invocation had nothing to add and never
|
|
7998
|
+
* started; unlike the other two reasons the run still settles ok with
|
|
7999
|
+
* the draft as its result. The reason is frozen into the journaled
|
|
8000
|
+
* decision that caused the skip (the acceptance decision, the
|
|
8001
|
+
* budget-cap decision, or the 'orchestrator_synthesis_skip' decision),
|
|
8002
|
+
* spread into the typed FailRunError data on the failing paths and
|
|
8003
|
+
* into the acceptance envelope on the valid-draft path, and announced
|
|
8004
|
+
* by an info 'orchestrator synthesis skipped' log event; it is absent
|
|
8005
|
+
* everywhere when synthesis is not configured or actually ran, so
|
|
8006
|
+
* existing runs stay byte identical.
|
|
8007
|
+
*/
|
|
8008
|
+
type OrchestrateSynthesisSkipReason = "synthesis_skipped_by_acceptance" | "synthesis_skipped_by_budget_cap" | "synthesis_skipped_by_valid_draft";
|
|
7955
8009
|
declare const ORCHESTRATE_WORKFLOW_NAME = "rulvar-orchestrate";
|
|
7956
8010
|
/**
|
|
7957
8011
|
* Resolves per-spawn dispatch options against the engine registries
|
package/dist/index.js
CHANGED
|
@@ -10927,7 +10927,17 @@ async function runAgent(options) {
|
|
|
10927
10927
|
let extensionGrants = 0;
|
|
10928
10928
|
let extensionEvidenceAtLastGrant = 0;
|
|
10929
10929
|
const pendingExtensionNotices = [];
|
|
10930
|
-
|
|
10930
|
+
/**
|
|
10931
|
+
* The ceiling the effective cap is measured from (RV602). The base cap
|
|
10932
|
+
* normally, so a fresh loop is byte-identical arithmetic; the journaled
|
|
10933
|
+
* cap of the restored grant when a resume carried one, so drifting live
|
|
10934
|
+
* limits cannot revoke a raise the journal already recorded. Grants
|
|
10935
|
+
* taken after the restore point count from here at the CURRENT
|
|
10936
|
+
* increment, which is the live policy doing what it should.
|
|
10937
|
+
*/
|
|
10938
|
+
let capBase = limits.maxToolCalls;
|
|
10939
|
+
let grantsOverCapBase = 0;
|
|
10940
|
+
const effectiveMaxToolCalls = () => capBase === void 0 ? void 0 : extension === void 0 ? capBase : capBase + grantsOverCapBase * extension.increment;
|
|
10931
10941
|
/** The limiter that ended the loop; rides the RV304 pressure snapshot. */
|
|
10932
10942
|
let limitLimiter;
|
|
10933
10943
|
/** True once the finalization reserve summary turn actually ran. */
|
|
@@ -10979,7 +10989,7 @@ async function runAgent(options) {
|
|
|
10979
10989
|
* join the history.
|
|
10980
10990
|
*/
|
|
10981
10991
|
const tryToolBudgetGrant = () => {
|
|
10982
|
-
if (extension === void 0 || limits.maxToolCalls === void 0) return false;
|
|
10992
|
+
if (extension === void 0 || limits.maxToolCalls === void 0 || capBase === void 0) return false;
|
|
10983
10993
|
if (extensionGrants >= extension.maxExtensions) return false;
|
|
10984
10994
|
if (extension.requireNewEvidence !== false) {
|
|
10985
10995
|
if ((guard?.evidenceCount() ?? 0) <= extensionEvidenceAtLastGrant) return false;
|
|
@@ -10989,22 +10999,28 @@ async function runAgent(options) {
|
|
|
10989
10999
|
const floor = extension.minHeadroomUsd ?? 0;
|
|
10990
11000
|
if (floor > 0 ? remaining < floor : remaining <= 0) return false;
|
|
10991
11001
|
}
|
|
10992
|
-
extensionGrants
|
|
10993
|
-
|
|
10994
|
-
const
|
|
10995
|
-
|
|
10996
|
-
|
|
11002
|
+
const grant = extensionGrants + 1;
|
|
11003
|
+
const cap = capBase + (grantsOverCapBase + 1) * extension.increment;
|
|
11004
|
+
const commit = () => {
|
|
11005
|
+
extensionGrants = grant;
|
|
11006
|
+
grantsOverCapBase += 1;
|
|
11007
|
+
extensionEvidenceAtLastGrant = guard?.evidenceCount() ?? 0;
|
|
11008
|
+
pendingExtensionNotices.push(toolBudgetExtensionNoticeText(grant, extension.maxExtensions, toolCallsUsed, cap));
|
|
11009
|
+
events?.emit({
|
|
11010
|
+
type: "log",
|
|
11011
|
+
level: "info",
|
|
11012
|
+
msg: `tool budget extended (grant ${String(grant)}/${String(extension.maxExtensions)}): maxToolCalls now ${String(cap)}`
|
|
11013
|
+
});
|
|
11014
|
+
return true;
|
|
11015
|
+
};
|
|
11016
|
+
const durable = options.toolBudgetDurability?.onExtensionGrant;
|
|
11017
|
+
if (durable === void 0) return commit();
|
|
11018
|
+
return durable({
|
|
11019
|
+
grant,
|
|
10997
11020
|
maxExtensions: extension.maxExtensions,
|
|
10998
11021
|
toolCallsUsed,
|
|
10999
11022
|
cap
|
|
11000
|
-
});
|
|
11001
|
-
pendingExtensionNotices.push(toolBudgetExtensionNoticeText(extensionGrants, extension.maxExtensions, toolCallsUsed, cap));
|
|
11002
|
-
events?.emit({
|
|
11003
|
-
type: "log",
|
|
11004
|
-
level: "info",
|
|
11005
|
-
msg: `tool budget extended (grant ${String(extensionGrants)}/${String(extension.maxExtensions)}): maxToolCalls now ${String(cap)}`
|
|
11006
|
-
});
|
|
11007
|
-
return true;
|
|
11023
|
+
}).then(commit);
|
|
11008
11024
|
};
|
|
11009
11025
|
const flushExtensionNotices = () => {
|
|
11010
11026
|
for (const text of pendingExtensionNotices.splice(0)) messages.push({
|
|
@@ -11058,19 +11074,26 @@ async function runAgent(options) {
|
|
|
11058
11074
|
if (finalizationWindow === void 0 || windowNoticeFired) return;
|
|
11059
11075
|
const state = windowActive();
|
|
11060
11076
|
if (state === void 0) return;
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
|
|
11077
|
+
const commit = () => {
|
|
11078
|
+
windowEntered = true;
|
|
11079
|
+
windowNoticeFired = true;
|
|
11080
|
+
pendingWindowNotices.push(finalizationWindowNoticeText(state.remaining, finalizationWindow.reserveCalls, state.budget));
|
|
11081
|
+
events?.emit({
|
|
11082
|
+
type: "log",
|
|
11083
|
+
level: "info",
|
|
11084
|
+
msg: `finalization window entered: ${String(state.remaining)} of the reserved final ${String(finalizationWindow.reserveCalls)} ${state.budget} remain`
|
|
11085
|
+
});
|
|
11086
|
+
};
|
|
11087
|
+
const durable = options.toolBudgetDurability?.onWindowEntry;
|
|
11088
|
+
if (durable === void 0) {
|
|
11089
|
+
commit();
|
|
11090
|
+
return;
|
|
11091
|
+
}
|
|
11092
|
+
return durable({
|
|
11064
11093
|
remaining: state.remaining,
|
|
11065
11094
|
reserveCalls: finalizationWindow.reserveCalls,
|
|
11066
11095
|
budget: state.budget
|
|
11067
|
-
});
|
|
11068
|
-
pendingWindowNotices.push(finalizationWindowNoticeText(state.remaining, finalizationWindow.reserveCalls, state.budget));
|
|
11069
|
-
events?.emit({
|
|
11070
|
-
type: "log",
|
|
11071
|
-
level: "info",
|
|
11072
|
-
msg: `finalization window entered: ${String(state.remaining)} of the reserved final ${String(finalizationWindow.reserveCalls)} ${state.budget} remain`
|
|
11073
|
-
});
|
|
11096
|
+
}).then(commit);
|
|
11074
11097
|
};
|
|
11075
11098
|
const flushWindowNotices = () => {
|
|
11076
11099
|
for (const text of pendingWindowNotices.splice(0)) messages.push({
|
|
@@ -11127,6 +11150,7 @@ async function runAgent(options) {
|
|
|
11127
11150
|
extensionGrants = Math.min(extension.maxExtensions, Math.ceil((toolCallsUsed - limits.maxToolCalls) / extension.increment));
|
|
11128
11151
|
extensionEvidenceAtLastGrant = guard?.evidenceCount() ?? 0;
|
|
11129
11152
|
}
|
|
11153
|
+
const derivedCap = limits.maxToolCalls === void 0 || extension === void 0 ? void 0 : limits.maxToolCalls + extensionGrants * extension.increment;
|
|
11130
11154
|
const durableRestored = options.toolBudgetDurability?.restored;
|
|
11131
11155
|
if (extension !== void 0 && durableRestored !== void 0) {
|
|
11132
11156
|
const journaled = Math.min(extension.maxExtensions, durableRestored.extensionsGranted);
|
|
@@ -11135,6 +11159,16 @@ async function runAgent(options) {
|
|
|
11135
11159
|
extensionEvidenceAtLastGrant = guard?.evidenceCount() ?? 0;
|
|
11136
11160
|
}
|
|
11137
11161
|
}
|
|
11162
|
+
grantsOverCapBase = extensionGrants;
|
|
11163
|
+
const journaledCap = durableRestored?.cap;
|
|
11164
|
+
if (journaledCap !== void 0 && limits.maxToolCalls !== void 0 && derivedCap !== void 0) if (Number.isSafeInteger(journaledCap) && journaledCap >= limits.maxToolCalls) {
|
|
11165
|
+
capBase = Math.max(journaledCap, derivedCap);
|
|
11166
|
+
grantsOverCapBase = 0;
|
|
11167
|
+
} else events?.emit({
|
|
11168
|
+
type: "log",
|
|
11169
|
+
level: "warn",
|
|
11170
|
+
msg: `restored tool budget cap ${String(journaledCap)} is not an integer at or above the base cap ${String(limits.maxToolCalls)}; ignoring it and deriving from the counts`
|
|
11171
|
+
});
|
|
11138
11172
|
if (windowActive() !== void 0 || finalizationWindow !== void 0 && durableRestored?.finalizationWindowEntered === true) {
|
|
11139
11173
|
windowEntered = true;
|
|
11140
11174
|
windowNoticeFired = true;
|
|
@@ -11231,7 +11265,10 @@ async function runAgent(options) {
|
|
|
11231
11265
|
return cap !== void 0 && toolCallsUsed >= cap ? "maxToolCalls" : guard !== void 0 && guard.unitsExhausted() ? "toolUnits" : void 0;
|
|
11232
11266
|
};
|
|
11233
11267
|
let expiredLimiter = expiryOf();
|
|
11234
|
-
if (expiredLimiter === "maxToolCalls" && call.name !== options.terminalTool?.name
|
|
11268
|
+
if (expiredLimiter === "maxToolCalls" && call.name !== options.terminalTool?.name) {
|
|
11269
|
+
const attempt = tryToolBudgetGrant();
|
|
11270
|
+
if (typeof attempt === "boolean" ? attempt : await attempt) expiredLimiter = expiryOf();
|
|
11271
|
+
}
|
|
11235
11272
|
if (expiredLimiter !== void 0) {
|
|
11236
11273
|
const tail = calls.slice(index);
|
|
11237
11274
|
const terminalName = options.terminalTool?.name;
|
|
@@ -11419,10 +11456,14 @@ async function runAgent(options) {
|
|
|
11419
11456
|
};
|
|
11420
11457
|
}
|
|
11421
11458
|
if (finalizationWindow !== void 0) {
|
|
11422
|
-
maybeMarkWindowEntry();
|
|
11459
|
+
const entry = maybeMarkWindowEntry();
|
|
11460
|
+
if (entry !== void 0) await entry;
|
|
11423
11461
|
let windowState = windowActive();
|
|
11424
11462
|
if (windowState !== void 0 && !windowAllows(gatedCall.name)) {
|
|
11425
|
-
if (windowState.budget === "tool calls" && extension !== void 0 && windowState.remaining + extension.increment > finalizationWindow.reserveCalls
|
|
11463
|
+
if (windowState.budget === "tool calls" && extension !== void 0 && windowState.remaining + extension.increment > finalizationWindow.reserveCalls) {
|
|
11464
|
+
const attempt = tryToolBudgetGrant();
|
|
11465
|
+
if (typeof attempt === "boolean" ? attempt : await attempt) windowState = windowActive();
|
|
11466
|
+
}
|
|
11426
11467
|
if (windowState !== void 0 && !windowAllows(gatedCall.name)) {
|
|
11427
11468
|
events?.emit({
|
|
11428
11469
|
type: "tool:end",
|
|
@@ -11492,7 +11533,8 @@ async function runAgent(options) {
|
|
|
11492
11533
|
});
|
|
11493
11534
|
}
|
|
11494
11535
|
}
|
|
11495
|
-
maybeMarkWindowEntry();
|
|
11536
|
+
const lastEntry = maybeMarkWindowEntry();
|
|
11537
|
+
if (lastEntry !== void 0) await lastEntry;
|
|
11496
11538
|
return {
|
|
11497
11539
|
parts,
|
|
11498
11540
|
limitHit: false
|
|
@@ -14847,10 +14889,11 @@ function createCtx(internals, rootWorkflow) {
|
|
|
14847
14889
|
runAgentOptions.toolBudgetDurability = {
|
|
14848
14890
|
...durableRestored === void 0 ? {} : { restored: {
|
|
14849
14891
|
extensionsGranted: durableRestored.extensionsGranted,
|
|
14850
|
-
finalizationWindowEntered: durableRestored.finalizationWindowEntered
|
|
14892
|
+
finalizationWindowEntered: durableRestored.finalizationWindowEntered,
|
|
14893
|
+
...durableRestored.cap === void 0 ? {} : { cap: durableRestored.cap }
|
|
14851
14894
|
} },
|
|
14852
|
-
onExtensionGrant: (grant) => {
|
|
14853
|
-
internals.replayer.appendSinglePhase({
|
|
14895
|
+
onExtensionGrant: async (grant) => {
|
|
14896
|
+
await internals.replayer.appendSinglePhase({
|
|
14854
14897
|
scope: state.scope,
|
|
14855
14898
|
key: "",
|
|
14856
14899
|
kind: "decision",
|
|
@@ -14863,8 +14906,8 @@ function createCtx(internals, rootWorkflow) {
|
|
|
14863
14906
|
}
|
|
14864
14907
|
});
|
|
14865
14908
|
},
|
|
14866
|
-
onWindowEntry: (entry) => {
|
|
14867
|
-
internals.replayer.appendSinglePhase({
|
|
14909
|
+
onWindowEntry: async (entry) => {
|
|
14910
|
+
await internals.replayer.appendSinglePhase({
|
|
14868
14911
|
scope: state.scope,
|
|
14869
14912
|
key: "",
|
|
14870
14913
|
kind: "decision",
|
|
@@ -16973,6 +17016,11 @@ function validateOrchestrateOptions(opts) {
|
|
|
16973
17016
|
if (synthesis.dedupeClaims !== void 0 && typeof synthesis.dedupeClaims !== "boolean") throw new ConfigError("orchestrate synthesis.dedupeClaims must be a boolean; got " + typeof synthesis.dedupeClaims);
|
|
16974
17017
|
const symmetry = synthesis;
|
|
16975
17018
|
if (symmetry.exposeChildResultTools !== void 0 && typeof symmetry.exposeChildResultTools !== "boolean") throw new ConfigError("orchestrate synthesis.exposeChildResultTools must be a boolean; got " + typeof symmetry.exposeChildResultTools);
|
|
17019
|
+
const conditional = synthesis;
|
|
17020
|
+
if (conditional.skipWhenDraftValid !== void 0) {
|
|
17021
|
+
if (typeof conditional.skipWhenDraftValid !== "boolean") throw new ConfigError("orchestrate synthesis.skipWhenDraftValid must be a boolean; got " + typeof conditional.skipWhenDraftValid);
|
|
17022
|
+
if (conditional.skipWhenDraftValid && opts.finishValidation === void 0) throw new ConfigError("orchestrate synthesis.skipWhenDraftValid requires finishValidation: without a declared finish contract there is nothing to judge the draft valid by");
|
|
17023
|
+
}
|
|
16976
17024
|
if (symmetry.context !== void 0 && symmetry.context !== "digests" && symmetry.context !== "full") throw new ConfigError("orchestrate synthesis.context must be 'digests' or 'full'; got " + JSON.stringify(symmetry.context));
|
|
16977
17025
|
if (synthesis.noteLimits !== void 0) validateUsageLimits(synthesis.noteLimits, "orchestrate synthesis.noteLimits");
|
|
16978
17026
|
if (synthesis.effort !== void 0 && ![
|
|
@@ -17980,6 +18028,24 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17980
18028
|
if (decision.contractHash !== void 0) return decision.contractHash === hash;
|
|
17981
18029
|
return internals.replayer.snapshot().filter((entry) => entry.kind === "decision" && entry.scope === callingState.scope && entry.value?.decisionType === "orchestrator_finish_validation_bundle").length <= 1;
|
|
17982
18030
|
};
|
|
18031
|
+
/**
|
|
18032
|
+
* The children snapshot (RV-202): spawn order, pure reads of the
|
|
18033
|
+
* records the orchestrator already tracks, so validators can hold
|
|
18034
|
+
* a finish result (or the RV510 draft pre-pass) against the
|
|
18035
|
+
* evidence the children produced. Only the JOURNALED verdict of
|
|
18036
|
+
* validateFinish survives; on replay the snapshot is never rebuilt
|
|
18037
|
+
* because the entry is read by call id there.
|
|
18038
|
+
*/
|
|
18039
|
+
const validationChildren = () => {
|
|
18040
|
+
const salvageOutputOn = opts?.acceptance?.acceptValidatedTerminalOutputOnLimit === true;
|
|
18041
|
+
return [...records.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => ({
|
|
18042
|
+
handle: record.handle,
|
|
18043
|
+
nodeId: record.nodeId,
|
|
18044
|
+
status: record.settled?.status ?? "running",
|
|
18045
|
+
text: record.settled === void 0 ? "" : serializeChildOutput(record.settled),
|
|
18046
|
+
...salvageOutputOn && record.settled?.status === "limit" && record.settled.output !== null && record.settled.output !== void 0 ? { salvageableOutput: true } : {}
|
|
18047
|
+
}));
|
|
18048
|
+
};
|
|
17983
18049
|
const validateFinish = async (call) => {
|
|
17984
18050
|
if (validationSpec === void 0) return { ok: true };
|
|
17985
18051
|
const maxRepairs = validationSpec.maxRepairs ?? 1;
|
|
@@ -17987,18 +18053,10 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
17987
18053
|
let decision = known.find((candidate) => candidate.callId === call.id);
|
|
17988
18054
|
if (decision === void 0) {
|
|
17989
18055
|
const result = call.result ?? null;
|
|
17990
|
-
const salvageOutputOn = opts?.acceptance?.acceptValidatedTerminalOutputOnLimit === true;
|
|
17991
|
-
const children = [...records.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal).map((record) => ({
|
|
17992
|
-
handle: record.handle,
|
|
17993
|
-
nodeId: record.nodeId,
|
|
17994
|
-
status: record.settled?.status ?? "running",
|
|
17995
|
-
text: record.settled === void 0 ? "" : serializeChildOutput(record.settled),
|
|
17996
|
-
...salvageOutputOn && record.settled?.status === "limit" && record.settled.output !== null && record.settled.output !== void 0 ? { salvageableOutput: true } : {}
|
|
17997
|
-
}));
|
|
17998
18056
|
const input = {
|
|
17999
18057
|
result,
|
|
18000
18058
|
text: typeof result === "string" ? result : JSON.stringify(result),
|
|
18001
|
-
children
|
|
18059
|
+
children: validationChildren()
|
|
18002
18060
|
};
|
|
18003
18061
|
const failed = [];
|
|
18004
18062
|
for (const validator of validationSpec.validators) {
|
|
@@ -18392,11 +18450,94 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
18392
18450
|
* facts; absent everywhere when no reserve is configured.
|
|
18393
18451
|
*/
|
|
18394
18452
|
let synthesisReserveLifecycle;
|
|
18453
|
+
/**
|
|
18454
|
+
* Set exactly when the RV510 gate skipped the synthesis span (live
|
|
18455
|
+
* pass and resume roll-forward alike); the acceptance envelope
|
|
18456
|
+
* reports the machine reason through it.
|
|
18457
|
+
*/
|
|
18458
|
+
let synthesisSkippedByValidDraft = false;
|
|
18395
18459
|
const runSynthesis = async (draft) => {
|
|
18396
18460
|
const spec = opts?.synthesis;
|
|
18397
18461
|
if (spec === void 0) return draft;
|
|
18398
18462
|
await recoveryDone;
|
|
18399
18463
|
if (spec.mode === "incremental") return await reconcileIncremental(draft, spec);
|
|
18464
|
+
if (spec.skipWhenDraftValid === true && validationSpec !== void 0) {
|
|
18465
|
+
const skipKey = "synthesis-draft-valid-skip";
|
|
18466
|
+
const announceSkip = (entryRef) => {
|
|
18467
|
+
internals.events.emit({
|
|
18468
|
+
type: "log",
|
|
18469
|
+
level: "info",
|
|
18470
|
+
msg: "orchestrator synthesis skipped",
|
|
18471
|
+
data: {
|
|
18472
|
+
reason: "synthesis_skipped_by_valid_draft",
|
|
18473
|
+
skipDecisionRef: entryRef
|
|
18474
|
+
}
|
|
18475
|
+
}, callingState.spanId);
|
|
18476
|
+
};
|
|
18477
|
+
const draftValue = draft ?? null;
|
|
18478
|
+
const draftHash = createHash("sha256").update(jcsSerialize(draftValue), "utf8").digest("hex");
|
|
18479
|
+
const validatorNames = validationSpec.validators.map((validator) => validator.name);
|
|
18480
|
+
/**
|
|
18481
|
+
* A journaled skip is the authority only for the generation and
|
|
18482
|
+
* the draft it judged (RV603). The documented remedy for a
|
|
18483
|
+
* broken contract is to fix it and resume, and a verdict that
|
|
18484
|
+
* outlives its contract defeats exactly that: the run would
|
|
18485
|
+
* settle ok carrying output the CURRENT contract rejects.
|
|
18486
|
+
* Bound by three facts, in descending strength: the contract
|
|
18487
|
+
* identity (the same `contractGenerationCurrent` test the
|
|
18488
|
+
* finish-validation decisions already use), the draft the
|
|
18489
|
+
* verdict actually judged, and the validator names that
|
|
18490
|
+
* rendered it. Entries written before this field existed carry
|
|
18491
|
+
* no draftHash and stay reusable, so journals in flight roll
|
|
18492
|
+
* forward byte for byte.
|
|
18493
|
+
*/
|
|
18494
|
+
const applies = (value) => contractGenerationCurrent(value) && (value.draftHash === void 0 || value.draftHash === draftHash) && (!Array.isArray(value.validators) || JSON.stringify(value.validators) === JSON.stringify(validatorNames));
|
|
18495
|
+
const prior = internals.replayer.snapshot().filter((entry) => entry.kind === "decision" && entry.scope === callingState.scope && entry.key === skipKey).at(-1);
|
|
18496
|
+
if (prior !== void 0 && applies(prior.value)) {
|
|
18497
|
+
synthesisSkippedByValidDraft = true;
|
|
18498
|
+
announceSkip(prior.seq);
|
|
18499
|
+
return draft;
|
|
18500
|
+
}
|
|
18501
|
+
const input = {
|
|
18502
|
+
result: draftValue,
|
|
18503
|
+
text: typeof draftValue === "string" ? draftValue : JSON.stringify(draftValue),
|
|
18504
|
+
children: validationChildren()
|
|
18505
|
+
};
|
|
18506
|
+
let allPassed = true;
|
|
18507
|
+
for (const validator of validationSpec.validators) {
|
|
18508
|
+
let verdict;
|
|
18509
|
+
try {
|
|
18510
|
+
verdict = validator.validate(input);
|
|
18511
|
+
} catch (thrown) {
|
|
18512
|
+
throw new ConfigError(`finish validator '${validator.name}' threw instead of returning a verdict during the skipWhenDraftValid pre-pass: ` + (thrown instanceof Error ? thrown.message : String(thrown)));
|
|
18513
|
+
}
|
|
18514
|
+
if (!verdict.ok) {
|
|
18515
|
+
allPassed = false;
|
|
18516
|
+
break;
|
|
18517
|
+
}
|
|
18518
|
+
}
|
|
18519
|
+
if (allPassed) {
|
|
18520
|
+
const skipEntry = await internals.replayer.appendSinglePhase({
|
|
18521
|
+
scope: callingState.scope,
|
|
18522
|
+
key: skipKey,
|
|
18523
|
+
kind: "decision",
|
|
18524
|
+
status: "ok",
|
|
18525
|
+
spanId: internals.spans.mint(callingState.spanId),
|
|
18526
|
+
site: "orchestrator-synthesis-skip",
|
|
18527
|
+
value: {
|
|
18528
|
+
decisionType: "orchestrator_synthesis_skip",
|
|
18529
|
+
reason: "synthesis_skipped_by_valid_draft",
|
|
18530
|
+
validators: validatorNames,
|
|
18531
|
+
...validationSpec.contract === void 0 ? {} : { contractHash: validationSpec.contract.hash },
|
|
18532
|
+
draftHash
|
|
18533
|
+
}
|
|
18534
|
+
});
|
|
18535
|
+
if (orchestratorAccount !== void 0 && (opts?.budget?.synthesisReserveUsd ?? 0) > 0) internals.budget.releaseSynthesisReserve(orchestratorAccount);
|
|
18536
|
+
synthesisSkippedByValidDraft = true;
|
|
18537
|
+
announceSkip(skipEntry.seq);
|
|
18538
|
+
return draft;
|
|
18539
|
+
}
|
|
18540
|
+
}
|
|
18400
18541
|
const exposeTools = spec.exposeChildResultTools === true;
|
|
18401
18542
|
const fullContext = spec.context === "full";
|
|
18402
18543
|
const synthesisToolNames = /* @__PURE__ */ new Set([FINISH_TOOL_NAME, ...exposeTools ? [GET_CHILD_RESULT_TOOL_NAME, READ_CHILD_ARTIFACT_TOOL_NAME] : []]);
|
|
@@ -18746,7 +18887,8 @@ function makeOrchestratorWorkflow(goal, opts) {
|
|
|
18746
18887
|
...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren },
|
|
18747
18888
|
...decision.salvagedTerminalOutputChildren === void 0 ? {} : { salvagedTerminalOutputChildren: decision.salvagedTerminalOutputChildren },
|
|
18748
18889
|
...envelopeSchemaRecovered === 0 ? {} : { schemaRecoveredFinishExchanges: envelopeSchemaRecovered },
|
|
18749
|
-
...synthesisReserveLifecycle === void 0 ? {} : { synthesisReserve: synthesisReserveLifecycle }
|
|
18890
|
+
...synthesisReserveLifecycle === void 0 ? {} : { synthesisReserve: synthesisReserveLifecycle },
|
|
18891
|
+
...synthesisSkippedByValidDraft ? { synthesisSkipped: "synthesis_skipped_by_valid_draft" } : {}
|
|
18750
18892
|
};
|
|
18751
18893
|
});
|
|
18752
18894
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.102.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",
|