@rulvar/core 1.54.0 → 1.56.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.js CHANGED
@@ -3750,6 +3750,197 @@ function repositoryResearchToolset(options) {
3750
3750
  };
3751
3751
  }
3752
3752
  //#endregion
3753
+ //#region src/tools/progress.ts
3754
+ /** The stock progress tool name the engine scans terminals for. */
3755
+ const PROGRESS_REPORT_TOOL_NAME = "report_progress";
3756
+ const PROGRESS_SCHEMA = {
3757
+ type: "object",
3758
+ additionalProperties: false,
3759
+ required: ["facts"],
3760
+ properties: {
3761
+ facts: {
3762
+ type: "array",
3763
+ items: { type: "string" },
3764
+ description: "New facts established since the last report; may be empty early on."
3765
+ },
3766
+ evidence: {
3767
+ type: "array",
3768
+ items: { type: "string" },
3769
+ description: "Evidence references backing the facts (file:line or recorded evidence ids)."
3770
+ },
3771
+ questions: {
3772
+ type: "array",
3773
+ items: { type: "string" },
3774
+ description: "Remaining unresolved questions."
3775
+ },
3776
+ note: {
3777
+ type: "string",
3778
+ description: "Optional short status note."
3779
+ }
3780
+ }
3781
+ };
3782
+ /**
3783
+ * The stock progress-report tool. Stateless and deterministic: the
3784
+ * result echoes the counts, so a verbatim repeated report is a
3785
+ * duplicate result digest to the exploration guards. The value is the
3786
+ * side contract: the engine captures the LAST successful call of this
3787
+ * tool as the structured terminal partial of a 'limit' invocation, so
3788
+ * an agent that reports after every batch never loses its collected
3789
+ * work to a budget expiry.
3790
+ */
3791
+ function progressReportTool() {
3792
+ return tool({
3793
+ name: PROGRESS_REPORT_TOOL_NAME,
3794
+ description: "Report research progress after every batch of tool calls: the new facts you established, the evidence references backing them, and the questions still open. If the invocation ends at a limit, your LAST report is returned to the caller as the structured partial result, so report before the budget runs out.",
3795
+ parameters: PROGRESS_SCHEMA,
3796
+ risk: "read",
3797
+ execute: (input) => {
3798
+ const report = input;
3799
+ return Promise.resolve({
3800
+ recorded: true,
3801
+ facts: report.facts?.length ?? 0,
3802
+ evidence: report.evidence?.length ?? 0,
3803
+ questions: report.questions?.length ?? 0
3804
+ });
3805
+ }
3806
+ });
3807
+ }
3808
+ function stringArray(value) {
3809
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
3810
+ }
3811
+ /**
3812
+ * The deterministic terminal scan: pairs `report_progress` tool calls
3813
+ * with their SUCCESSFUL results by id (a denied or failed call never
3814
+ * counts, mirroring the exploration guard's restore) and normalizes the
3815
+ * last one into a {@link ProgressReport}. Pure over the message window
3816
+ * it is given: the live loop hands its own history, the replay path
3817
+ * hands the terminal checkpoint's messages, and a compaction naturally
3818
+ * narrows the window to what the model itself still sees.
3819
+ */
3820
+ function latestProgressReport(messages) {
3821
+ const callsById = /* @__PURE__ */ new Map();
3822
+ let latest;
3823
+ for (const msg of messages) for (const part of msg.parts) if (part.type === "tool-call" && part.name === "report_progress") callsById.set(part.id, part.args);
3824
+ else if (part.type === "tool-result" && part.name === "report_progress" && part.isError !== true && callsById.has(part.id)) {
3825
+ const args = callsById.get(part.id);
3826
+ if (typeof args === "object" && args !== null && !Array.isArray(args)) {
3827
+ const record = args;
3828
+ const report = {
3829
+ facts: stringArray(record.facts),
3830
+ evidence: stringArray(record.evidence),
3831
+ questions: stringArray(record.questions)
3832
+ };
3833
+ if (typeof record.note === "string") report.note = record.note;
3834
+ latest = report;
3835
+ }
3836
+ }
3837
+ return latest;
3838
+ }
3839
+ //#endregion
3840
+ //#region src/engine/profile-templates.ts
3841
+ /**
3842
+ * The research template's stop conditions: a weighted unit budget over
3843
+ * the research tools (bookkeeping tools are free), per-tool caps, both
3844
+ * repetition guards, and soft budget notices. Exported so hosts and
3845
+ * tests can read the exact defaults they are overriding.
3846
+ */
3847
+ const RESEARCH_PROFILE_LIMITS = {
3848
+ maxTurns: 24,
3849
+ maxToolCalls: 48,
3850
+ toolBudgetNotices: true,
3851
+ maxRepeatedToolSignature: 2,
3852
+ maxNoNewEvidenceCalls: 6,
3853
+ maxCallsPerTool: {
3854
+ list_files: 12,
3855
+ search_files: 20,
3856
+ read_file: 30
3857
+ },
3858
+ toolUnits: {
3859
+ max: 64,
3860
+ costs: {
3861
+ list_files: 1,
3862
+ search_files: 2,
3863
+ read_file: 2,
3864
+ record_evidence: 0,
3865
+ list_evidence: 0,
3866
+ report_progress: 0
3867
+ }
3868
+ }
3869
+ };
3870
+ /** The implementation template's stop conditions. */
3871
+ const IMPLEMENTATION_PROFILE_LIMITS = {
3872
+ maxTurns: 32,
3873
+ maxToolCalls: 64,
3874
+ toolBudgetNotices: true,
3875
+ maxRepeatedToolSignature: 3,
3876
+ noProgressTurns: 3
3877
+ };
3878
+ /** The review template's stop conditions. */
3879
+ const REVIEW_PROFILE_LIMITS = {
3880
+ maxTurns: 16,
3881
+ maxToolCalls: 32,
3882
+ toolBudgetNotices: true,
3883
+ maxRepeatedToolSignature: 2,
3884
+ maxNoNewEvidenceCalls: 8
3885
+ };
3886
+ function mergeLimits(template, overrides) {
3887
+ return {
3888
+ ...template,
3889
+ ...overrides ?? {}
3890
+ };
3891
+ }
3892
+ /**
3893
+ * The batteries-included research child: the confined
3894
+ * {@link repositoryResearchToolset} over `root`, the stock
3895
+ * report_progress tool, and {@link RESEARCH_PROFILE_LIMITS} as the stop
3896
+ * conditions. A child spawned from this profile that runs out of budget
3897
+ * settles 'limit' WITH its last progress report as the structured
3898
+ * partial, and the recorded evidence stays readable host-side through
3899
+ * `evidence()`.
3900
+ */
3901
+ function researchAgentProfile(options) {
3902
+ const { description, limits, extraTools, ...toolsetOptions } = options;
3903
+ const kit = repositoryResearchToolset(toolsetOptions);
3904
+ return {
3905
+ profile: {
3906
+ description: description ?? "Repository research over a confined root: paginated list_files/search_files/read_file with stable cursors, record_evidence verifying every citation, and report_progress after every batch. Stop conditions built in: weighted tool units, per-tool caps, repetition and no-new-evidence guards, budget notices. On limit the last progress report is the structured partial.",
3907
+ tools: [
3908
+ ...kit.tools,
3909
+ progressReportTool(),
3910
+ ...extraTools ?? []
3911
+ ],
3912
+ limits: mergeLimits(RESEARCH_PROFILE_LIMITS, limits)
3913
+ },
3914
+ evidence: () => kit.evidence()
3915
+ };
3916
+ }
3917
+ /**
3918
+ * The implementation child template: the caller's task tools plus the
3919
+ * progress contract, with {@link IMPLEMENTATION_PROFILE_LIMITS} as the
3920
+ * stop conditions (a no-progress detector instead of the research
3921
+ * no-new-evidence guard: implementation legitimately re-reads state).
3922
+ */
3923
+ function implementationAgentProfile(options = {}) {
3924
+ return {
3925
+ description: options.description ?? "Implementation work with built-in stop conditions: tool budget with notices, repeated-call guard, no-progress detector. Report progress with report_progress after every batch; on limit the last report is the structured partial.",
3926
+ tools: [progressReportTool(), ...options.tools ?? []],
3927
+ limits: mergeLimits(IMPLEMENTATION_PROFILE_LIMITS, options.limits)
3928
+ };
3929
+ }
3930
+ /**
3931
+ * The review child template: the caller's task tools plus the progress
3932
+ * contract, with {@link REVIEW_PROFILE_LIMITS} as the stop conditions
3933
+ * (a tighter turn budget and the no-new-evidence guard: a reviewer
3934
+ * circling over the same pages should stop, not spin).
3935
+ */
3936
+ function reviewAgentProfile(options = {}) {
3937
+ return {
3938
+ description: options.description ?? "Focused review with built-in stop conditions: tight turn and tool budgets with notices, repetition and no-new-evidence guards. Report findings with report_progress after every batch; on limit the last report is the structured partial.",
3939
+ tools: [progressReportTool(), ...options.tools ?? []],
3940
+ limits: mergeLimits(REVIEW_PROFILE_LIMITS, options.limits)
3941
+ };
3942
+ }
3943
+ //#endregion
3753
3944
  //#region src/journal/identity.ts
