open-multi-agent-kit 0.95.2 → 0.96.1
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/CHANGELOG.md +60 -0
- package/README.md +37 -4
- package/dist/approvals/approval-receipt-store.d.ts.map +1 -1
- package/dist/approvals/approval-receipt-store.js +25 -0
- package/dist/approvals/approval-receipt-store.js.map +1 -1
- package/dist/commands/doctor-provider.d.ts.map +1 -1
- package/dist/commands/doctor-provider.js +15 -20
- package/dist/commands/doctor-provider.js.map +1 -1
- package/dist/commands/sdk-session-cli.d.ts +24 -0
- package/dist/commands/sdk-session-cli.d.ts.map +1 -0
- package/dist/commands/sdk-session-cli.js +225 -0
- package/dist/commands/sdk-session-cli.js.map +1 -0
- package/dist/core/advisory-judge-model.d.ts +18 -0
- package/dist/core/advisory-judge-model.d.ts.map +1 -0
- package/dist/core/advisory-judge-model.js +168 -0
- package/dist/core/advisory-judge-model.js.map +1 -0
- package/dist/core/advisory-judge-response.d.ts +8 -0
- package/dist/core/advisory-judge-response.d.ts.map +1 -0
- package/dist/core/advisory-judge-response.js +61 -0
- package/dist/core/advisory-judge-response.js.map +1 -0
- package/dist/core/advisory-judge-types.d.ts +53 -0
- package/dist/core/advisory-judge-types.d.ts.map +1 -0
- package/dist/core/advisory-judge-types.js +2 -0
- package/dist/core/advisory-judge-types.js.map +1 -0
- package/dist/core/advisory-judge.d.ts +8 -0
- package/dist/core/advisory-judge.d.ts.map +1 -0
- package/dist/core/advisory-judge.js +178 -0
- package/dist/core/advisory-judge.js.map +1 -0
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +141 -184
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +28 -4
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction-gate.d.ts +28 -0
- package/dist/core/compaction-gate.d.ts.map +1 -0
- package/dist/core/compaction-gate.js +34 -0
- package/dist/core/compaction-gate.js.map +1 -0
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js +5 -3
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/domain-loadouts.d.ts.map +1 -1
- package/dist/core/domain-loadouts.js +2 -2
- package/dist/core/domain-loadouts.js.map +1 -1
- package/dist/core/durable-goal-checkpoint.d.ts +6 -0
- package/dist/core/durable-goal-checkpoint.d.ts.map +1 -0
- package/dist/core/durable-goal-checkpoint.js +195 -0
- package/dist/core/durable-goal-checkpoint.js.map +1 -0
- package/dist/core/durable-goal-parse.d.ts +3 -0
- package/dist/core/durable-goal-parse.d.ts.map +1 -0
- package/dist/core/durable-goal-parse.js +86 -0
- package/dist/core/durable-goal-parse.js.map +1 -0
- package/dist/core/durable-goal-reducer.d.ts +3 -0
- package/dist/core/durable-goal-reducer.d.ts.map +1 -0
- package/dist/core/durable-goal-reducer.js +175 -0
- package/dist/core/durable-goal-reducer.js.map +1 -0
- package/dist/core/durable-goal-store.d.ts +19 -0
- package/dist/core/durable-goal-store.d.ts.map +1 -0
- package/dist/core/durable-goal-store.js +126 -0
- package/dist/core/durable-goal-store.js.map +1 -0
- package/dist/core/durable-goal.d.ts +92 -0
- package/dist/core/durable-goal.d.ts.map +1 -0
- package/dist/core/durable-goal.js +62 -0
- package/dist/core/durable-goal.js.map +1 -0
- package/dist/core/extensions/builtin/goal-controller.d.ts +3 -0
- package/dist/core/extensions/builtin/goal-controller.d.ts.map +1 -0
- package/dist/core/extensions/builtin/goal-controller.js +125 -0
- package/dist/core/extensions/builtin/goal-controller.js.map +1 -0
- package/dist/core/extensions/builtin/identical-loop.d.ts +3 -0
- package/dist/core/extensions/builtin/identical-loop.d.ts.map +1 -0
- package/dist/core/extensions/builtin/identical-loop.js +31 -0
- package/dist/core/extensions/builtin/identical-loop.js.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset.d.ts +3 -0
- package/dist/core/extensions/builtin/prompt-preset.d.ts.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset.js +13 -0
- package/dist/core/extensions/builtin/prompt-preset.js.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-repair.d.ts +3 -0
- package/dist/core/extensions/builtin/tool-pair-repair.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-pair-repair.js +12 -0
- package/dist/core/extensions/builtin/tool-pair-repair.js.map +1 -0
- package/dist/core/extensions/types.d.ts +6 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/goal-continuation.d.ts +13 -0
- package/dist/core/goal-continuation.d.ts.map +1 -0
- package/dist/core/goal-continuation.js +10 -0
- package/dist/core/goal-continuation.js.map +1 -0
- package/dist/core/grok-harness.d.ts +1 -1
- package/dist/core/grok-harness.d.ts.map +1 -1
- package/dist/core/grok-harness.js +1 -1
- package/dist/core/grok-harness.js.map +1 -1
- package/dist/core/grok-playbook.d.ts +1 -1
- package/dist/core/grok-playbook.d.ts.map +1 -1
- package/dist/core/grok-playbook.js +1 -1
- package/dist/core/grok-playbook.js.map +1 -1
- package/dist/core/identical-loop.d.ts +15 -0
- package/dist/core/identical-loop.d.ts.map +1 -0
- package/dist/core/identical-loop.js +34 -0
- package/dist/core/identical-loop.js.map +1 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-prompt-policy.d.ts +11 -0
- package/dist/core/model-prompt-policy.d.ts.map +1 -0
- package/dist/core/model-prompt-policy.js +17 -0
- package/dist/core/model-prompt-policy.js.map +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +16 -4
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +2 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/prompt-budget.d.ts +21 -0
- package/dist/core/prompt-budget.d.ts.map +1 -0
- package/dist/core/prompt-budget.js +51 -0
- package/dist/core/prompt-budget.js.map +1 -0
- package/dist/core/prompt-cache.d.ts +14 -0
- package/dist/core/prompt-cache.d.ts.map +1 -0
- package/dist/core/prompt-cache.js +16 -0
- package/dist/core/prompt-cache.js.map +1 -0
- package/dist/core/prompt-preset.d.ts +8 -0
- package/dist/core/prompt-preset.d.ts.map +1 -0
- package/dist/core/prompt-preset.js +56 -0
- package/dist/core/prompt-preset.js.map +1 -0
- package/dist/core/provider-resilience.d.ts +23 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +34 -1
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/provider-retry.d.ts +29 -0
- package/dist/core/provider-retry.d.ts.map +1 -0
- package/dist/core/provider-retry.js +49 -0
- package/dist/core/provider-retry.js.map +1 -0
- package/dist/core/provider-usage.d.ts +2 -1
- package/dist/core/provider-usage.d.ts.map +1 -1
- package/dist/core/provider-usage.js +40 -6
- package/dist/core/provider-usage.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +32 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sandbox/backend.d.ts +12 -0
- package/dist/core/sandbox/backend.d.ts.map +1 -1
- package/dist/core/sandbox/backend.js +52 -42
- package/dist/core/sandbox/backend.js.map +1 -1
- package/dist/core/sandbox/policy.d.ts +2 -0
- package/dist/core/sandbox/policy.d.ts.map +1 -1
- package/dist/core/sandbox/policy.js +2 -1
- package/dist/core/sandbox/policy.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-system-prompt.d.ts +28 -0
- package/dist/core/session-system-prompt.d.ts.map +1 -0
- package/dist/core/session-system-prompt.js +37 -0
- package/dist/core/session-system-prompt.js.map +1 -0
- package/dist/core/strict-record.d.ts +2 -0
- package/dist/core/strict-record.d.ts.map +1 -0
- package/dist/core/strict-record.js +9 -0
- package/dist/core/strict-record.js.map +1 -0
- package/dist/core/subagent-orchestration.d.ts.map +1 -1
- package/dist/core/subagent-orchestration.js +4 -2
- package/dist/core/subagent-orchestration.js.map +1 -1
- package/dist/core/tool-pair-repair.d.ts +11 -0
- package/dist/core/tool-pair-repair.d.ts.map +1 -0
- package/dist/core/tool-pair-repair.js +38 -0
- package/dist/core/tool-pair-repair.js.map +1 -0
- package/dist/core/tools/artifact-spill.d.ts +13 -0
- package/dist/core/tools/artifact-spill.d.ts.map +1 -0
- package/dist/core/tools/artifact-spill.js +26 -0
- package/dist/core/tools/artifact-spill.js.map +1 -0
- package/dist/core/tools/bash.d.ts +7 -4
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +8 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/diagnostics.d.ts.map +1 -1
- package/dist/core/tools/diagnostics.js +8 -2
- package/dist/core/tools/diagnostics.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/read.d.ts +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +10 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +2 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/guardrails/evidence-protocol.d.ts +10 -0
- package/dist/guardrails/evidence-protocol.d.ts.map +1 -0
- package/dist/guardrails/evidence-protocol.js +34 -0
- package/dist/guardrails/evidence-protocol.js.map +1 -0
- package/dist/guardrails/evidence-system.d.ts +3 -1
- package/dist/guardrails/evidence-system.d.ts.map +1 -1
- package/dist/guardrails/evidence-system.js +22 -14
- package/dist/guardrails/evidence-system.js.map +1 -1
- package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -1
- package/dist/guardrails/replay-ledger-lock-owner.js +3 -1
- package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -1
- package/dist/guardrails/replay-payload-hash.d.ts +6 -0
- package/dist/guardrails/replay-payload-hash.d.ts.map +1 -0
- package/dist/guardrails/replay-payload-hash.js +24 -0
- package/dist/guardrails/replay-payload-hash.js.map +1 -0
- package/dist/index.d.ts +20 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +8 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +14 -3
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/types/evidence.d.ts +11 -3
- package/dist/types/evidence.d.ts.map +1 -1
- package/dist/types/evidence.js.map +1 -1
- package/docs/adaptorch-preview-spec.md +1 -1
- package/docs/adaptorch-preview.md +2 -2
- package/docs/book-to-skill.md +79 -0
- package/docs/compaction.md +10 -0
- package/docs/containerization.md +9 -0
- package/docs/docs.json +18 -2
- package/docs/environment-variables.md +11 -4
- package/docs/extensions.md +14 -1
- package/docs/grok-harness.md +35 -12
- package/docs/index.md +9 -5
- package/docs/loadout-domains/grok-harness.md +2 -2
- package/docs/models.md +21 -8
- package/docs/provider-resilience.md +42 -55
- package/docs/providers.md +13 -4
- package/docs/run-protocol.md +104 -0
- package/docs/sdk.md +111 -5
- package/docs/security.md +22 -32
- package/docs/sessions.md +20 -0
- package/docs/settings.md +2 -0
- package/docs/skills.md +9 -1
- package/docs/usage.md +38 -16
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +6 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +37 -15
- package/package.json +8 -5
- package/dist/core/context-budget-compressors.d.ts +0 -50
- package/dist/core/context-budget-compressors.d.ts.map +0 -1
- package/dist/core/context-budget-compressors.js +0 -166
- package/dist/core/context-budget-compressors.js.map +0 -1
- package/dist/core/extensions/loader.js.bak-sharedjiti-20260812-111246 +0 -553
|
@@ -16,7 +16,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
16
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
17
|
import { basename, dirname, join as joinPath } from "node:path";
|
|
18
18
|
import { isDeepStrictEqual } from "node:util";
|
|
19
|
-
import { getVisionRouteModel
|
|
19
|
+
import { getVisionRouteModel } from "omk-agent-core";
|
|
20
20
|
import { clampThinkingLevel, cleanupSessionResources, deriveContextPromptCacheKey, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, resetApiProviders, streamSimple, } from "omk-ai";
|
|
21
21
|
import { APP_NAME, VERSION } from "../config.js";
|
|
22
22
|
import { theme } from "../modes/interactive/theme/theme.js";
|
|
@@ -32,6 +32,7 @@ import { getCompactionHeadroomThreshold } from "./compaction/compaction.js";
|
|
|
32
32
|
import { createCompactionHysteresisConfig, createCompactionHysteresisState, stepCompactionHysteresis, } from "./compaction/hysteresis.js";
|
|
33
33
|
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateProjectedContextTokens, generateBranchSummary, prepareCompaction, resolveCompactionModel, } from "./compaction/index.js";
|
|
34
34
|
import { compactionEmitWillRetry } from "./compaction/resume-policy.js";
|
|
35
|
+
import { isSessionModelOverflow, shouldSkipCompactionCheck } from "./compaction-gate.js";
|
|
35
36
|
import { estimateToolResultReserve, } from "./context-budget-reserved-tokens.js";
|
|
36
37
|
import { createDiskContextBudgetCacheProviderV2, DiskContextBudgetCacheProviderV2, } from "./context-budget-v2-cache-disk.js";
|
|
37
38
|
import { applyContextCacheInvalidation, createContextCacheInvalidationSnapshot, } from "./context-budget-v2-cache-invalidation.js";
|
|
@@ -50,6 +51,8 @@ import { buildRuntimeProvenance } from "./runtime-provenance.js";
|
|
|
50
51
|
import { TurnMetricsSink } from "./turn-metrics.js";
|
|
51
52
|
/** Values that disable a session-level feature through its environment variable. */
|
|
52
53
|
const DISABLED_ENV_VALUES = new Set(["0", "false", "off", "disable", "disabled"]);
|
|
54
|
+
const MAX_OVERFLOW_RECOVERY_ATTEMPTS = 2;
|
|
55
|
+
const OVERFLOW_RECOVERY_EMERGENCY_TOKENS = 4_096;
|
|
53
56
|
function isDisabledEnvValue(value) {
|
|
54
57
|
return value !== undefined && DISABLED_ENV_VALUES.has(value.trim().toLowerCase());
|
|
55
58
|
}
|
|
@@ -68,9 +71,13 @@ function firstTextContent(result) {
|
|
|
68
71
|
return undefined;
|
|
69
72
|
}
|
|
70
73
|
import { redactCredentialShapedContent } from "./compaction/transaction.js";
|
|
74
|
+
import { selectContextFilesForModel } from "./model-prompt-policy.js";
|
|
71
75
|
import { findExactModelReferenceMatch } from "./model-resolver.js";
|
|
76
|
+
import { computePromptTokenBudget } from "./prompt-budget.js";
|
|
77
|
+
import { classifyPromptCacheTransition } from "./prompt-cache.js";
|
|
72
78
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
73
|
-
import {
|
|
79
|
+
import { isQuotaExhaustionMessage, isStickySafetyModel, pickFailoverCandidate, resolveProviderResilience, shouldEjectStickySafetyModel, shouldHonorSafetyFailover, stickySafetyBlockMessage, } from "./provider-resilience.js";
|
|
80
|
+
import { computeRetryDelayMs, failoverModelKey, isFailoverTriggerError, isRetryableAssistantError, nextRetryAttempt, retryBudgetForAssistantError, } from "./provider-retry.js";
|
|
74
81
|
import { getBiasStepsForCell, parseRouterBiasSnapshot } from "./reasoning-router-bias.js";
|
|
75
82
|
import { classifyTaskV4, deriveRouterFeedbackFeaturesV4, resolveThinkingLevelV4WithUncertainty, TASK_CLASS_THINKING_LEVELS_V4, } from "./reasoning-router-v4.js";
|
|
76
83
|
import { redactSensitiveText, redactSensitiveTextForced } from "./redaction.js";
|
|
@@ -82,6 +89,7 @@ import { SessionBashService } from "./session-bash-service.js";
|
|
|
82
89
|
import { SessionCompactionService } from "./session-compaction-service.js";
|
|
83
90
|
import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
|
|
84
91
|
import { acquireSessionOwnerLeaseSync } from "./session-owner-lease.js";
|
|
92
|
+
import { assembleSessionSystemPrompt } from "./session-system-prompt.js";
|
|
85
93
|
import { classifySessionTermination, } from "./session-termination.js";
|
|
86
94
|
import { createSyntheticSourceInfo } from "./source-info.js";
|
|
87
95
|
import { buildSystemPromptPlan } from "./system-prompt.js";
|
|
@@ -302,7 +310,7 @@ export class AgentSession {
|
|
|
302
310
|
// Compaction state
|
|
303
311
|
_compactionAbortController = undefined;
|
|
304
312
|
_autoCompactionAbortController = undefined;
|
|
305
|
-
|
|
313
|
+
_overflowRecoveryAttempts = 0;
|
|
306
314
|
_thresholdCompactionEmergency = false;
|
|
307
315
|
_compactionHysteresisState = createCompactionHysteresisState();
|
|
308
316
|
// Branch summarization state
|
|
@@ -313,6 +321,8 @@ export class AgentSession {
|
|
|
313
321
|
// v10.3-Ω: models that already refused/failed this turn — failover must advance,
|
|
314
322
|
// not re-pick the same candidate (claude-opus-5 → grok → grok → grok loop fix).
|
|
315
323
|
_refusedModels = new Set();
|
|
324
|
+
/** True when `--model` / `--provider` pinned this process to one model. */
|
|
325
|
+
_modelPinned;
|
|
316
326
|
// Bash execution state
|
|
317
327
|
_bashService;
|
|
318
328
|
_compactionService;
|
|
@@ -381,6 +391,7 @@ export class AgentSession {
|
|
|
381
391
|
this.sessionManager = config.sessionManager;
|
|
382
392
|
this.settingsManager = config.settingsManager;
|
|
383
393
|
this._scopedModels = config.scopedModels ?? [];
|
|
394
|
+
this._modelPinned = config.modelPinned === true;
|
|
384
395
|
this._resourceLoader = config.resourceLoader;
|
|
385
396
|
this._customTools = config.customTools ?? [];
|
|
386
397
|
this._cwd = config.cwd;
|
|
@@ -585,12 +596,15 @@ export class AgentSession {
|
|
|
585
596
|
if (/stop_reason\s*=\s*(refusal|sensitive)|content\/safety stop|safety stop|provider\.refusal|kind=provider_refusal/i.test(text)) {
|
|
586
597
|
return { area: "provider", code: "refusal" };
|
|
587
598
|
}
|
|
599
|
+
// Quota/billing exhaustion is checked BEFORE the generic 401/403 auth
|
|
600
|
+
// patterns: "403 ... usage limit for this billing cycle" is transient per
|
|
601
|
+
// cycle and must fail over, not terminate the turn as an auth error.
|
|
602
|
+
if (isQuotaExhaustionMessage(text) || /rate.?limit|too many requests|429/i.test(text)) {
|
|
603
|
+
return { area: "provider", code: "rate_limit" };
|
|
604
|
+
}
|
|
588
605
|
if (/auth|unauthori[sz]ed|forbidden|invalid.?api.?key|no api key|401|403|\/login/i.test(text)) {
|
|
589
606
|
return { area: "provider", code: "auth" };
|
|
590
607
|
}
|
|
591
|
-
if (/rate.?limit|too many requests|429|quota|available balance|billing/i.test(text)) {
|
|
592
|
-
return { area: "provider", code: "rate_limit" };
|
|
593
|
-
}
|
|
594
608
|
// Kimi/K3 + OpenAI-compat: orphan tool results after dropped error assistants.
|
|
595
609
|
// Sanitize-and-retry (transform-messages drops orphans), not a hard tool fatal.
|
|
596
610
|
if (/tool_call_id\s+is\s+not\s+found|tool_call_id\s+not\s+found|unknown\s+tool_call_id/i.test(text)) {
|
|
@@ -609,6 +623,9 @@ export class AgentSession {
|
|
|
609
623
|
if (!this.model || /no model|model selected|model is required/i.test(message)) {
|
|
610
624
|
return { area: "configuration", code: "invalid" };
|
|
611
625
|
}
|
|
626
|
+
if (isQuotaExhaustionMessage(message)) {
|
|
627
|
+
return { area: "provider", code: "rate_limit" };
|
|
628
|
+
}
|
|
612
629
|
if (/auth|api key|unauthori[sz]ed|forbidden|401|403|\/login/i.test(message)) {
|
|
613
630
|
return { area: "provider", code: "auth" };
|
|
614
631
|
}
|
|
@@ -966,7 +983,7 @@ export class AgentSession {
|
|
|
966
983
|
// When a user message starts, check if it's from either queue and remove it BEFORE emitting
|
|
967
984
|
// This ensures the UI sees the updated queue state
|
|
968
985
|
if (event.type === "message_start" && event.message.role === "user") {
|
|
969
|
-
this.
|
|
986
|
+
this._overflowRecoveryAttempts = 0;
|
|
970
987
|
const messageText = this._getUserMessageText(event.message);
|
|
971
988
|
if (messageText) {
|
|
972
989
|
// Check steering queue first
|
|
@@ -1017,7 +1034,7 @@ export class AgentSession {
|
|
|
1017
1034
|
this._lastAssistantMessage = finalizedEvent.message;
|
|
1018
1035
|
const assistantMsg = finalizedEvent.message;
|
|
1019
1036
|
if (assistantMsg.stopReason !== "error") {
|
|
1020
|
-
this.
|
|
1037
|
+
this._overflowRecoveryAttempts = 0;
|
|
1021
1038
|
}
|
|
1022
1039
|
// Reset retry counter immediately on successful assistant response
|
|
1023
1040
|
// This prevents accumulation across multiple LLM calls within a turn
|
|
@@ -1035,13 +1052,14 @@ export class AgentSession {
|
|
|
1035
1052
|
};
|
|
1036
1053
|
_willRetryAfterAgentEnd(event) {
|
|
1037
1054
|
const settings = this.settingsManager.getRetrySettings();
|
|
1038
|
-
if (!settings.enabled
|
|
1055
|
+
if (!settings.enabled)
|
|
1039
1056
|
return false;
|
|
1040
|
-
}
|
|
1041
1057
|
for (let i = event.messages.length - 1; i >= 0; i--) {
|
|
1042
1058
|
const message = event.messages[i];
|
|
1043
1059
|
if (message.role === "assistant") {
|
|
1044
|
-
|
|
1060
|
+
const assistant = message;
|
|
1061
|
+
const maxRetries = retryBudgetForAssistantError(assistant, settings.maxRetries);
|
|
1062
|
+
return this._retryAttempt < maxRetries && this._isRetryableError(assistant);
|
|
1045
1063
|
}
|
|
1046
1064
|
}
|
|
1047
1065
|
return false;
|
|
@@ -1546,7 +1564,7 @@ export class AgentSession {
|
|
|
1546
1564
|
}
|
|
1547
1565
|
return undefined;
|
|
1548
1566
|
}
|
|
1549
|
-
_getContextBudgetOptions(queryContext = this._extractCurrentQuery()) {
|
|
1567
|
+
_getContextBudgetOptions(queryContext = this._extractCurrentQuery(), model = this.model) {
|
|
1550
1568
|
const contextGovernorOverride = process.env.OMK_CONTEXT_GOVERNOR;
|
|
1551
1569
|
if (contextGovernorOverride === "0") {
|
|
1552
1570
|
return undefined;
|
|
@@ -1554,56 +1572,24 @@ export class AgentSession {
|
|
|
1554
1572
|
if (contextGovernorOverride !== "1" && !this.settingsManager.getContextBudgetEnabled()) {
|
|
1555
1573
|
return undefined;
|
|
1556
1574
|
}
|
|
1557
|
-
const contextWindow =
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
this._computeResponseReserve(contextWindow);
|
|
1569
|
-
}
|
|
1570
|
-
else if (contextWindow > 0) {
|
|
1571
|
-
// Dynamic: derive from model contextWindow
|
|
1572
|
-
const envPromptRatio = parsePositiveFloatEnv("OMK_CONTEXT_GOVERNOR_PROMPT_RATIO");
|
|
1573
|
-
const envResponseRatio = parsePositiveFloatEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RATIO");
|
|
1574
|
-
responseReserveTokens =
|
|
1575
|
-
parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RESERVE_TOKENS") ??
|
|
1576
|
-
this._computeResponseReserve(contextWindow, envResponseRatio);
|
|
1577
|
-
const safetyMargin = Math.floor(contextWindow * SAFETY_MARGIN_RATIO);
|
|
1578
|
-
if (envPromptRatio !== undefined && envPromptRatio > 0 && envPromptRatio < 1) {
|
|
1579
|
-
maxPromptTokens = Math.floor(contextWindow * envPromptRatio);
|
|
1580
|
-
}
|
|
1581
|
-
else {
|
|
1582
|
-
// Default: contextWindow minus reserves and safety margin
|
|
1583
|
-
maxPromptTokens = contextWindow - responseReserveTokens - safetyMargin;
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
else {
|
|
1587
|
-
// No model info — legacy fallback
|
|
1588
|
-
maxPromptTokens = LEGACY_MAX_PROMPT_TOKENS;
|
|
1589
|
-
responseReserveTokens =
|
|
1590
|
-
parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RESERVE_TOKENS") ?? LEGACY_RESPONSE_RESERVE_TOKENS;
|
|
1591
|
-
}
|
|
1575
|
+
const contextWindow = model?.contextWindow ?? 0;
|
|
1576
|
+
const budget = computePromptTokenBudget({
|
|
1577
|
+
contextWindow,
|
|
1578
|
+
modelMaxTokens: model?.maxTokens,
|
|
1579
|
+
envMaxPromptTokens: parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_MAX_PROMPT_TOKENS"),
|
|
1580
|
+
envResponseReserveTokens: parsePositiveIntegerEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RESERVE_TOKENS"),
|
|
1581
|
+
envPromptRatio: parsePositiveFloatEnv("OMK_CONTEXT_GOVERNOR_PROMPT_RATIO"),
|
|
1582
|
+
envResponseRatio: parsePositiveFloatEnv("OMK_CONTEXT_GOVERNOR_RESPONSE_RATIO"),
|
|
1583
|
+
});
|
|
1584
|
+
const maxPromptTokens = budget.maxPromptTokens;
|
|
1585
|
+
const responseReserveTokens = budget.responseReserveTokens;
|
|
1592
1586
|
const cacheProvider = this._contextBudgetCacheProvider ?? this._createContextBudgetCacheProvider();
|
|
1593
1587
|
cacheProvider.setInvalidationSnapshot?.(this._contextCacheInvalidationSnapshot);
|
|
1594
1588
|
this._contextBudgetCacheProvider = cacheProvider;
|
|
1595
|
-
// Enforce floor
|
|
1596
|
-
if (maxPromptTokens < MIN_PROMPT_TOKENS) {
|
|
1597
|
-
maxPromptTokens = MIN_PROMPT_TOKENS;
|
|
1598
|
-
}
|
|
1599
|
-
// Ensure responseReserve does not exceed maxPromptTokens
|
|
1600
|
-
if (responseReserveTokens >= maxPromptTokens) {
|
|
1601
|
-
responseReserveTokens = Math.max(Math.floor(maxPromptTokens / 4), LEGACY_RESPONSE_RESERVE_TOKENS);
|
|
1602
|
-
}
|
|
1603
1589
|
return {
|
|
1604
1590
|
maxPromptTokens,
|
|
1605
1591
|
responseReserveTokens,
|
|
1606
|
-
modelId:
|
|
1592
|
+
modelId: model?.id ?? "unknown",
|
|
1607
1593
|
tokenizerMode: parseTokenizerModeEnv(process.env.OMK_CONTEXT_GOVERNOR_TOKENIZER),
|
|
1608
1594
|
activeSkillNames: parseCommaSeparatedEnv(process.env.OMK_CONTEXT_GOVERNOR_ACTIVE_SKILLS),
|
|
1609
1595
|
queryContext,
|
|
@@ -1614,15 +1600,6 @@ export class AgentSession {
|
|
|
1614
1600
|
* Compute responseReserveTokens from contextWindow.
|
|
1615
1601
|
* Prefers the model's own maxTokens when available, otherwise uses a ratio.
|
|
1616
1602
|
*/
|
|
1617
|
-
_computeResponseReserve(contextWindow, overrideRatio) {
|
|
1618
|
-
// Prefer model's maxTokens (actual output limit) if available and reasonable
|
|
1619
|
-
const modelMaxTokens = this.model?.maxTokens;
|
|
1620
|
-
if (modelMaxTokens !== undefined && modelMaxTokens > 0 && modelMaxTokens < contextWindow) {
|
|
1621
|
-
return modelMaxTokens;
|
|
1622
|
-
}
|
|
1623
|
-
const ratio = overrideRatio ?? RESPONSE_RESERVE_RATIO;
|
|
1624
|
-
return Math.max(Math.floor(contextWindow * ratio), LEGACY_RESPONSE_RESERVE_TOKENS);
|
|
1625
|
-
}
|
|
1626
1603
|
_recordPromptCachePlan(reason) {
|
|
1627
1604
|
const boundary = this.agent.state.systemPromptCacheBoundary;
|
|
1628
1605
|
const scope = this.model ? `${this.model.provider}/${this.model.id}` : "unknown";
|
|
@@ -1633,60 +1610,40 @@ export class AgentSession {
|
|
|
1633
1610
|
messages: [],
|
|
1634
1611
|
tools: this.agent.state.tools,
|
|
1635
1612
|
}, scope);
|
|
1636
|
-
|
|
1613
|
+
const transition = classifyPromptCacheTransition(this._promptCacheKey, nextKey);
|
|
1614
|
+
if (transition.kind === "bypass") {
|
|
1637
1615
|
this._promptCacheBoundaryBypasses += 1;
|
|
1638
1616
|
this._promptCacheStablePrefixCharacters = 0;
|
|
1639
|
-
if (
|
|
1617
|
+
if (transition.recordBreak) {
|
|
1640
1618
|
this._promptCacheLastBreakReason = reason;
|
|
1641
1619
|
}
|
|
1642
1620
|
this._promptCacheKey = undefined;
|
|
1643
1621
|
return;
|
|
1644
1622
|
}
|
|
1645
|
-
if (
|
|
1623
|
+
if (transition.kind === "changed") {
|
|
1646
1624
|
this._promptCacheKeyChanges += 1;
|
|
1647
1625
|
this._promptCacheLastBreakReason = reason;
|
|
1648
1626
|
}
|
|
1649
1627
|
this._promptCacheKey = nextKey;
|
|
1650
1628
|
this._promptCacheStablePrefixCharacters = boundary ?? 0;
|
|
1651
1629
|
}
|
|
1652
|
-
_rebuildSystemPrompt(toolNames) {
|
|
1653
|
-
const
|
|
1654
|
-
const toolSnippets = {};
|
|
1655
|
-
const promptGuidelines = [];
|
|
1656
|
-
for (const name of validToolNames) {
|
|
1657
|
-
const snippet = this._toolPromptSnippets.get(name);
|
|
1658
|
-
if (snippet) {
|
|
1659
|
-
toolSnippets[name] = snippet;
|
|
1660
|
-
}
|
|
1661
|
-
const toolGuidelines = this._toolPromptGuidelines.get(name);
|
|
1662
|
-
if (toolGuidelines) {
|
|
1663
|
-
promptGuidelines.push(...toolGuidelines);
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
const loaderSystemPrompt = this._resourceLoader.getSystemPrompt();
|
|
1667
|
-
const loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();
|
|
1668
|
-
const grokAppend = grokPlaybookAppendForProvider(this.model?.provider);
|
|
1669
|
-
const appendParts = [...loaderAppendSystemPrompt];
|
|
1670
|
-
if (grokAppend) {
|
|
1671
|
-
appendParts.push(grokAppend);
|
|
1672
|
-
}
|
|
1673
|
-
const appendSystemPrompt = appendParts.length > 0 ? appendParts.join("\n\n") : undefined;
|
|
1674
|
-
const loadedSkills = this._resourceLoader.getSkills().skills;
|
|
1675
|
-
const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
|
|
1676
|
-
this._baseSystemPromptOptions = {
|
|
1630
|
+
_rebuildSystemPrompt(toolNames, model = this.model) {
|
|
1631
|
+
const assembled = assembleSessionSystemPrompt({
|
|
1677
1632
|
cwd: this._cwd,
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1633
|
+
toolNames,
|
|
1634
|
+
hasTool: (name) => this._toolRegistry.has(name),
|
|
1635
|
+
toolPromptSnippets: this._toolPromptSnippets,
|
|
1636
|
+
toolPromptGuidelines: this._toolPromptGuidelines,
|
|
1637
|
+
customPrompt: this._resourceLoader.getSystemPrompt(),
|
|
1638
|
+
appendSystemPrompt: this._resourceLoader.getAppendSystemPrompt(),
|
|
1639
|
+
providerAppend: grokPlaybookAppendForProvider(model?.provider),
|
|
1640
|
+
skills: this._resourceLoader.getSkills().skills,
|
|
1641
|
+
contextFiles: selectContextFilesForModel(this._resourceLoader.getAgentsFiles().agentsFiles, model),
|
|
1642
|
+
contextBudget: this._getContextBudgetOptions(undefined, model),
|
|
1643
|
+
});
|
|
1644
|
+
this._baseSystemPromptOptions = assembled.options;
|
|
1645
|
+
this._baseSystemPromptCacheBoundary = assembled.cacheBoundary;
|
|
1646
|
+
return assembled.prompt;
|
|
1690
1647
|
}
|
|
1691
1648
|
// =========================================================================
|
|
1692
1649
|
// Prompting
|
|
@@ -2226,16 +2183,25 @@ export class AgentSession {
|
|
|
2226
2183
|
if (!this._modelRegistry.hasConfiguredAuth(model)) {
|
|
2227
2184
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
2228
2185
|
}
|
|
2229
|
-
// Root-level: block sticky safety models (Fable) unless
|
|
2186
|
+
// Root-level: block sticky safety models (Fable) unless the user pinned `--model`.
|
|
2230
2187
|
const resilience = resolveProviderResilience(this.settingsManager.getProviderResilienceSettings());
|
|
2231
|
-
if (
|
|
2188
|
+
if (shouldEjectStickySafetyModel({
|
|
2189
|
+
blockStickySafetyModels: resilience.blockStickySafetyModels,
|
|
2190
|
+
modelPinned: this._modelPinned,
|
|
2191
|
+
}) &&
|
|
2192
|
+
isStickySafetyModel(model.id, model.provider)) {
|
|
2232
2193
|
throw new Error(stickySafetyBlockMessage(model.id, model.provider));
|
|
2233
2194
|
}
|
|
2234
2195
|
// Grok OAuth: block selecting Imagine models as the session chat model.
|
|
2235
2196
|
assertTextChatModelForCompletion(model.id, model.provider);
|
|
2236
2197
|
const previousModel = this.model;
|
|
2237
2198
|
const thinkingLevel = this._getThinkingLevelForModelSwitch();
|
|
2199
|
+
const nextSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames(), model);
|
|
2238
2200
|
this.agent.state.model = model;
|
|
2201
|
+
this._baseSystemPrompt = nextSystemPrompt;
|
|
2202
|
+
this.agent.state.systemPrompt = this._baseSystemPrompt;
|
|
2203
|
+
this.agent.state.systemPromptCacheBoundary = this._baseSystemPromptCacheBoundary;
|
|
2204
|
+
this.agent.state.systemPromptCacheBoundaryBypass = false;
|
|
2239
2205
|
this.sessionManager.appendModelChange(model.provider, model.id);
|
|
2240
2206
|
this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
|
|
2241
2207
|
// Re-clamp thinking level for new model's capabilities
|
|
@@ -2949,46 +2915,41 @@ export class AgentSession {
|
|
|
2949
2915
|
*/
|
|
2950
2916
|
async _checkCompaction(assistantMessage, skipAbortedCheck = true) {
|
|
2951
2917
|
const settings = this.settingsManager.getCompactionSettings();
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
// Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
|
|
2955
|
-
if (skipAbortedCheck && assistantMessage.stopReason === "aborted")
|
|
2956
|
-
return false;
|
|
2957
|
-
const contextWindow = this.model?.contextWindow ?? 0;
|
|
2958
|
-
// Skip overflow check if the message came from a different model.
|
|
2959
|
-
// This handles the case where user switched from a smaller-context model (e.g. opus)
|
|
2960
|
-
// to a larger-context model (e.g. codex) - the overflow error from the old model
|
|
2961
|
-
// shouldn't trigger compaction for the new model.
|
|
2962
|
-
const sameModel = this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
|
|
2963
|
-
// Image-bearing turns are auto-routed to a vision-capable model (gpt-5.6-luna)
|
|
2964
|
-
// while the session model stays text-only (deepseek). An overflow from that
|
|
2965
|
-
// routed model must still count as the session's own overflow: compaction is
|
|
2966
|
-
// the fix, and the raw context_length_exceeded error must never reach the user.
|
|
2967
|
-
const visionRouteOverflow = this.model != null &&
|
|
2968
|
-
!(this.model.input ?? []).includes("image") &&
|
|
2969
|
-
isVisionRouteModel({ provider: assistantMessage.provider, id: assistantMessage.model });
|
|
2970
|
-
// Skip compaction checks if this assistant message is older than the latest
|
|
2971
|
-
// compaction boundary. This prevents a stale pre-compaction usage/error
|
|
2972
|
-
// from retriggering compaction on the first prompt after compaction.
|
|
2918
|
+
// Stale pre-compaction usage/errors must not retrigger compaction on the
|
|
2919
|
+
// first prompt after one finished.
|
|
2973
2920
|
const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
|
|
2974
|
-
|
|
2975
|
-
|
|
2921
|
+
if (shouldSkipCompactionCheck({
|
|
2922
|
+
enabled: settings.enabled,
|
|
2923
|
+
skipAbortedCheck,
|
|
2924
|
+
stopReason: assistantMessage.stopReason,
|
|
2925
|
+
messageTimestamp: assistantMessage.timestamp,
|
|
2926
|
+
latestCompactionTimestamp: compactionEntry ? new Date(compactionEntry.timestamp).getTime() : undefined,
|
|
2927
|
+
})) {
|
|
2976
2928
|
return false;
|
|
2977
2929
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2930
|
+
const contextWindow = this.model?.contextWindow ?? 0;
|
|
2931
|
+
// Case 1: Overflow - LLM returned context overflow error. Only counts when
|
|
2932
|
+
// the failing message belongs to the session model — or to the auto-routed
|
|
2933
|
+
// vision model while the session model is text-only.
|
|
2934
|
+
if (isSessionModelOverflow({
|
|
2935
|
+
message: assistantMessage,
|
|
2936
|
+
contextWindow,
|
|
2937
|
+
sessionProvider: this.model?.provider,
|
|
2938
|
+
sessionModelId: this.model?.id,
|
|
2939
|
+
sessionInputs: this.model?.input,
|
|
2940
|
+
})) {
|
|
2941
|
+
if (this._overflowRecoveryAttempts >= MAX_OVERFLOW_RECOVERY_ATTEMPTS) {
|
|
2981
2942
|
this._emit({
|
|
2982
2943
|
type: "compaction_end",
|
|
2983
2944
|
reason: "overflow",
|
|
2984
2945
|
result: undefined,
|
|
2985
2946
|
aborted: false,
|
|
2986
2947
|
willRetry: false,
|
|
2987
|
-
errorMessage: "Context overflow recovery failed after
|
|
2948
|
+
errorMessage: "Context overflow recovery failed after two staged compact-and-retry attempts. Reduce the latest input or switch to a model with a larger effective context window.",
|
|
2988
2949
|
});
|
|
2989
2950
|
return false;
|
|
2990
2951
|
}
|
|
2991
|
-
this.
|
|
2952
|
+
this._overflowRecoveryAttempts++;
|
|
2992
2953
|
// Remove the error message from agent state (it IS saved to session for history,
|
|
2993
2954
|
// but we don't want it in context for the retry)
|
|
2994
2955
|
const messages = this.agent.state.messages;
|
|
@@ -3026,11 +2987,24 @@ export class AgentSession {
|
|
|
3026
2987
|
}
|
|
3027
2988
|
return false;
|
|
3028
2989
|
}
|
|
2990
|
+
_overflowCompactionSettings(settings, attempt) {
|
|
2991
|
+
if (attempt < MAX_OVERFLOW_RECOVERY_ATTEMPTS)
|
|
2992
|
+
return settings;
|
|
2993
|
+
return {
|
|
2994
|
+
...settings,
|
|
2995
|
+
reserveTokens: Math.min(settings.reserveTokens, OVERFLOW_RECOVERY_EMERGENCY_TOKENS),
|
|
2996
|
+
reservedOutputTokens: Math.min(settings.reservedOutputTokens ?? settings.reserveTokens, OVERFLOW_RECOVERY_EMERGENCY_TOKENS),
|
|
2997
|
+
keepRecentTokens: Math.min(settings.keepRecentTokens, OVERFLOW_RECOVERY_EMERGENCY_TOKENS),
|
|
2998
|
+
};
|
|
2999
|
+
}
|
|
3029
3000
|
/**
|
|
3030
3001
|
* Internal: Run auto-compaction with events.
|
|
3031
3002
|
*/
|
|
3032
3003
|
async _runAutoCompaction(reason, willRetry, emergency = reason === "overflow" || this._thresholdCompactionEmergency) {
|
|
3033
|
-
const
|
|
3004
|
+
const configuredSettings = this.settingsManager.getCompactionSettings();
|
|
3005
|
+
const settings = reason === "overflow"
|
|
3006
|
+
? this._overflowCompactionSettings(configuredSettings, this._overflowRecoveryAttempts)
|
|
3007
|
+
: configuredSettings;
|
|
3034
3008
|
this._emit({ type: "compaction_start", reason });
|
|
3035
3009
|
this._autoCompactionAbortController = new AbortController();
|
|
3036
3010
|
try {
|
|
@@ -3666,25 +3640,12 @@ export class AgentSession {
|
|
|
3666
3640
|
// =========================================================================
|
|
3667
3641
|
// Auto-Retry
|
|
3668
3642
|
// =========================================================================
|
|
3669
|
-
_isNonRetryableProviderLimitError(errorMessage) {
|
|
3670
|
-
return /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(errorMessage);
|
|
3671
|
-
}
|
|
3672
3643
|
/**
|
|
3673
3644
|
* Check if an error is retryable (overloaded, rate limit, server errors).
|
|
3674
3645
|
* Context overflow errors are NOT retryable (handled by compaction instead).
|
|
3675
3646
|
*/
|
|
3676
3647
|
_isRetryableError(message) {
|
|
3677
|
-
|
|
3678
|
-
return false;
|
|
3679
|
-
// Context overflow is handled by compaction, not retry
|
|
3680
|
-
const contextWindow = this.model?.contextWindow ?? 0;
|
|
3681
|
-
if (isContextOverflow(message, contextWindow))
|
|
3682
|
-
return false;
|
|
3683
|
-
const err = message.errorMessage;
|
|
3684
|
-
if (this._isNonRetryableProviderLimitError(err))
|
|
3685
|
-
return false;
|
|
3686
|
-
// Shared contract with provider-resilience.ts (Fable safety stop, K3 orphan tool_call_id, terminated).
|
|
3687
|
-
return isTransientProviderErrorMessage(err);
|
|
3648
|
+
return isRetryableAssistantError(message, this.model?.contextWindow ?? 0);
|
|
3688
3649
|
}
|
|
3689
3650
|
/**
|
|
3690
3651
|
* Retry policy + callbacks shared by compaction and branch-summary summarization calls.
|
|
@@ -3721,8 +3682,12 @@ export class AgentSession {
|
|
|
3721
3682
|
/** Eject sticky safety model at prompt boundary (session resume / leftover default). */
|
|
3722
3683
|
async _ejectStickySafetyModelIfNeeded() {
|
|
3723
3684
|
const resilience = resolveProviderResilience(this.settingsManager.getProviderResilienceSettings());
|
|
3724
|
-
if (!
|
|
3685
|
+
if (!shouldEjectStickySafetyModel({
|
|
3686
|
+
blockStickySafetyModels: resilience.blockStickySafetyModels,
|
|
3687
|
+
modelPinned: this._modelPinned,
|
|
3688
|
+
})) {
|
|
3725
3689
|
return;
|
|
3690
|
+
}
|
|
3726
3691
|
const current = this.model;
|
|
3727
3692
|
if (!current || !isStickySafetyModel(current.id, current.provider))
|
|
3728
3693
|
return;
|
|
@@ -3750,19 +3715,28 @@ export class AgentSession {
|
|
|
3750
3715
|
*/
|
|
3751
3716
|
async _maybeFailoverFromSafetyStop(message) {
|
|
3752
3717
|
const resilience = resolveProviderResilience(this.settingsManager.getProviderResilienceSettings());
|
|
3753
|
-
if (!
|
|
3718
|
+
if (!shouldHonorSafetyFailover({
|
|
3719
|
+
autoFailoverOnSafetyStop: resilience.autoFailoverOnSafetyStop,
|
|
3720
|
+
modelPinned: this._modelPinned,
|
|
3721
|
+
modelId: this.model?.id,
|
|
3722
|
+
provider: this.model?.provider,
|
|
3723
|
+
})) {
|
|
3754
3724
|
return undefined;
|
|
3755
|
-
|
|
3725
|
+
}
|
|
3726
|
+
// Fire on safety-stop FPs AND on billing/quota exhaustion: both mean the
|
|
3727
|
+
// current model cannot finish this turn and same-model retry is useless.
|
|
3728
|
+
if (!isFailoverTriggerError(message.errorMessage)) {
|
|
3756
3729
|
return undefined;
|
|
3730
|
+
}
|
|
3757
3731
|
const current = this.model;
|
|
3758
3732
|
// v10.0-Ω: do NOT gate on isStickySafetyModel(source).
|
|
3759
3733
|
// claude-opus-5 emits the same stop_reason=refusal FP; same-model retry is useless.
|
|
3760
3734
|
// v10.3-Ω: mark the model that just refused so we never re-pick it this turn.
|
|
3761
3735
|
if (current)
|
|
3762
|
-
this._refusedModels.add(
|
|
3736
|
+
this._refusedModels.add(failoverModelKey(current.provider, current.id));
|
|
3763
3737
|
const pick = pickFailoverCandidate(resilience.failoverCandidates, current ? { provider: current.provider, id: current.id } : undefined, (c) => {
|
|
3764
3738
|
// Skip any model that already refused/failed this turn → advance the chain.
|
|
3765
|
-
if (this._refusedModels.has(
|
|
3739
|
+
if (this._refusedModels.has(failoverModelKey(c.provider, c.id)))
|
|
3766
3740
|
return false;
|
|
3767
3741
|
const next = this._modelRegistry.find(c.provider, c.id);
|
|
3768
3742
|
if (!next)
|
|
@@ -3780,34 +3754,32 @@ export class AgentSession {
|
|
|
3780
3754
|
}
|
|
3781
3755
|
catch {
|
|
3782
3756
|
// setModel failed (auth/guard) — blacklist so next retry advances further.
|
|
3783
|
-
this._refusedModels.add(
|
|
3757
|
+
this._refusedModels.add(failoverModelKey(pick.provider, pick.id));
|
|
3784
3758
|
return undefined;
|
|
3785
3759
|
}
|
|
3786
3760
|
}
|
|
3787
3761
|
async _prepareRetry(message) {
|
|
3788
3762
|
const settings = this.settingsManager.getRetrySettings();
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3763
|
+
const maxRetries = retryBudgetForAssistantError(message, settings.maxRetries);
|
|
3764
|
+
const attempt = nextRetryAttempt({
|
|
3765
|
+
enabled: settings.enabled,
|
|
3766
|
+
completedAttempts: this._retryAttempt,
|
|
3767
|
+
maxRetries,
|
|
3768
|
+
});
|
|
3769
|
+
if (attempt === undefined)
|
|
3796
3770
|
return false;
|
|
3797
|
-
|
|
3771
|
+
this._retryAttempt = attempt;
|
|
3798
3772
|
// Content/safety stop (Fable/Opus/Sonnet): switch model BEFORE delay so retry is not same-model refusal.
|
|
3799
3773
|
const failoverTo = await this._maybeFailoverFromSafetyStop(message);
|
|
3800
3774
|
// Safety stops are usually immediate false positives — short delay after failover, full backoff otherwise.
|
|
3801
|
-
const delayMs = failoverTo
|
|
3802
|
-
? Math.min(400, settings.baseDelayMs)
|
|
3803
|
-
: settings.baseDelayMs * 2 ** (this._retryAttempt - 1);
|
|
3775
|
+
const delayMs = computeRetryDelayMs(settings.baseDelayMs, attempt, failoverTo !== undefined);
|
|
3804
3776
|
const errorMessage = failoverTo
|
|
3805
3777
|
? `${message.errorMessage || "content/safety stop"} → failover ${failoverTo}`
|
|
3806
3778
|
: message.errorMessage || "Unknown error";
|
|
3807
3779
|
this._emit({
|
|
3808
3780
|
type: "auto_retry_start",
|
|
3809
|
-
attempt
|
|
3810
|
-
maxAttempts:
|
|
3781
|
+
attempt,
|
|
3782
|
+
maxAttempts: maxRetries,
|
|
3811
3783
|
delayMs,
|
|
3812
3784
|
errorMessage,
|
|
3813
3785
|
});
|
|
@@ -4319,21 +4291,6 @@ export class AgentSession {
|
|
|
4319
4291
|
return this._extensionRunner;
|
|
4320
4292
|
}
|
|
4321
4293
|
}
|
|
4322
|
-
// ---------------------------------------------------------------------------
|
|
4323
|
-
// Context budget constants
|
|
4324
|
-
// ---------------------------------------------------------------------------
|
|
4325
|
-
/** Fraction of contextWindow reserved for model response generation. */
|
|
4326
|
-
const RESPONSE_RESERVE_RATIO = 0.2;
|
|
4327
|
-
/** Fraction of contextWindow held back as safety margin for token-count imprecision. */
|
|
4328
|
-
const SAFETY_MARGIN_RATIO = 0.1;
|
|
4329
|
-
// Default prompt budget = contextWindow - responseReserve - safetyMargin.
|
|
4330
|
-
// With the defaults above (0.2 + 0.1) this yields ~0.70 of contextWindow, but it
|
|
4331
|
-
// stays correct when responseReserve is overridden by model.maxTokens.
|
|
4332
|
-
/** Absolute floor for maxPromptTokens — below this the budget is meaningless. */
|
|
4333
|
-
const MIN_PROMPT_TOKENS = 4000;
|
|
4334
|
-
/** Legacy defaults when no model contextWindow is known. */
|
|
4335
|
-
const LEGACY_MAX_PROMPT_TOKENS = 60_000;
|
|
4336
|
-
const LEGACY_RESPONSE_RESERVE_TOKENS = 8_192;
|
|
4337
4294
|
function parsePositiveIntegerEnv(name) {
|
|
4338
4295
|
const raw = process.env[name];
|
|
4339
4296
|
if (raw === undefined || raw.trim() === "") {
|