3754
3945
  /**
3755
3946
  * Content-addressed entry identity (M1-T04): IdentityInput records per
@@ -7733,9 +7924,10 @@ var Semaphore = class {
7733
7924
  * ride RetryPolicy; hosts with known tier limits opt in per adapter id
7734
7925
  * via createEngine concurrency.perProvider.
7735
7926
  *
7736
- * There is deliberately NO distributed cross-process limiter: two
7737
- * processes sharing one API key coordinate nothing here (a
7738
- * process-global limiter is an open question).
7927
+ * This keyed limiter bounds PARALLELISM inside one engine only. Two
7928
+ * processes sharing one API key coordinate through the QuotaLimiter
7929
+ * SPI instead (RV-215, createEngine `quota`): rate and volume live
7930
+ * there, in shared storage; in-flight slots live here.
7739
7931
  */
7740
7932
  var KeyedLimiter = class {
7741
7933
  semaphores = /* @__PURE__ */ new Map();
@@ -7936,6 +8128,223 @@ function liftRetainedParts(providerMetadata, adapter) {
7936
8128
  }));
7937
8129
  }
7938
8130
  //#endregion
8131
+ //#region src/model/quota.ts
8132
+ /**
8133
+ * Quota rules and the in-process reference QuotaLimiter (RV-215).
8134
+ * The rule model is shared by every reference implementation
8135
+ * (memoryQuotaLimiter here, SqliteQuotaLimiter in
8136
+ * @rulvar/store-sqlite): fixed one-minute windows aligned to the
8137
+ * epoch, admission at reservation time, reconciliation to actual
8138
+ * usage inside the same window. The hard guarantee is on
8139
+ * `requestsPerMinute` (every wire attempt is exactly one request);
8140
+ * `tokensPerMinute` admits on the heuristic estimate and settles to
8141
+ * actual usage, so token windows are approximate at admission and
8142
+ * exact at settlement.
8143
+ *
8144
+ * Docs: https://docs.rulvar.com/guide/model-routing
8145
+ */
8146
+ /**
8147
+ * Captured at module load, before the InProcessRunner's
8148
+ * nondeterminism guard can patch the global: the limiter's clock is
8149
+ * engine infrastructure on the live-only dispatch path and must never
8150
+ * be blamed on workflow code.
8151
+ */
8152
+ const nativeNow = Date.now;
8153
+ /** The fixed accounting window every PerMinute cap counts over. */
8154
+ const QUOTA_WINDOW_MS = 6e4;
8155
+ /**
8156
+ * Validates a quota rule set as a typed ConfigError before any
8157
+ * limiter can admit under it: a non-array or empty set, a rule
8158
+ * without a cap, a malformed dimension, or a malformed cap all fail
8159
+ * loud at construction. Shared by every reference implementation.
8160
+ */
8161
+ function validateQuotaRules(rules, site = "quota rules") {
8162
+ const raw = rules;
8163
+ if (!Array.isArray(raw)) throw new ConfigError(`${site} must be an array of QuotaRule objects`);
8164
+ if (raw.length === 0) throw new ConfigError(`${site} must contain at least one rule`);
8165
+ raw.forEach((entry, index) => {
8166
+ const at = `${site}[${String(index)}]`;
8167
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) throw new ConfigError(`${at} must be a QuotaRule object`);
8168
+ const rule = entry;
8169
+ for (const dimension of [
8170
+ "provider",
8171
+ "model",
8172
+ "tenant"
8173
+ ]) {
8174
+ const value = rule[dimension];
8175
+ if (value !== void 0 && (typeof value !== "string" || value === "")) throw new ConfigError(`${at}.${dimension} must be a nonempty string when given`);
8176
+ }
8177
+ if (rule.requestsPerMinute === void 0 && rule.tokensPerMinute === void 0) throw new ConfigError(`${at} must set requestsPerMinute or tokensPerMinute (or both)`);
8178
+ for (const cap of ["requestsPerMinute", "tokensPerMinute"]) if (rule[cap] !== void 0) requirePositiveInteger(rule[cap], `${at}.${cap}`);
8179
+ });
8180
+ }
8181
+ /** True when every dimension the rule pins matches the request. */
8182
+ function quotaRuleMatches(rule, request) {
8183
+ return (rule.provider === void 0 || rule.provider === request.provider) && (rule.model === void 0 || rule.model === request.model) && (rule.tenant === void 0 || rule.tenant === request.tenant);
8184
+ }
8185
+ /** The tokens a reservation is admitted under: input estimate plus the output cap. */
8186
+ function quotaEstimateTokens(request) {
8187
+ return request.estimate.inputTokens + (request.estimate.maxOutputTokens ?? 0);
8188
+ }
8189
+ /** The tokens a settled attempt actually consumed. */
8190
+ function quotaActualTokens(usage) {
8191
+ return usage.inputTokens + usage.outputTokens;
8192
+ }
8193
+ /**
8194
+ * One rule's admission verdict against its current-window counters,
8195
+ * the pure decision both reference implementations share. A denial
8196
+ * carries the window remainder as retryAfterMs, except when the
8197
+ * estimate alone can never fit the token cap: that denial says
8198
+ * retryAfterMs 0 (retry immediately), so the caller's bounded
8199
+ * attempts exhaust without waiting and failover gets its chance.
8200
+ */
8201
+ function quotaRuleAdmission(rule, counters, estimate, msUntilWindowEnd) {
8202
+ if (rule.requestsPerMinute !== void 0 && counters.requests + estimate.requests > rule.requestsPerMinute) return {
8203
+ admit: false,
8204
+ retryAfterMs: msUntilWindowEnd,
8205
+ reason: `requestsPerMinute ${String(rule.requestsPerMinute)} exhausted`
8206
+ };
8207
+ if (rule.tokensPerMinute !== void 0) {
8208
+ if (estimate.tokens > rule.tokensPerMinute) return {
8209
+ admit: false,
8210
+ retryAfterMs: 0,
8211
+ reason: `the estimate of ${String(estimate.tokens)} tokens can never fit tokensPerMinute ${String(rule.tokensPerMinute)}`
8212
+ };
8213
+ if (counters.tokens + estimate.tokens > rule.tokensPerMinute) return {
8214
+ admit: false,
8215
+ retryAfterMs: msUntilWindowEnd,
8216
+ reason: `tokensPerMinute ${String(rule.tokensPerMinute)} exhausted`
8217
+ };
8218
+ }
8219
+ return { admit: true };
8220
+ }
8221
+ /**
8222
+ * Folds one more failing rule into the decision the caller returns:
8223
+ * the wait is the LONGEST failing horizon (every matching rule must
8224
+ * admit), and the FIRST failing rule names the denial.
8225
+ */
8226
+ function mergeQuotaDenial(current, next) {
8227
+ if (current === void 0) return {
8228
+ retryAfterMs: next.retryAfterMs,
8229
+ reason: next.reason
8230
+ };
8231
+ return next.retryAfterMs > current.retryAfterMs ? {
8232
+ retryAfterMs: next.retryAfterMs,
8233
+ reason: current.reason
8234
+ } : current;
8235
+ }
8236
+ /**
8237
+ * The in-process reference QuotaLimiter: fixed epoch-aligned
8238
+ * one-minute windows over the shared rule model. Coordinates every
8239
+ * engine that shares THIS instance inside one process; processes
8240
+ * coordinate through a shared-storage implementation of the same SPI
8241
+ * (SqliteQuotaLimiter in @rulvar/store-sqlite) instead.
8242
+ */
8243
+ function memoryQuotaLimiter(rules, options = {}) {
8244
+ validateQuotaRules(rules, "memoryQuotaLimiter rules");
8245
+ const now = options.now ?? (() => nativeNow());
8246
+ const buckets = /* @__PURE__ */ new Map();
8247
+ const reservations = /* @__PURE__ */ new Map();
8248
+ let nextReservation = 0;
8249
+ const windowStartAt = (at) => at - at % QUOTA_WINDOW_MS;
8250
+ const bucketFor = (ruleIndex, windowStart) => {
8251
+ let bucket = buckets.get(ruleIndex);
8252
+ if (bucket === void 0 || bucket.windowStart !== windowStart) {
8253
+ bucket = {
8254
+ windowStart,
8255
+ requests: 0,
8256
+ tokens: 0
8257
+ };
8258
+ buckets.set(ruleIndex, bucket);
8259
+ }
8260
+ return bucket;
8261
+ };
8262
+ const prune = (windowStart) => {
8263
+ for (const [id, reservation] of reservations) if (reservation.windowStart < windowStart) reservations.delete(id);
8264
+ };
8265
+ return {
8266
+ reserve(request) {
8267
+ const at = now();
8268
+ const windowStart = windowStartAt(at);
8269
+ prune(windowStart);
8270
+ const estimateTokens = quotaEstimateTokens(request);
8271
+ const msUntilWindowEnd = windowStart + QUOTA_WINDOW_MS - at;
8272
+ const matched = [];
8273
+ let denial;
8274
+ rules.forEach((rule, index) => {
8275
+ if (!quotaRuleMatches(rule, request)) return;
8276
+ matched.push(index);
8277
+ const verdict = quotaRuleAdmission(rule, bucketFor(index, windowStart), {
8278
+ requests: request.estimate.requests,
8279
+ tokens: estimateTokens
8280
+ }, msUntilWindowEnd);
8281
+ if (!verdict.admit) denial = mergeQuotaDenial(denial, verdict);
8282
+ });
8283
+ if (denial !== void 0) return Promise.resolve({
8284
+ granted: false,
8285
+ ...denial
8286
+ });
8287
+ for (const index of matched) {
8288
+ const bucket = bucketFor(index, windowStart);
8289
+ bucket.requests += request.estimate.requests;
8290
+ bucket.tokens += estimateTokens;
8291
+ }
8292
+ nextReservation += 1;
8293
+ const reservationId = `mq-${String(nextReservation)}`;
8294
+ reservations.set(reservationId, {
8295
+ windowStart,
8296
+ estimateTokens,
8297
+ ruleIndexes: matched
8298
+ });
8299
+ return Promise.resolve({
8300
+ granted: true,
8301
+ reservationId
8302
+ });
8303
+ },
8304
+ reconcile(reservationId, usage) {
8305
+ const reservation = reservations.get(reservationId);
8306
+ if (reservation === void 0) return Promise.resolve();
8307
+ reservations.delete(reservationId);
8308
+ const windowStart = windowStartAt(now());
8309
+ if (reservation.windowStart !== windowStart) return Promise.resolve();
8310
+ const delta = quotaActualTokens(usage) - reservation.estimateTokens;
8311
+ for (const index of reservation.ruleIndexes) {
8312
+ const bucket = buckets.get(index);
8313
+ if (bucket !== void 0 && bucket.windowStart === windowStart) bucket.tokens = Math.max(0, bucket.tokens + delta);
8314
+ }
8315
+ return Promise.resolve();
8316
+ },
8317
+ snapshot() {
8318
+ const windowStart = windowStartAt(now());
8319
+ return rules.map((rule, index) => {
8320
+ const bucket = buckets.get(index);
8321
+ const current = bucket !== void 0 && bucket.windowStart === windowStart;
8322
+ return {
8323
+ rule,
8324
+ windowStart,
8325
+ requests: current ? bucket.requests : 0,
8326
+ tokens: current ? bucket.tokens : 0
8327
+ };
8328
+ });
8329
+ }
8330
+ };
8331
+ }
8332
+ /**
8333
+ * Validates createEngine's quota config as a typed ConfigError before
8334
+ * any run could dispatch under a malformed limiter (the intake
8335
+ * discipline every engine option follows).
8336
+ */
8337
+ function validateEngineQuotaConfig(config, site = "createEngine quota") {
8338
+ if (config === void 0) return;
8339
+ const raw = config;
8340
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new ConfigError(`${site} must be an object with a limiter`);
8341
+ const candidate = raw;
8342
+ const limiter = candidate.limiter;
8343
+ if (typeof limiter !== "object" || limiter === null || typeof limiter.reserve !== "function" || typeof limiter.reconcile !== "function") throw new ConfigError(`${site}.limiter must implement QuotaLimiter (reserve and reconcile functions)`);
8344
+ if (candidate.tenant !== void 0 && (typeof candidate.tenant !== "string" || candidate.tenant === "")) throw new ConfigError(`${site}.tenant must be a nonempty string when given`);
8345
+ if (candidate.onLimiterError !== void 0 && candidate.onLimiterError !== "deny" && candidate.onLimiterError !== "allow") throw new ConfigError(`${site}.onLimiterError must be 'deny' or 'allow' when given`);
8346
+ }
8347
+ //#endregion
7939
8348
  //#region src/model/retry.ts
7940
8349
  /**
7941
8350
  * Transport RetryPolicy (M4-T05): retries live UNDER the journal. A
@@ -8461,6 +8870,10 @@ function mergeUsageLimits(call, profile, engine) {
8461
8870
  if (maxRepeatedToolSignature !== void 0) merged.maxRepeatedToolSignature = maxRepeatedToolSignature;
8462
8871
  const maxNoNewEvidenceCalls = pick("maxNoNewEvidenceCalls");
8463
8872
  if (maxNoNewEvidenceCalls !== void 0) merged.maxNoNewEvidenceCalls = maxNoNewEvidenceCalls;
8873
+ const maxCallsPerTool = pick("maxCallsPerTool");
8874
+ if (maxCallsPerTool !== void 0) merged.maxCallsPerTool = maxCallsPerTool;
8875
+ const toolUnits = pick("toolUnits");
8876
+ if (toolUnits !== void 0) merged.toolUnits = toolUnits;
8464
8877
  return merged;
8465
8878
  }
8466
8879
  /**
@@ -8485,6 +8898,21 @@ function validateUsageLimits(limits, site) {
8485
8898
  if (limits.toolBudgetNotices !== void 0 && typeof limits.toolBudgetNotices !== "boolean") throw new ConfigError(`${site}.toolBudgetNotices must be a boolean; got ${typeof limits.toolBudgetNotices}`);
8486
8899
  if (limits.maxRepeatedToolSignature !== void 0) requirePositiveInteger(limits.maxRepeatedToolSignature, `${site}.maxRepeatedToolSignature`);
8487
8900
  if (limits.maxNoNewEvidenceCalls !== void 0) requirePositiveInteger(limits.maxNoNewEvidenceCalls, `${site}.maxNoNewEvidenceCalls`);
8901
+ if (limits.maxCallsPerTool !== void 0) {
8902
+ const caps = limits.maxCallsPerTool;
8903
+ if (typeof caps !== "object" || caps === null || Array.isArray(caps)) throw new ConfigError(`${site}.maxCallsPerTool must be a record of per-tool caps`);
8904
+ for (const [name, cap] of Object.entries(caps)) requireNonNegativeInteger(cap, `${site}.maxCallsPerTool['${name}']`);
8905
+ }
8906
+ if (limits.toolUnits !== void 0) {
8907
+ const units = limits.toolUnits;
8908
+ if (typeof units !== "object" || units === null || Array.isArray(units)) throw new ConfigError(`${site}.toolUnits must be { max, costs? }`);
8909
+ const { max, costs } = units;
8910
+ requirePositiveInteger(max, `${site}.toolUnits.max`);
8911
+ if (costs !== void 0) {
8912
+ if (typeof costs !== "object" || costs === null || Array.isArray(costs)) throw new ConfigError(`${site}.toolUnits.costs must be a record of per-tool costs`);
8913
+ for (const [name, cost] of Object.entries(costs)) requireNonNegativeInteger(cost, `${site}.toolUnits.costs['${name}']`);
8914
+ }
8915
+ }
8488
8916
  }
8489
8917
  //#endregion
8490
8918
  //#region src/runtime/model-retry.ts
@@ -9019,7 +9447,7 @@ function formatRePrompt(issues, attempt, maxAttempts) {
9019
9447
  const GUARD_DOCS_URL = "https://docs.rulvar.com/guide/agents#exploration-guards";
9020
9448
  /** True when any exploration guard field asks for tracking. */
9021
9449
  function explorationTrackingEnabled(limits) {
9022
- return limits.maxRepeatedToolSignature !== void 0 || limits.maxNoNewEvidenceCalls !== void 0 || limits.toolBudgetNotices === true;
9450
+ return limits.maxRepeatedToolSignature !== void 0 || limits.maxNoNewEvidenceCalls !== void 0 || limits.toolBudgetNotices === true || limits.maxCallsPerTool !== void 0 || limits.toolUnits !== void 0;
9023
9451
  }
9024
9452
  function digestOf$1(value) {
9025
9453
  try {
@@ -9038,6 +9466,8 @@ var ExplorationGuard = class {
9038
9466
  repeated = 0;
9039
9467
  duplicateResults = 0;
9040
9468
  denied = 0;
9469
+ deniedToolCap = 0;
9470
+ unitsUsed = 0;
9041
9471
  unserializableSeq = 0;
9042
9472
  constructor(config) {
9043
9473
  this.config = config;
@@ -9075,10 +9505,25 @@ var ExplorationGuard = class {
9075
9505
  }
9076
9506
  }
9077
9507
  /**
9078
- * The pre-dispatch verdict: denies the call that would exceed
9079
- * maxRepeatedToolSignature executions of the same signature.
9508
+ * The pre-dispatch verdict: denies the call that would exceed its
9509
+ * tool's maxCallsPerTool cap, then the call that would exceed
9510
+ * maxRepeatedToolSignature executions of the same signature. A denial
9511
+ * never consumes maxToolCalls or tool units.
9080
9512
  */
9081
9513
  beforeExecute(name, args) {
9514
+ const cap = this.config.maxCallsPerTool?.[name];
9515
+ if (cap !== void 0) {
9516
+ const executions = this.byTool.get(name) ?? 0;
9517
+ if (executions >= cap) {
9518
+ this.deniedToolCap += 1;
9519
+ return {
9520
+ deny: true,
9521
+ guard: "per-tool-cap",
9522
+ executions,
9523
+ reason: `exploration guard: '${name}' already executed ${String(executions)} time(s) this invocation (maxCallsPerTool ${String(cap)}). Use what you have or a different tool (${GUARD_DOCS_URL}).`
9524
+ };
9525
+ }
9526
+ }
9082
9527
  const max = this.config.maxRepeatedToolSignature;
9083
9528
  if (max === void 0) return { deny: false };
9084
9529
  const executions = this.signatureExecutions.get(this.signatureOf(name, args)) ?? 0;
@@ -9086,6 +9531,7 @@ var ExplorationGuard = class {
9086
9531
  this.denied += 1;
9087
9532
  return {
9088
9533
  deny: true,
9534
+ guard: "repeated-signature",
9089
9535
  executions,
9090
9536
  reason: `exploration guard: this exact '${name}' call already executed ${String(executions)} time(s) this invocation (maxRepeatedToolSignature ${String(max)}). Reuse the earlier result or change the arguments (${GUARD_DOCS_URL}).`
9091
9537
  };
@@ -9103,6 +9549,7 @@ var ExplorationGuard = class {
9103
9549
  recordExecution(name, args, result, successful) {
9104
9550
  this.executed += 1;
9105
9551
  this.byTool.set(name, (this.byTool.get(name) ?? 0) + 1);
9552
+ if (this.config.toolUnits !== void 0) this.unitsUsed += this.config.toolUnits.costs?.[name] ?? 1;
9106
9553
  const signature = this.signatureOf(name, args);
9107
9554
  const prior = this.signatureExecutions.get(signature) ?? 0;
9108
9555
  if (prior > 0) this.repeated += 1;
@@ -9119,6 +9566,14 @@ var ExplorationGuard = class {
9119
9566
  const max = this.config.maxNoNewEvidenceCalls;
9120
9567
  return max !== void 0 && this.noNewEvidenceStreak >= max;
9121
9568
  }
9569
+ /**
9570
+ * True once the spent tool units reached the weighted budget: the
9571
+ * loop's pre-dispatch check, mirroring maxToolCalls (terminal 'limit',
9572
+ * paid partial work). Never true without toolUnits configured.
9573
+ */
9574
+ unitsExhausted() {
9575
+ return this.config.toolUnits !== void 0 && this.unitsUsed >= this.config.toolUnits.max;
9576
+ }
9122
9577
  /** The abort message for a tripped no-new-evidence guard. */
9123
9578
  describeTrip() {
9124
9579
  return `exploration guard: ${String(this.noNewEvidenceStreak)} consecutive tool calls returned no new evidence (maxNoNewEvidenceCalls ${String(this.config.maxNoNewEvidenceCalls ?? this.noNewEvidenceStreak)}; every result was already seen this invocation). The executed work is kept; narrow the scope, vary the queries, or raise the limit (${GUARD_DOCS_URL}).`;
@@ -9133,7 +9588,9 @@ var ExplorationGuard = class {
9133
9588
  repeatedCalls: this.repeated,
9134
9589
  duplicateResultCalls: this.duplicateResults,
9135
9590
  deniedRepeats: this.denied,
9136
- byTool
9591
+ byTool,
9592
+ ...this.config.maxCallsPerTool === void 0 ? {} : { deniedToolCap: this.deniedToolCap },
9593
+ ...this.config.toolUnits === void 0 ? {} : { toolUnitsUsed: this.unitsUsed }
9137
9594
  };
9138
9595
  }
9139
9596
  };
@@ -9743,6 +10200,10 @@ async function runAgent(options) {
9743
10200
  parts,
9744
10201
  limitHit: true
9745
10202
  };
10203
+ if (guard !== void 0 && guard.unitsExhausted()) return {
10204
+ parts,
10205
+ limitHit: true
10206
+ };
9746
10207
  const def = runtime.defs.find((candidate) => candidate.name === call.name);
9747
10208
  events?.emit({
9748
10209
  type: "tool:start",
@@ -9898,11 +10359,11 @@ async function runAgent(options) {
9898
10359
  toolName: gatedCall.name,
9899
10360
  outcome: "denied",
9900
10361
  durationMs: now() - gateStartedAt,
9901
- guard: "repeated-signature"
10362
+ guard: guardVerdict.guard
9902
10363
  });
9903
10364
  parts.push(errorPart(call, {
9904
10365
  error: guardVerdict.reason,
9905
- guard: "repeated-signature"
10366
+ guard: guardVerdict.guard
9906
10367
  }));
9907
10368
  continue;
9908
10369
  }
@@ -10068,12 +10529,74 @@ async function runAgent(options) {
10068
10529
  const target = site.chain[site.cursor.index] ?? site.chain[0];
10069
10530
  let tries = 0;
10070
10531
  inner: for (;;) {
10532
+ let reservationId;
10533
+ const quotaDeniedOutcome = (denial) => ({
10534
+ turn: {
10535
+ text: "",
10536
+ toolCalls: []
10537
+ },
10538
+ usage: ZERO_USAGE$1,
10539
+ reported: ZERO_USAGE$1,
10540
+ usageApprox: false,
10541
+ quotaDenied: true,
10542
+ wireError: {
10543
+ code: denial.infrastructure === void 0 ? "rate-limit" : "quota-limiter",
10544
+ message: denial.infrastructure ?? `the shared quota limiter denied ${target.resolved.ref}` + (denial.reason === void 0 ? "" : `: ${denial.reason}`),
10545
+ retryable: true,
10546
+ data: {
10547
+ kind: denial.infrastructure === void 0 ? "rate-limit" : "transport",
10548
+ source: "quota-limiter",
10549
+ ...denial.retryAfterMs === void 0 ? {} : { retryAfterMs: denial.retryAfterMs },
10550
+ ...denial.reason === void 0 ? {} : { reason: denial.reason }
10551
+ }
10552
+ }
10553
+ });
10554
+ const dispatchWithQuota = async (quota) => {
10555
+ const req = site.requestFor(target);
10556
+ let decision;
10557
+ try {
10558
+ decision = await quota.reserve({
10559
+ provider: target.adapter.id,
10560
+ model: target.resolved.model,
10561
+ estimate: {
10562
+ requests: 1,
10563
+ inputTokens: estimateInputTokens(req.messages),
10564
+ ...req.maxOutputTokens === void 0 ? {} : { maxOutputTokens: req.maxOutputTokens }
10565
+ }
10566
+ });
10567
+ } catch (thrown) {
10568
+ const detail = thrown instanceof Error ? thrown.message : String(thrown);
10569
+ if (quota.onLimiterError === "allow") {
10570
+ events?.emit({
10571
+ type: "log",
10572
+ level: "warn",
10573
+ msg: `the shared quota limiter failed; dispatching ${target.resolved.ref} without a reservation (onLimiterError 'allow'): ${detail}`
10574
+ });
10575
+ return streamTurn(target.adapter, req, site.streamOptionsFor(target));
10576
+ }
10577
+ return quotaDeniedOutcome({ infrastructure: `the shared quota limiter failed (onLimiterError 'deny'): ${detail}` });
10578
+ }
10579
+ if (!decision.granted) return quotaDeniedOutcome(decision);
10580
+ reservationId = decision.reservationId;
10581
+ return streamTurn(target.adapter, req, site.streamOptionsFor(target));
10582
+ };
10071
10583
  const dispatch = () => {
10072
10584
  const aborted = abortKind();
10073
- return aborted === void 0 ? streamTurn(target.adapter, site.requestFor(target), site.streamOptionsFor(target)) : Promise.resolve(abortedOutcome(aborted));
10585
+ if (aborted !== void 0) return Promise.resolve(abortedOutcome(aborted));
10586
+ return options.quota === void 0 ? streamTurn(target.adapter, site.requestFor(target), site.streamOptionsFor(target)) : dispatchWithQuota(options.quota);
10074
10587
  };
10075
10588
  const outcome = await (options.providerSlot === void 0 ? dispatch() : options.providerSlot(target.adapter.id, dispatch, options.signal));
10076
- recordUsage(outcome.usage, outcome.reported, target.adapter.id, target.resolved.ref, site.role, outcome.usageViolation);
10589
+ if (reservationId !== void 0 && options.quota !== void 0) try {
10590
+ await options.quota.reconcile(reservationId, outcome.usage);
10591
+ } catch (thrown) {
10592
+ const detail = thrown instanceof Error ? thrown.message : String(thrown);
10593
+ events?.emit({
10594
+ type: "log",
10595
+ level: "warn",
10596
+ msg: `the shared quota limiter failed to reconcile a reservation: ${detail}`
10597
+ });
10598
+ }
10599
+ if (outcome.quotaDenied !== true) recordUsage(outcome.usage, outcome.reported, target.adapter.id, target.resolved.ref, site.role, outcome.usageViolation);
10077
10600
  tries += 1;
10078
10601
  const retryClass = outcome.aborted === "idle" ? "transport" : outcome.wireError === void 0 ? void 0 : retryClassOf(outcome.wireError);
10079
10602
  if (retryClass === void 0) return {
@@ -10751,6 +11274,8 @@ async function runAgent(options) {
10751
11274
  }
10752
11275
  endPhase(extractPhase, phaseOutcome(), extractServed);
10753
11276
  }
11277
+ const limitPartial = status === "limit" ? latestProgressReport(messages) : void 0;
11278
+ if (limitPartial !== void 0) await saveBoundary();
10754
11279
  let transcriptRef = "";
10755
11280
  if (options.transcript !== void 0) {
10756
11281
  transcriptRef = options.transcript.mintRef();
@@ -10773,6 +11298,7 @@ async function runAgent(options) {
10773
11298
  if (abortClass !== void 0) result.abortClass = abortClass;
10774
11299
  if (errorMessage !== void 0) result.errorMessage = errorMessage;
10775
11300
  if (guard !== void 0) result.exploration = guard.summary(toolCallsUsed);
11301
+ if (limitPartial !== void 0) result.partial = limitPartial;
10776
11302
  if (usageApprox) result.usageApprox = true;
10777
11303
  if (transportRetries > 0) result.transportRetries = transportRetries;
10778
11304
  return result;
@@ -11744,7 +12270,13 @@ const WAKE_SUMMARY_RENDER_BUDGET_CHARS = 400;
11744
12270
  * spawn ordinal; the LLM distillation upgrade is M7 territory).
11745
12271
  */
11746
12272
  function summarizeOutput(result) {
11747
- return truncateToBudget(result.status === "ok" ? typeof result.output === "string" ? result.output : JSON.stringify(result.output ?? null) : result.errorMessage ?? `terminal status ${result.status}`, 400);
12273
+ let raw;
12274
+ if (result.status === "ok") raw = typeof result.output === "string" ? result.output : JSON.stringify(result.output ?? null);
12275
+ else {
12276
+ raw = result.errorMessage ?? `terminal status ${result.status}`;
12277
+ if (result.partial !== void 0) raw = `${raw}; partial: ${JSON.stringify(result.partial)}`;
12278
+ }
12279
+ return truncateToBudget(raw, 400);
11748
12280
  }
11749
12281
  /** Folds one settled child into its digest (spawn-ordinal ordering is the caller's). */
11750
12282
  function digestOf(record, result) {
@@ -12617,6 +13149,10 @@ function createCtx(internals, rootWorkflow) {
12617
13149
  const checkpoint = blob === null ? void 0 : decodeCheckpoint(blob);
12618
13150
  if (checkpoint !== void 0) {
12619
13151
  result.turns = checkpoint.turns;
13152
+ if (result.status === "limit") {
13153
+ const partialReport = latestProgressReport(checkpoint.messages);
13154
+ if (partialReport !== void 0) result.partial = partialReport;
13155
+ }
12620
13156
  replayedToolResults = checkpoint.messages.filter((msg) => msg.role === "tool").flatMap((msg) => msg.parts).filter((part) => part.type === "tool-result").map((part) => ({
12621
13157
  name: part.name,
12622
13158
  isError: part.isError === true
@@ -12995,6 +13531,18 @@ function createCtx(internals, rootWorkflow) {
12995
13531
  if (profile?.compaction !== void 0) runAgentOptions.compaction = profile.compaction;
12996
13532
  if (loopFallbacks.length > 0) runAgentOptions.fallbacks = loopFallbacks;
12997
13533
  if (retryPolicy !== void 0) runAgentOptions.retry = { policy: retryPolicy };
13534
+ if (internals.quota !== void 0) {
13535
+ const quota = internals.quota;
13536
+ runAgentOptions.quota = {
13537
+ reserve: (request) => quota.limiter.reserve({
13538
+ ...request,
13539
+ runId: internals.runId,
13540
+ ...quota.tenant === void 0 ? {} : { tenant: quota.tenant }
13541
+ }),
13542
+ reconcile: (reservationId, usage) => quota.limiter.reconcile(reservationId, usage),
13543
+ onLimiterError: quota.onLimiterError
13544
+ };
13545
+ }
12998
13546
  if (internals.providerLimiter !== void 0) {
12999
13547
  const limiter = internals.providerLimiter;
13000
13548
  runAgentOptions.providerSlot = (key, fn, signal) => limiter.withSlot(key, fn, () => internals.events.emit({
@@ -13817,7 +14365,14 @@ function pageOf(content, rawOffset, rawMaxChars) {
13817
14365
  }
13818
14366
  /** The serialized full result of a settled child: the raw string, or JSON. */
13819
14367
  function serializeChildOutput(result) {
13820
- if (result.status !== "ok") return result.errorMessage ?? `terminal status ${result.status}`;
14368
+ if (result.status !== "ok") {
14369
+ const base = result.errorMessage ?? `terminal status ${result.status}`;
14370
+ if (result.partial !== void 0) return JSON.stringify({
14371
+ error: base,
14372
+ partial: result.partial
14373
+ });
14374
+ return base;
14375
+ }
13821
14376
  return typeof result.output === "string" ? result.output : JSON.stringify(result.output ?? null);
13822
14377
  }
13823
14378
  /**
@@ -13839,6 +14394,8 @@ function validateOrchestrateOptions(opts) {
13839
14394
  const minSuccessful = typeof policy === "object" && policy !== null && !Array.isArray(policy) ? policy.minSuccessful : void 0;
13840
14395
  if (policy !== "all-ok" && minSuccessful === void 0) throw new ConfigError(`orchestrate acceptance.childPolicy must be 'all-ok' or { minSuccessful: N }; got ${JSON.stringify(policy)}`);
13841
14396
  if (policy !== "all-ok") requirePositiveInteger(minSuccessful, "orchestrate acceptance.childPolicy.minSuccessful");
14397
+ const acceptPartial = opts.acceptance.acceptPartialChildren;
14398
+ if (acceptPartial !== void 0 && typeof acceptPartial !== "boolean") throw new ConfigError(`orchestrate acceptance.acceptPartialChildren must be a boolean; got ${typeof acceptPartial}`);
13842
14399
  }
13843
14400
  if (opts.finishValidation !== void 0) {
13844
14401
  const fv = opts.finishValidation;
@@ -13903,6 +14460,16 @@ function finishValidationPromptLines(spec) {
13903
14460
  return [`The host validates every finish({ result }) with deterministic validators: ${names}.`, "A rejected finish returns the failure reasons as the tool error result; repair the result and call finish again. " + (repairs === 0 ? "No repair attempt is granted: the first rejected finish fails the run." : repairs === 1 ? "At most one repair attempt is granted before the run fails." : `At most ${String(repairs)} repair attempts are granted before the run fails.`)];
13904
14461
  }
13905
14462
  /**
14463
+ * The partial-salvage contract rides the PROMPT exactly like finish
14464
+ * validation (RV-210 close-out): present only when
14465
+ * acceptance.acceptPartialChildren is set, so every other configuration
14466
+ * keeps byte-identical coordination prompts.
14467
+ */
14468
+ function acceptancePromptLines(acceptance) {
14469
+ if (acceptance?.acceptPartialChildren !== true) return [];
14470
+ return ["Partial salvage is on: a child that ends at its limit AFTER recording progress with report_progress counts as a partial success for acceptance; its digest carries the partial and get_child_result (when enabled) pages the full report. When the gap matters, respawn a NARROWED child carrying the partial instead of repeating the task."];
14471
+ }
14472
+ /**
13906
14473
  * Resolves per-spawn dispatch options against the engine registries
13907
14474
  * (registered SchemaSpec and tool profile names; M7-T05). An
13908
14475
  * unknown ref is a typed ConfigError, surfaced as a tool error to the
@@ -15232,7 +15799,11 @@ function makeOrchestratorWorkflow(goal, opts) {
15232
15799
  const priorRejection = validationDecisions().find((decision) => decision.verdict === "rejected");
15233
15800
  if (priorRejection !== void 0) throw finishValidationError(priorRejection);
15234
15801
  }
15235
- const promptLines = [...extension?.promptLines?.() ?? [], ...finishValidationPromptLines(validationSpec)];
15802
+ const promptLines = [
15803
+ ...extension?.promptLines?.() ?? [],
15804
+ ...finishValidationPromptLines(validationSpec),
15805
+ ...acceptancePromptLines(opts?.acceptance)
15806
+ ];
15236
15807
  const result = await runtime.runInScope(orchestratorState, () => ctx.agent(orchestratorPrompt(goal, opts?.maxSpawns, promptLines.length === 0 ? void 0 : promptLines), agentOpts));
15237
15808
  const liveTermination = extensionTermination;
15238
15809
  if (liveTermination !== void 0) throw liveTermination;
@@ -15248,21 +15819,32 @@ function makeOrchestratorWorkflow(goal, opts) {
15248
15819
  else {
15249
15820
  const childStatusCounts = {};
15250
15821
  const degradedReasons = [];
15822
+ const salvaged = [];
15823
+ let hardDegraded = 0;
15824
+ const acceptPartial = opts.acceptance.acceptPartialChildren === true;
15251
15825
  const sortedRecords = [...records.values()].sort((a, b) => a.spawnOrdinal - b.spawnOrdinal);
15252
15826
  for (const record of sortedRecords) {
15253
15827
  const status = record.settled?.status ?? "running";
15254
15828
  childStatusCounts[status] = (childStatusCounts[status] ?? 0) + 1;
15255
- if (status !== "ok") degradedReasons.push(status === "running" ? `child ${record.nodeId} was still running when finish validated` : `child ${record.nodeId} settled '${status}'`);
15829
+ if (status === "ok") continue;
15830
+ if (acceptPartial && status === "limit" && record.settled?.partial !== void 0) {
15831
+ salvaged.push(record.nodeId);
15832
+ degradedReasons.push(`child ${record.nodeId} accepted as partial (settled 'limit' with a structured partial)`);
15833
+ continue;
15834
+ }
15835
+ hardDegraded += 1;
15836
+ degradedReasons.push(status === "running" ? `child ${record.nodeId} was still running when finish validated` : `child ${record.nodeId} settled '${status}'`);
15256
15837
  }
15257
15838
  const childPolicy = opts.acceptance.childPolicy;
15258
- const accepted = childPolicy === "all-ok" ? degradedReasons.length === 0 : (childStatusCounts.ok ?? 0) >= childPolicy.minSuccessful;
15839
+ const accepted = childPolicy === "all-ok" ? hardDegraded === 0 : (childStatusCounts.ok ?? 0) + salvaged.length >= childPolicy.minSuccessful;
15259
15840
  decision = {
15260
15841
  decisionType: "orchestrator_acceptance",
15261
15842
  verdict: accepted ? "accepted" : "rejected",
15262
15843
  completion: !accepted ? "rejected" : degradedReasons.length === 0 ? "complete" : "partial",
15263
15844
  childPolicy,
15264
15845
  childStatusCounts,
15265
- degradedReasons
15846
+ degradedReasons,
15847
+ ...salvaged.length === 0 ? {} : { salvagedPartialChildren: salvaged }
15266
15848
  };
15267
15849
  await internals.replayer.appendSinglePhase({
15268
15850
  scope: callingState.scope,
@@ -15281,14 +15863,16 @@ function makeOrchestratorWorkflow(goal, opts) {
15281
15863
  completion: "rejected",
15282
15864
  childPolicy: decision.childPolicy,
15283
15865
  childStatusCounts: decision.childStatusCounts,
15284
- degradedReasons: decision.degradedReasons
15866
+ degradedReasons: decision.degradedReasons,
15867
+ ...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren }
15285
15868
  } });
15286
15869
  }
15287
15870
  return {
15288
15871
  result: await runSynthesis(result.output),
15289
15872
  completion: decision.completion,
15290
15873
  childStatusCounts: decision.childStatusCounts,
15291
- degradedReasons: decision.degradedReasons
15874
+ degradedReasons: decision.degradedReasons,
15875
+ ...decision.salvagedPartialChildren === void 0 ? {} : { salvagedPartialChildren: decision.salvagedPartialChildren }
15292
15876
  };
15293
15877
  });
15294
15878
  }
@@ -16030,6 +16614,12 @@ function createEngine(options) {
16030
16614
  if (profile.compaction?.threshold !== void 0) requireFraction(profile.compaction.threshold, `createEngine defaults.profiles['${name}'].compaction.threshold`);
16031
16615
  }
16032
16616
  validateDeterminismConfig(options.determinism);
16617
+ validateEngineQuotaConfig(options.quota);
16618
+ const quotaRuntime = options.quota === void 0 ? void 0 : {
16619
+ limiter: options.quota.limiter,
16620
+ ...options.quota.tenant === void 0 ? {} : { tenant: options.quota.tenant },
16621
+ onLimiterError: options.quota.onLimiterError ?? "deny"
16622
+ };
16033
16623
  const knowledgeStore = options.stores?.modelKnowledge;
16034
16624
  const knowledge = knowledgeStore === void 0 ? void 0 : { current: () => knowledgeStore.current() };
16035
16625
  const runner = new InProcessRunner(options.onEscalation === void 0 ? void 0 : { onEscalation: options.onEscalation });
@@ -16136,6 +16726,7 @@ function createEngine(options) {
16136
16726
  admission,
16137
16727
  semaphore: new Semaphore(options.concurrency?.perRun ?? 12),
16138
16728
  providerLimiter,
16729
+ ...quotaRuntime === void 0 ? {} : { quota: quotaRuntime },
16139
16730
  ...options.pricing === void 0 ? {} : { pricingVersion: options.pricing.pricingVersion },
16140
16731
  ...options.budgetDefaults?.flatReserveUsd === void 0 ? {} : { flatReserveUsd: options.budgetDefaults.flatReserveUsd },
16141
16732
  ...defaults.roleFloors === void 0 ? {} : { floors: defaults.roleFloors },
@@ -16810,4 +17401,4 @@ function createSandboxBridge(ctx, options) {
16810
17401
  };
16811
17402
  }
16812
17403
  //#endregion
16813
- export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_CHILD_RESULT_PAGE_CHARS, DEFAULT_CITATION_PATTERN, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_EVIDENCE_MIN_SHARE, DEFAULT_FINISH_MAX_REPAIRS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_SYNTHESIS_MAX_TURNS, DEFAULT_SYNTHESIS_NOTE_MAX_TURNS, DedupIndex, DeterminismError, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EscalationDecisionAbortedError, EventBus, ExternalRegistry, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FileModelKnowledgeStore, FileTranscriptStore, GET_CHILD_RESULT_SCHEMA, GET_CHILD_RESULT_TOOL_NAME, GitWorktreeProvider, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_CHILD_RESULT_PAGE_CHARS, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, ParallelSiteCounter, PlanInvariantError, READ_CHILD_ARTIFACT_SCHEMA, READ_CHILD_ARTIFACT_TOOL_NAME, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RUN_SETTLE_DECISION_TYPE, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, assertFencedWrites, atCompactionThreshold, auditRun, auditRuns, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, dedupeRepeatedClaims, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, evidencePreservedValidator, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hasFencedWrites, hasMetaLookup, hashRunArgs, hashRunOutput, hashWorkflowBody, hashWorkflowSource, identityJcs, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lastRunSettle, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, mergeUsageLimits, metaMatchesFilter, minMatchesValidator, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, readRunMeta, readTerminationInit, reconcileRunMeta, reduceCriticalPath, reduceInvocationTable, registryKeyRing, remeasureQueue, replayDisposition, repositoryResearchToolset, requiredFieldsValidator, requiredSectionsValidator, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };
17404
+ export { AWAIT_SCHEMA, AdmissionController, AdmissionRejectedError, AgentCallError, BUDGET_ABORT_REASON, BudgetExhaustedError, CANCEL_AGENT_SCHEMA, CHECKPOINT_FORMAT_V1, CLAIM_STATEMENT_MAX_CHARS, CLAIM_TTL_DAYS, COMPACTION_SUMMARY_PREFIX, CURRENT_HASH_VERSION, ConfigError, DEFAULT_CHILD_BUDGET_FRACTION, DEFAULT_CHILD_RESULT_PAGE_CHARS, DEFAULT_CITATION_PATTERN, DEFAULT_COMPACTION_THRESHOLD, DEFAULT_ESCALATION_LIMITS, DEFAULT_EVIDENCE_MIN_SHARE, DEFAULT_FINISH_MAX_REPAIRS, DEFAULT_FLAT_RESERVE_USD, DEFAULT_MAX_CHILDREN_PER_NODE, DEFAULT_MAX_DEPTH, DEFAULT_MAX_OSCILLATIONS_PER_KEY, DEFAULT_MAX_PINNED_WORKTREES, DEFAULT_MAX_REVISIONS_PER_RUN, DEFAULT_MAX_TOTAL_SPAWNS, DEFAULT_MAX_TURNS, DEFAULT_MODEL_RETRY_ATTEMPTS, DEFAULT_NO_PROGRESS_TURNS, DEFAULT_PER_RUN_CONCURRENCY, DEFAULT_RETRY_POLICY, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DEFAULT_SYNTHESIS_MAX_TURNS, DEFAULT_SYNTHESIS_NOTE_MAX_TURNS, DedupIndex, DeterminismError, EMIT_RESULT_TOOL, EMPTY_SCHEMA_HASH, EMPTY_TOOLSET_HASH, ESCALATE_TOOL_NAME, ESCALATION_REPORT_SCHEMA, ESCALATION_REQUEST_SCHEMA, EVENT_SEGMENT_STRIDE, EscalationDecisionAbortedError, EventBus, ExternalRegistry, FINALIZE_SYNTHESIS_INSTRUCTION, FINISH_SCHEMA, FINISH_TOOL_NAME, FailRunError, FileModelKnowledgeStore, FileTranscriptStore, GET_CHILD_RESULT_SCHEMA, GET_CHILD_RESULT_TOOL_NAME, GitWorktreeProvider, IMPLEMENTATION_PROFILE_LIMITS, INBOX_PROPOSAL_TTL_DAYS, InMemoryStore, InMemoryTranscriptStore, InProcessRunner, InvalidResolutionError, JournalCompatibilityError, JournalMatcher, JournalMissError, JournalOrderViolation, JsonlFileStore, KB_ACTIVE_CLAIMS_CAP, KB_CARD_RENDER_BUDGET_CHARS, KeyedLimiter, KnowledgeCasError, LARGE_VALUE_WARN_BYTES, LEGACY_LTID_PREFIX, LEGACY_SIGNATURE_INPUTS, LINEAGE_SIG_VERSION, LeaseHeldError, LineageIndex, MASKED_SECRET, MAX_CHILD_RESULT_PAGE_CHARS, MAX_DEPTH_CEILING, ModelRetry, NoProgressDetector, NonSerializableValueError, ORCHESTRATE_WORKFLOW_NAME, OrchestratorCapConfigError, PARALLEL_AGENTS_SCHEMA, PROGRESS_REPORT_TOOL_NAME, ParallelSiteCounter, PlanInvariantError, QUOTA_WINDOW_MS, READ_CHILD_ARTIFACT_SCHEMA, READ_CHILD_ARTIFACT_TOOL_NAME, RESEARCH_PROFILE_LIMITS, REVIEW_PROFILE_LIMITS, ROLE_EFFORT_DEFAULTS, ROOT_ACCOUNT, ROOT_SCOPE, RUN_PROFILES, RUN_SETTLE_DECISION_TYPE, ReplayPlanHashMismatch, Replayer, ResolutionArbiter, ResolutionFold, RulvarError, RunBudget, SANDBOX_AGENT_OPT_KEYS, SPAWN_AGENT_SCHEMA, SandboxError, ScriptRejected, Semaphore, SpanRegistry, TOOL_NAME_PATTERN, TerminationAccount, WAIT_FOR_EVENTS_SCHEMA, WAIT_FOR_EVENTS_TOOL_NAME, WAKE_SUMMARY_RENDER_BUDGET_CHARS, admissionReserveUsd, affordableOutputTokens, agentErrorFromWire, agentErrorToWire, agentResultWire, agentScope, applyClaimOps, applyStructuredOutputTier, approachSigCoarse, approachSigOf, archiveDeprecatedModelOps, assertFencedWrites, atCompactionThreshold, auditRun, auditRuns, buildAbandonFold, buildAdapterRegistry, buildCostReport, buildDeriverRegistry, buildOrchestratorTools, buildTerminationInitValue, buildToolContext, canRideLoopTurn, canonicalIsolationTag, canonicalizeLadder, canonicalizeSchema, capIssues, capsHashOf, checkFloors, checkpointRefFor, childCoveragePrefix, claimExpired, claimExpiry, claimIssues, claimOpIssues, classifyAgentError, classifyAttemptOutcome, collectDeclaredLadders, compactMessages, compilePermissionChain, compilePermissionPreset, compileVerifiedLayer, costReportFromJournal, countsAgainstLimit, createCanonicalIdMinter, createCtx, createEngine, createSandboxBridge, currentOnlyKeyRing, decodeCheckpoint, dedupeRepeatedClaims, defineWorkflow, deriveContentKey, deriverV1, deriverV2, digestOf, dispositionHook, emptyDigestBlocks, emptyToolset, encodeCheckpoint, entryUsageSlices, escalateTool, evaluatePermission, evaluateReuse, evidencePreservedValidator, executeWorkflow, exhaustionCodeOf, extractCandidate, failoverTriggerOf, fallbackTriggerOf, filterClaimsForRun, finalizeFires, foldTermination, formatRePrompt, formatScopePath, hasFencedWrites, hasMetaLookup, hashRunArgs, hashRunOutput, hashWorkflowBody, hashWorkflowSource, identityJcs, implementationAgentProfile, isEscalated, isSchemaPairSpec, isStandardSchemaSpec, isStrictCompatibleSchema, kMaxOf, knowledgeHash, ladderLengthOf, ladderRungChoice, lastRunSettle, latestProgressReport, lexShellCommand, liftRetainedParts, lineageWeightOf, makeOrchestratorWorkflow, maskSecrets, maskSecretsDeep, maskSecretsJson, matchArgvPattern, matchShellCommand, mcp, memoryQuotaLimiter, mergeQuotaDenial, mergeUsageLimits, metaMatchesFilter, minMatchesValidator, modelEpochOf, modelKnowledgeCard, modelSpecIdentity, needsSeparateExtract, nextFailover, nodeLinkKey, normalizeApproachTag, normalizeEntry, normalizeFallbacks, orchestrate, parallelScope, parseModelRef, parseScopePath, phiInitialOf, pipelineScope, planNodeScope, priceEntryUsage, priceUsdOf, profileCard, profileRegistrySnapshotHash, progressReportTool, projectHistory, projectIdentity, projectToJsonSchema, proposalStatement, providerOf, quotaActualTokens, quotaEstimateTokens, quotaRuleAdmission, quotaRuleMatches, readRunMeta, readTerminationInit, reconcileRunMeta, reduceCriticalPath, reduceInvocationTable, registryKeyRing, remeasureQueue, replayDisposition, repositoryResearchToolset, requiredFieldsValidator, requiredSectionsValidator, researchAgentProfile, resolveModelInvocation, resolvePricing, resolveToolset, retryClassOf, retryDelayMs, reviewAgentProfile, roleConfiguredInRouting, roundOneDisposition, runAgent, runProfile, sanitizeTerminalText, sanitizeTokenCount, sanitizeUsage, sanitizeUsageDelta, scanJournalCompatibility, schemaHash, schemaHashOfSpec, selectStructuredOutputTier, shouldCompact, snapshotUsage, spawnDepthOf, summarizeInstruction, summarizeOutput, terminationConfigDrift, tierWithinCaps, toApprovalDecision, toJournalValue, tool, toolContract, toolsetHash, ttlState, usageViolations, validateEditorialCommit, validateEngineQuotaConfig, validateEntryShape, validateEscalationLimits, validateEscalationReport, validateQuotaRules, validateRetryPolicy, validateSchemaSpec, validateTerminationLimits, validateUsageLimits, workflowScope, workflowSourceRef, wrapJournalStore, wrapTranscriptStore };