open-multi-agent-kit 0.96.1 → 0.97.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/CHANGELOG.md +57 -0
- package/README.md +14 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/commands/resource-doctor-cli.d.ts +22 -0
- package/dist/commands/resource-doctor-cli.d.ts.map +1 -0
- package/dist/commands/resource-doctor-cli.js +98 -0
- package/dist/commands/resource-doctor-cli.js.map +1 -0
- package/dist/core/agent-session.d.ts +46 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +373 -12
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/attachment-store.d.ts +39 -0
- package/dist/core/attachment-store.d.ts.map +1 -0
- package/dist/core/attachment-store.js +81 -0
- package/dist/core/attachment-store.js.map +1 -0
- package/dist/core/command-safety.d.ts +8 -0
- package/dist/core/command-safety.d.ts.map +1 -1
- package/dist/core/command-safety.js +140 -14
- package/dist/core/command-safety.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +6 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +35 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/completion-sound.d.ts +109 -0
- package/dist/core/completion-sound.d.ts.map +1 -0
- package/dist/core/completion-sound.js +165 -0
- package/dist/core/completion-sound.js.map +1 -0
- package/dist/core/context-budget-governor-v2.d.ts +1 -1
- package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
- package/dist/core/context-budget-governor-v2.js +1 -1
- package/dist/core/context-budget-governor-v2.js.map +1 -1
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js +2 -2
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
- package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-cache-keys.js +2 -0
- package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
- package/dist/core/context-budget-v2-plan-cache-keys.js +3 -0
- package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
- package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
- package/dist/core/context-budget-v2-planner.js +2 -1
- package/dist/core/context-budget-v2-planner.js.map +1 -1
- package/dist/core/context-budget-v2-scoring.d.ts +17 -0
- package/dist/core/context-budget-v2-scoring.d.ts.map +1 -1
- package/dist/core/context-budget-v2-scoring.js +25 -8
- package/dist/core/context-budget-v2-scoring.js.map +1 -1
- package/dist/core/context-budget-v2-selection.d.ts +2 -2
- package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
- package/dist/core/context-budget-v2-selection.js +18 -1
- package/dist/core/context-budget-v2-selection.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +17 -0
- package/dist/core/context-budget-v2-types.d.ts.map +1 -1
- package/dist/core/context-budget-v2-types.js +15 -0
- package/dist/core/context-budget-v2-types.js.map +1 -1
- package/dist/core/extensions/builtin/command-safety-gate.d.ts +3 -1
- package/dist/core/extensions/builtin/command-safety-gate.d.ts.map +1 -1
- package/dist/core/extensions/builtin/command-safety-gate.js +7 -0
- package/dist/core/extensions/builtin/command-safety-gate.js.map +1 -1
- package/dist/core/extensions/types.d.ts +12 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/host-resource-probe.d.ts +63 -0
- package/dist/core/host-resource-probe.d.ts.map +1 -0
- package/dist/core/host-resource-probe.js +126 -0
- package/dist/core/host-resource-probe.js.map +1 -0
- package/dist/core/host-resource-snapshot.d.ts +54 -0
- package/dist/core/host-resource-snapshot.d.ts.map +1 -0
- package/dist/core/host-resource-snapshot.js +139 -0
- package/dist/core/host-resource-snapshot.js.map +1 -0
- package/dist/core/model-registry.d.ts +8 -3
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +71 -1
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/package-doctor-source-scan.d.ts +9 -0
- package/dist/core/package-doctor-source-scan.d.ts.map +1 -1
- package/dist/core/package-doctor-source-scan.js +77 -4
- package/dist/core/package-doctor-source-scan.js.map +1 -1
- package/dist/core/prompt-attachment.d.ts +51 -0
- package/dist/core/prompt-attachment.d.ts.map +1 -0
- package/dist/core/prompt-attachment.js +186 -0
- package/dist/core/prompt-attachment.js.map +1 -0
- package/dist/core/prompt-settlement.d.ts +81 -0
- package/dist/core/prompt-settlement.d.ts.map +1 -0
- package/dist/core/prompt-settlement.js +84 -0
- package/dist/core/prompt-settlement.js.map +1 -0
- package/dist/core/provider-resilience.d.ts +28 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +51 -1
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/provider-retry.d.ts +7 -0
- package/dist/core/provider-retry.d.ts.map +1 -1
- package/dist/core/provider-retry.js +24 -0
- package/dist/core/provider-retry.js.map +1 -1
- package/dist/core/provider-usage.d.ts +15 -2
- package/dist/core/provider-usage.d.ts.map +1 -1
- package/dist/core/provider-usage.js +176 -2
- package/dist/core/provider-usage.js.map +1 -1
- package/dist/core/reasoning-router-promotion.d.ts +84 -0
- package/dist/core/reasoning-router-promotion.d.ts.map +1 -0
- package/dist/core/reasoning-router-promotion.js +92 -0
- package/dist/core/reasoning-router-promotion.js.map +1 -0
- package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
- package/dist/core/reasoning-router-resolver.js +1 -1
- package/dist/core/reasoning-router-resolver.js.map +1 -1
- package/dist/core/resource-admission-config.d.ts +55 -0
- package/dist/core/resource-admission-config.d.ts.map +1 -0
- package/dist/core/resource-admission-config.js +97 -0
- package/dist/core/resource-admission-config.js.map +1 -0
- package/dist/core/resource-admission.d.ts +81 -0
- package/dist/core/resource-admission.d.ts.map +1 -0
- package/dist/core/resource-admission.js +128 -0
- package/dist/core/resource-admission.js.map +1 -0
- package/dist/core/resource-governor-format.d.ts +50 -0
- package/dist/core/resource-governor-format.d.ts.map +1 -0
- package/dist/core/resource-governor-format.js +113 -0
- package/dist/core/resource-governor-format.js.map +1 -0
- package/dist/core/resource-governor-settings.d.ts +56 -0
- package/dist/core/resource-governor-settings.d.ts.map +1 -0
- package/dist/core/resource-governor-settings.js +89 -0
- package/dist/core/resource-governor-settings.js.map +1 -0
- package/dist/core/resource-observation-journal.d.ts +74 -0
- package/dist/core/resource-observation-journal.d.ts.map +1 -0
- package/dist/core/resource-observation-journal.js +198 -0
- package/dist/core/resource-observation-journal.js.map +1 -0
- package/dist/core/resource-safety-gate.d.ts +54 -0
- package/dist/core/resource-safety-gate.d.ts.map +1 -0
- package/dist/core/resource-safety-gate.js +51 -0
- package/dist/core/resource-safety-gate.js.map +1 -0
- package/dist/core/run-journal.d.ts.map +1 -1
- package/dist/core/run-journal.js +13 -1
- package/dist/core/run-journal.js.map +1 -1
- package/dist/core/run-resource-lease.d.ts +73 -0
- package/dist/core/run-resource-lease.d.ts.map +1 -0
- package/dist/core/run-resource-lease.js +78 -0
- package/dist/core/run-resource-lease.js.map +1 -0
- package/dist/core/session-bash-service.d.ts +12 -0
- package/dist/core/session-bash-service.d.ts.map +1 -1
- package/dist/core/session-bash-service.js +18 -3
- package/dist/core/session-bash-service.js.map +1 -1
- package/dist/core/session-system-prompt.d.ts +2 -0
- package/dist/core/session-system-prompt.d.ts.map +1 -1
- package/dist/core/session-system-prompt.js +2 -0
- package/dist/core/session-system-prompt.js.map +1 -1
- package/dist/core/session-termination.d.ts +8 -3
- package/dist/core/session-termination.d.ts.map +1 -1
- package/dist/core/session-termination.js +40 -3
- package/dist/core/session-termination.js.map +1 -1
- package/dist/core/settings-manager.d.ts +12 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +36 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagent-lane-launcher.d.ts +64 -0
- package/dist/core/subagent-lane-launcher.d.ts.map +1 -0
- package/dist/core/subagent-lane-launcher.js +107 -0
- package/dist/core/subagent-lane-launcher.js.map +1 -0
- package/dist/core/system-cpu-sampler.d.ts +45 -0
- package/dist/core/system-cpu-sampler.d.ts.map +1 -0
- package/dist/core/system-cpu-sampler.js +95 -0
- package/dist/core/system-cpu-sampler.js.map +1 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +5 -8
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +3 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +13 -48
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +1 -34
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/workload-classifier.d.ts +32 -0
- package/dist/core/workload-classifier.d.ts.map +1 -0
- package/dist/core/workload-classifier.js +66 -0
- package/dist/core/workload-classifier.js.map +1 -0
- package/dist/core/workload-family-matcher.d.ts +3 -0
- package/dist/core/workload-family-matcher.d.ts.map +1 -0
- package/dist/core/workload-family-matcher.js +126 -0
- package/dist/core/workload-family-matcher.js.map +1 -0
- package/dist/core/workload-permit-pool.d.ts +78 -0
- package/dist/core/workload-permit-pool.d.ts.map +1 -0
- package/dist/core/workload-permit-pool.js +138 -0
- package/dist/core/workload-permit-pool.js.map +1 -0
- package/dist/core/workload-shard-executor.d.ts +68 -0
- package/dist/core/workload-shard-executor.d.ts.map +1 -0
- package/dist/core/workload-shard-executor.js +156 -0
- package/dist/core/workload-shard-executor.js.map +1 -0
- package/dist/core/workload-shard-plan.d.ts +81 -0
- package/dist/core/workload-shard-plan.d.ts.map +1 -0
- package/dist/core/workload-shard-plan.js +183 -0
- package/dist/core/workload-shard-plan.js.map +1 -0
- package/dist/core/workload-shard-runner.d.ts +11 -0
- package/dist/core/workload-shard-runner.d.ts.map +1 -0
- package/dist/core/workload-shard-runner.js +98 -0
- package/dist/core/workload-shard-runner.js.map +1 -0
- package/dist/core/workload-shard-store.d.ts +44 -0
- package/dist/core/workload-shard-store.d.ts.map +1 -0
- package/dist/core/workload-shard-store.js +189 -0
- package/dist/core/workload-shard-store.js.map +1 -0
- package/dist/core/workload-sharders.d.ts +47 -0
- package/dist/core/workload-sharders.d.ts.map +1 -0
- package/dist/core/workload-sharders.js +162 -0
- package/dist/core/workload-sharders.js.map +1 -0
- package/dist/core/workload-shell-scan.d.ts +12 -0
- package/dist/core/workload-shell-scan.d.ts.map +1 -0
- package/dist/core/workload-shell-scan.js +79 -0
- package/dist/core/workload-shell-scan.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +6 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/attachment-strip.d.ts +21 -0
- package/dist/modes/interactive/components/attachment-strip.d.ts.map +1 -0
- package/dist/modes/interactive/components/attachment-strip.js +136 -0
- package/dist/modes/interactive/components/attachment-strip.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts +6 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +26 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-sidebar.js +12 -1
- package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +27 -4
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +255 -53
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/compaction.md +1 -1
- package/docs/extensions.md +12 -3
- package/docs/index.md +4 -1
- package/docs/metrics.md +74 -2
- package/docs/provider-resilience.md +11 -0
- package/docs/providers.md +1 -1
- package/docs/sdk.md +4 -0
- package/docs/settings.md +64 -2
- package/docs/skills.md +16 -0
- package/docs/usage.md +1 -1
- 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/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/subagent/agent-capability-router.test.ts +7 -0
- package/examples/extensions/subagent/domain-profiles.ts +0 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +8 -10
- package/dist/core/tools/omp-pure-seams.d.ts +0 -41
- package/dist/core/tools/omp-pure-seams.d.ts.map +0 -1
- package/dist/core/tools/omp-pure-seams.js +0 -142
- package/dist/core/tools/omp-pure-seams.js.map +0 -1
- package/dist/core/tools/omp-seam-runtime.d.ts +0 -100
- package/dist/core/tools/omp-seam-runtime.d.ts.map +0 -1
- package/dist/core/tools/omp-seam-runtime.js +0 -40
- package/dist/core/tools/omp-seam-runtime.js.map +0 -1
|
@@ -44,6 +44,7 @@ import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
|
|
|
44
44
|
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
45
45
|
import { assertTextChatModelForCompletion } from "./grok-harness.js";
|
|
46
46
|
import { grokPlaybookAppendForProvider } from "./grok-playbook.js";
|
|
47
|
+
import { captureHostResourceSnapshot } from "./host-resource-snapshot.js";
|
|
47
48
|
import { decideLoadoutAccess } from "./loadout-access-policy.js";
|
|
48
49
|
import { loadMcpServerConfigs } from "./mcp/config.js";
|
|
49
50
|
import { McpManager } from "./mcp/manager.js";
|
|
@@ -53,6 +54,7 @@ import { TurnMetricsSink } from "./turn-metrics.js";
|
|
|
53
54
|
const DISABLED_ENV_VALUES = new Set(["0", "false", "off", "disable", "disabled"]);
|
|
54
55
|
const MAX_OVERFLOW_RECOVERY_ATTEMPTS = 2;
|
|
55
56
|
const OVERFLOW_RECOVERY_EMERGENCY_TOKENS = 4_096;
|
|
57
|
+
const MAX_RESOURCE_OBSERVATION_JOURNALS = 32;
|
|
56
58
|
function isDisabledEnvValue(value) {
|
|
57
59
|
return value !== undefined && DISABLED_ENV_VALUES.has(value.trim().toLowerCase());
|
|
58
60
|
}
|
|
@@ -75,15 +77,21 @@ import { selectContextFilesForModel } from "./model-prompt-policy.js";
|
|
|
75
77
|
import { findExactModelReferenceMatch } from "./model-resolver.js";
|
|
76
78
|
import { computePromptTokenBudget } from "./prompt-budget.js";
|
|
77
79
|
import { classifyPromptCacheTransition } from "./prompt-cache.js";
|
|
80
|
+
import { createPromptSettlementState, reducePromptSettlement, settlePromptIfReady, } from "./prompt-settlement.js";
|
|
78
81
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
79
|
-
import { isQuotaExhaustionMessage, isStickySafetyModel, pickFailoverCandidate, resolveProviderResilience, shouldEjectStickySafetyModel, shouldHonorSafetyFailover, stickySafetyBlockMessage, } from "./provider-resilience.js";
|
|
82
|
+
import { isQuotaExhaustionMessage, isStickySafetyModel, isUpstreamUnavailableMessage, pickFailoverCandidate, resolveFailoverCandidates, resolveProviderResilience, sameModelRouteCandidates, shouldEjectStickySafetyModel, shouldHonorSafetyFailover, stickySafetyBlockMessage, } from "./provider-resilience.js";
|
|
80
83
|
import { computeRetryDelayMs, failoverModelKey, isFailoverTriggerError, isRetryableAssistantError, nextRetryAttempt, retryBudgetForAssistantError, } from "./provider-retry.js";
|
|
81
84
|
import { getBiasStepsForCell, parseRouterBiasSnapshot } from "./reasoning-router-bias.js";
|
|
82
85
|
import { classifyTaskV4, deriveRouterFeedbackFeaturesV4, resolveThinkingLevelV4WithUncertainty, TASK_CLASS_THINKING_LEVELS_V4, } from "./reasoning-router-v4.js";
|
|
83
86
|
import { redactSensitiveText, redactSensitiveTextForced } from "./redaction.js";
|
|
84
87
|
import { getRepositoryRouterLearningPaths } from "./repository-learning-scope.js";
|
|
88
|
+
import { decideResourceAdmission } from "./resource-admission.js";
|
|
89
|
+
import { resolveResourceGovernorSettings } from "./resource-governor-settings.js";
|
|
90
|
+
import { admissionObservationFacts, classificationObservationFacts, ResourceObservationJournal, settledObservationFacts, snapshotObservationFacts, soundObservationFacts, } from "./resource-observation-journal.js";
|
|
91
|
+
import { decideResourceSafetyGate, RESOURCE_PRESSURE_REQUIRED_ACTION } from "./resource-safety-gate.js";
|
|
85
92
|
import { appendRouterFeedbackRecord, ROUTER_FEEDBACK_LEVELS, } from "./router-feedback-collector.js";
|
|
86
93
|
import { RunJournalStore } from "./run-journal-store.js";
|
|
94
|
+
import { RunResourceLeaseController } from "./run-resource-lease.js";
|
|
87
95
|
import { SessionBashRuntime } from "./session-bash-runtime.js";
|
|
88
96
|
import { SessionBashService } from "./session-bash-service.js";
|
|
89
97
|
import { SessionCompactionService } from "./session-compaction-service.js";
|
|
@@ -98,6 +106,8 @@ import { createAllToolDefinitions } from "./tools/index.js";
|
|
|
98
106
|
import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
|
|
99
107
|
import { createVerifiedBashOperations } from "./verified-bash-adapter.js";
|
|
100
108
|
import { resolveSessionWorkspaceScope } from "./verified-bash-runtime.js";
|
|
109
|
+
import { classifyWorkloadCommand } from "./workload-classifier.js";
|
|
110
|
+
import { WorkloadPermitError, WorkloadPermitPool } from "./workload-permit-pool.js";
|
|
101
111
|
/**
|
|
102
112
|
* Parse a skill block from message text.
|
|
103
113
|
* Returns null if the text doesn't contain a skill block.
|
|
@@ -356,6 +366,14 @@ export class AgentSession {
|
|
|
356
366
|
_runJournalStore;
|
|
357
367
|
_ownedSessionOwnerLease;
|
|
358
368
|
_activeRunId = null;
|
|
369
|
+
// Resource governor (roadmap M2): lease controller owns the temporary tool
|
|
370
|
+
// cap for governed runs; the last decision backs the §19.4 read-only API.
|
|
371
|
+
_resourceLeaseController;
|
|
372
|
+
_resourceObservations = null;
|
|
373
|
+
_resourceObservationJournals = new Map();
|
|
374
|
+
_latestResourcePromptRunId = null;
|
|
375
|
+
_lastResourceAdmission = null;
|
|
376
|
+
_workloadPermitPool;
|
|
359
377
|
_pendingRuntimeTerminationCause;
|
|
360
378
|
_activeRunToolTermination;
|
|
361
379
|
_lastTermination;
|
|
@@ -436,6 +454,7 @@ export class AgentSession {
|
|
|
436
454
|
isStreaming: () => this.isStreaming,
|
|
437
455
|
pushMessage: (message) => this.agent.state.messages.push(message),
|
|
438
456
|
appendMessage: (message) => this.sessionManager.appendMessage(message),
|
|
457
|
+
acquireResourcePermit: (command, signal) => this._acquireBashResourcePermit(command, signal),
|
|
439
458
|
});
|
|
440
459
|
this._compactionService = new SessionCompactionService({
|
|
441
460
|
sessionManager: this.sessionManager,
|
|
@@ -605,6 +624,12 @@ export class AgentSession {
|
|
|
605
624
|
if (/auth|unauthori[sz]ed|forbidden|invalid.?api.?key|no api key|401|403|\/login/i.test(text)) {
|
|
606
625
|
return { area: "provider", code: "auth" };
|
|
607
626
|
}
|
|
627
|
+
// Gateway/upstream 5xx and dropped streams are transport failures, not
|
|
628
|
+
// transcript-shape problems. Classify as network so the guidance points to
|
|
629
|
+
// retry/model-switch instead of the protocol sanitize path.
|
|
630
|
+
if (isUpstreamUnavailableMessage(text)) {
|
|
631
|
+
return { area: "provider", code: "network" };
|
|
632
|
+
}
|
|
608
633
|
// Kimi/K3 + OpenAI-compat: orphan tool results after dropped error assistants.
|
|
609
634
|
// Sanitize-and-retry (transform-messages drops orphans), not a hard tool fatal.
|
|
610
635
|
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)) {
|
|
@@ -629,6 +654,9 @@ export class AgentSession {
|
|
|
629
654
|
if (/auth|api key|unauthori[sz]ed|forbidden|401|403|\/login/i.test(message)) {
|
|
630
655
|
return { area: "provider", code: "auth" };
|
|
631
656
|
}
|
|
657
|
+
if (isUpstreamUnavailableMessage(message)) {
|
|
658
|
+
return { area: "provider", code: "network" };
|
|
659
|
+
}
|
|
632
660
|
if (/context.+overflow|context window|too many tokens/i.test(message)) {
|
|
633
661
|
return { area: "provider", code: "context_overflow" };
|
|
634
662
|
}
|
|
@@ -1111,6 +1139,11 @@ export class AgentSession {
|
|
|
1111
1139
|
}
|
|
1112
1140
|
else if (event.type === "agent_end") {
|
|
1113
1141
|
await this._extensionRunner.emit({ type: "agent_end", messages: event.messages });
|
|
1142
|
+
// `agent_end` fires once per attempt. Extensions that release per-run
|
|
1143
|
+
// state need the boundary no retry follows, so mark it explicitly.
|
|
1144
|
+
if (!this._willRetryAfterAgentEnd(event)) {
|
|
1145
|
+
await this._extensionRunner.emit({ type: "agent_settled", messages: event.messages });
|
|
1146
|
+
}
|
|
1114
1147
|
}
|
|
1115
1148
|
else if (event.type === "turn_start") {
|
|
1116
1149
|
this._turnMetricsState = {
|
|
@@ -1627,7 +1660,15 @@ export class AgentSession {
|
|
|
1627
1660
|
this._promptCacheKey = nextKey;
|
|
1628
1661
|
this._promptCacheStablePrefixCharacters = boundary ?? 0;
|
|
1629
1662
|
}
|
|
1663
|
+
_getDefaultActiveSkills() {
|
|
1664
|
+
const trustedSkillNames = new Set(this._resourceLoader
|
|
1665
|
+
.getSkills()
|
|
1666
|
+
.skills.filter((skill) => skill.sourceInfo.scope === "user")
|
|
1667
|
+
.map((skill) => skill.name));
|
|
1668
|
+
return this.settingsManager.getDefaultActiveSkills().filter((name) => trustedSkillNames.has(name));
|
|
1669
|
+
}
|
|
1630
1670
|
_rebuildSystemPrompt(toolNames, model = this.model) {
|
|
1671
|
+
const defaultActiveSkills = this._getDefaultActiveSkills();
|
|
1631
1672
|
const assembled = assembleSessionSystemPrompt({
|
|
1632
1673
|
cwd: this._cwd,
|
|
1633
1674
|
toolNames,
|
|
@@ -1638,6 +1679,8 @@ export class AgentSession {
|
|
|
1638
1679
|
appendSystemPrompt: this._resourceLoader.getAppendSystemPrompt(),
|
|
1639
1680
|
providerAppend: grokPlaybookAppendForProvider(model?.provider),
|
|
1640
1681
|
skills: this._resourceLoader.getSkills().skills,
|
|
1682
|
+
activeSkillNames: defaultActiveSkills,
|
|
1683
|
+
activeSkillSource: defaultActiveSkills.length > 0 ? "settings" : undefined,
|
|
1641
1684
|
contextFiles: selectContextFilesForModel(this._resourceLoader.getAgentsFiles().agentsFiles, model),
|
|
1642
1685
|
contextBudget: this._getContextBudgetOptions(undefined, model),
|
|
1643
1686
|
});
|
|
@@ -1649,6 +1692,15 @@ export class AgentSession {
|
|
|
1649
1692
|
// Prompting
|
|
1650
1693
|
// =========================================================================
|
|
1651
1694
|
async _runAgentPrompt(messages) {
|
|
1695
|
+
// One identity per top-level run (§16.2): internal retries and
|
|
1696
|
+
// continuations inside this call share it, as does the resource lease.
|
|
1697
|
+
const promptRunId = `prompt-run-${randomUUID()}`;
|
|
1698
|
+
const startedAtEpochMs = Date.now();
|
|
1699
|
+
// Roadmap M2: the lease spans the whole run including internal retries
|
|
1700
|
+
// and continuations, so they share one admission decision (§8.3).
|
|
1701
|
+
const resourceLease = await this._beginResourceGovernedRun(promptRunId);
|
|
1702
|
+
const resourceObservations = this._resourceObservationJournals.get(promptRunId) ?? null;
|
|
1703
|
+
let outcome = "completed";
|
|
1652
1704
|
try {
|
|
1653
1705
|
await this.agent.prompt(messages);
|
|
1654
1706
|
while (await this._handlePostAgentRun()) {
|
|
@@ -1656,11 +1708,251 @@ export class AgentSession {
|
|
|
1656
1708
|
}
|
|
1657
1709
|
}
|
|
1658
1710
|
catch (error) {
|
|
1711
|
+
outcome =
|
|
1712
|
+
this._userAbortRequested || (error instanceof Error && /abort/i.test(error.message)) ? "aborted" : "failed";
|
|
1659
1713
|
this._publishRuntimeFailure(error);
|
|
1660
1714
|
throw error;
|
|
1661
1715
|
}
|
|
1662
1716
|
finally {
|
|
1717
|
+
if (resourceLease !== null) {
|
|
1718
|
+
// Generation-safe: a stale release after another run acquired the
|
|
1719
|
+
// lease is a no-op instead of clobbering the newer cap (§8.1).
|
|
1720
|
+
this._resourceLeaseController?.release(resourceLease);
|
|
1721
|
+
resourceObservations?.record("resource_lease_released_v1", { promptRunId });
|
|
1722
|
+
}
|
|
1663
1723
|
this._flushPendingBashMessages();
|
|
1724
|
+
// §16.4 (M4): settle only after every in-run continuation drained.
|
|
1725
|
+
this._emitPromptSettledIfReady({ promptRunId, startedAtEpochMs, outcome, resourceObservations });
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* Emit `prompt_settled` when the §16.4 conditions hold. Called exactly
|
|
1730
|
+
* once per promptRunId (from `_runAgentPrompt`'s finally), so the same
|
|
1731
|
+
* run can never emit twice; late-queued messages block emission
|
|
1732
|
+
* conservatively — the continuation run settles under its own id.
|
|
1733
|
+
* Settlement is a UX signal only and must never affect the run (§16.1).
|
|
1734
|
+
*/
|
|
1735
|
+
_emitPromptSettledIfReady(input) {
|
|
1736
|
+
try {
|
|
1737
|
+
let state = createPromptSettlementState(input.promptRunId, input.startedAtEpochMs);
|
|
1738
|
+
state = reducePromptSettlement(state, { kind: "terminal", outcome: input.outcome });
|
|
1739
|
+
if (this.isStreaming) {
|
|
1740
|
+
state = reducePromptSettlement(state, { kind: "tool", delta: 1 });
|
|
1741
|
+
}
|
|
1742
|
+
if (this.agent.hasQueuedMessages()) {
|
|
1743
|
+
state = reducePromptSettlement(state, { kind: "continuation", delta: 1 });
|
|
1744
|
+
}
|
|
1745
|
+
const settled = settlePromptIfReady(state, Date.now());
|
|
1746
|
+
if (settled.event !== null) {
|
|
1747
|
+
input.resourceObservations?.record("prompt_settled_v1", settledObservationFacts(settled.event));
|
|
1748
|
+
this._emit(settled.event);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
catch {
|
|
1752
|
+
// Never let settlement bookkeeping break a run.
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Resource governor preflight for one top-level run (roadmap §8, §25.3 M2).
|
|
1757
|
+
*
|
|
1758
|
+
* - `off`: nothing; v0.96.1 observable behavior is preserved.
|
|
1759
|
+
* - `observe` (default): fire-and-forget probe records a decision for the
|
|
1760
|
+
* §19.4 read-only API; the cap never changes and the prompt is never
|
|
1761
|
+
* delayed. The pending probe is bounded (~cpuSampleMs) and never rejects.
|
|
1762
|
+
* - `adaptive`/`strict`: bounded blocking probe (default 300 ms deadline),
|
|
1763
|
+
* then a generation-safe lease applies the effective tool cap for this
|
|
1764
|
+
* run. The caller's `finally` releases it exactly once.
|
|
1765
|
+
*
|
|
1766
|
+
* Never throws: any probe or policy failure leaves this run ungoverned
|
|
1767
|
+
* (§2.1: probe failure must not crash or block the prompt).
|
|
1768
|
+
*/
|
|
1769
|
+
async _beginResourceGovernedRun(promptRunId) {
|
|
1770
|
+
try {
|
|
1771
|
+
const resolved = resolveResourceGovernorSettings(this.settingsManager.getResourceGovernorSettings());
|
|
1772
|
+
if (resolved.mode === "off") {
|
|
1773
|
+
return null;
|
|
1774
|
+
}
|
|
1775
|
+
const resourceObservations = this._openResourceObservations(promptRunId);
|
|
1776
|
+
const probeOptions = {
|
|
1777
|
+
cwd: this._cwd,
|
|
1778
|
+
maxProbeMs: resolved.maxProbeMs,
|
|
1779
|
+
cpuSampleMs: resolved.cpuSampleMs,
|
|
1780
|
+
};
|
|
1781
|
+
// §7.3: Agent.maxToolConcurrency uses undefined for "unlimited"; the
|
|
1782
|
+
// admission contract uses 0 for the same meaning.
|
|
1783
|
+
const configuredCaps = { maxToolConcurrency: this.agent.maxToolConcurrency ?? 0 };
|
|
1784
|
+
if (resolved.mode === "observe") {
|
|
1785
|
+
void captureHostResourceSnapshot(probeOptions)
|
|
1786
|
+
.then((snapshot) => {
|
|
1787
|
+
const decision = decideResourceAdmission({
|
|
1788
|
+
snapshot,
|
|
1789
|
+
config: resolved.admission,
|
|
1790
|
+
configuredCaps,
|
|
1791
|
+
});
|
|
1792
|
+
if (this._latestResourcePromptRunId === promptRunId) {
|
|
1793
|
+
this._lastResourceAdmission = decision;
|
|
1794
|
+
}
|
|
1795
|
+
resourceObservations?.record("resource_snapshot_v1", snapshotObservationFacts(snapshot));
|
|
1796
|
+
resourceObservations?.record("resource_admission_v1", admissionObservationFacts(decision));
|
|
1797
|
+
})
|
|
1798
|
+
.catch(() => { });
|
|
1799
|
+
return null;
|
|
1800
|
+
}
|
|
1801
|
+
const snapshot = await captureHostResourceSnapshot(probeOptions);
|
|
1802
|
+
const decision = decideResourceAdmission({ snapshot, config: resolved.admission, configuredCaps });
|
|
1803
|
+
if (this._latestResourcePromptRunId === promptRunId) {
|
|
1804
|
+
this._lastResourceAdmission = decision;
|
|
1805
|
+
}
|
|
1806
|
+
resourceObservations?.record("resource_snapshot_v1", snapshotObservationFacts(snapshot));
|
|
1807
|
+
resourceObservations?.record("resource_admission_v1", admissionObservationFacts(decision));
|
|
1808
|
+
this._resourceLeaseController ??= new RunResourceLeaseController({
|
|
1809
|
+
getCap: () => this.agent.maxToolConcurrency,
|
|
1810
|
+
setCap: (cap) => {
|
|
1811
|
+
this.agent.maxToolConcurrency = cap;
|
|
1812
|
+
},
|
|
1813
|
+
});
|
|
1814
|
+
const lease = this._resourceLeaseController.acquire({ promptRunId, decision });
|
|
1815
|
+
resourceObservations?.record("resource_lease_acquired_v1", {
|
|
1816
|
+
decisionId: decision.decisionId,
|
|
1817
|
+
appliedToolCap: decision.maxToolConcurrency,
|
|
1818
|
+
});
|
|
1819
|
+
return lease;
|
|
1820
|
+
}
|
|
1821
|
+
catch {
|
|
1822
|
+
return null;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
_openResourceObservations(promptRunId) {
|
|
1826
|
+
this._latestResourcePromptRunId = promptRunId;
|
|
1827
|
+
try {
|
|
1828
|
+
const journal = ResourceObservationJournal.open(this._cwd, promptRunId);
|
|
1829
|
+
this._resourceObservations = journal;
|
|
1830
|
+
this._resourceObservationJournals.set(promptRunId, journal);
|
|
1831
|
+
while (this._resourceObservationJournals.size > MAX_RESOURCE_OBSERVATION_JOURNALS) {
|
|
1832
|
+
const oldestPromptRunId = this._resourceObservationJournals.keys().next().value;
|
|
1833
|
+
if (oldestPromptRunId === undefined)
|
|
1834
|
+
break;
|
|
1835
|
+
this._resourceObservationJournals.delete(oldestPromptRunId);
|
|
1836
|
+
}
|
|
1837
|
+
return journal;
|
|
1838
|
+
}
|
|
1839
|
+
catch {
|
|
1840
|
+
this._resourceObservations = null;
|
|
1841
|
+
this._resourceObservationJournals.delete(promptRunId);
|
|
1842
|
+
return null;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
/** §20.2: record the TUI-side completion sound result in its originating run journal. */
|
|
1846
|
+
recordCompletionSoundResult(promptRunId, result) {
|
|
1847
|
+
const journal = this._resourceObservationJournals.get(promptRunId);
|
|
1848
|
+
journal?.record("completion_sound_result_v1", soundObservationFacts(result));
|
|
1849
|
+
this._resourceObservationJournals.delete(promptRunId);
|
|
1850
|
+
if (this._latestResourcePromptRunId === promptRunId) {
|
|
1851
|
+
this._latestResourcePromptRunId = null;
|
|
1852
|
+
if (this._resourceObservations === journal)
|
|
1853
|
+
this._resourceObservations = null;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
/** §19.4 read-only SDK surface: the most recent resource admission decision, if any. */
|
|
1857
|
+
getCurrentResourceAdmission() {
|
|
1858
|
+
return this._lastResourceAdmission;
|
|
1859
|
+
}
|
|
1860
|
+
/** §19.4 read-only SDK surface: capture a fresh host resource snapshot. */
|
|
1861
|
+
async getHostResourceSnapshot() {
|
|
1862
|
+
const resolved = resolveResourceGovernorSettings(this.settingsManager.getResourceGovernorSettings());
|
|
1863
|
+
return captureHostResourceSnapshot({
|
|
1864
|
+
cwd: this._cwd,
|
|
1865
|
+
maxProbeMs: resolved.maxProbeMs,
|
|
1866
|
+
cpuSampleMs: resolved.cpuSampleMs,
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
/** §19.4 read-only SDK surface: shared workload permit pool state, if any. */
|
|
1870
|
+
getWorkloadPermitSnapshot() {
|
|
1871
|
+
return this._workloadPermitPool?.snapshot() ?? null;
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* §14.1 shared-budget seam: child subagents must reuse this instance
|
|
1875
|
+
* instead of creating a private pool (host oversubscription). The live
|
|
1876
|
+
* child launcher wiring lands in M6.
|
|
1877
|
+
*/
|
|
1878
|
+
get workloadPermitPool() {
|
|
1879
|
+
this._workloadPermitPool ??= new WorkloadPermitPool();
|
|
1880
|
+
return this._workloadPermitPool;
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Resource safety gate for the bash boundary (roadmap §9.4/§11, M3).
|
|
1884
|
+
* Runs only in adaptive/strict mode with a recorded admission decision;
|
|
1885
|
+
* observe/off keep v0.96.1 behavior. Never throws: gate machinery
|
|
1886
|
+
* failures leave the command ungated (§2.1), while computed block
|
|
1887
|
+
* verdicts and permit rejections return a bounded §11.3 payload.
|
|
1888
|
+
*/
|
|
1889
|
+
async _acquireBashResourcePermit(command, signal) {
|
|
1890
|
+
try {
|
|
1891
|
+
const resolved = resolveResourceGovernorSettings(this.settingsManager.getResourceGovernorSettings());
|
|
1892
|
+
if (resolved.mode !== "adaptive" && resolved.mode !== "strict") {
|
|
1893
|
+
return {};
|
|
1894
|
+
}
|
|
1895
|
+
const decision = this._lastResourceAdmission;
|
|
1896
|
+
if (decision === null) {
|
|
1897
|
+
return {};
|
|
1898
|
+
}
|
|
1899
|
+
const classification = classifyWorkloadCommand(command);
|
|
1900
|
+
this._resourceObservations?.record("workload_classification_v1", classificationObservationFacts(classification));
|
|
1901
|
+
const verdict = decideResourceSafetyGate({ commandSafety: "allowed", classification, decision });
|
|
1902
|
+
if (verdict.kind === "allow") {
|
|
1903
|
+
return {};
|
|
1904
|
+
}
|
|
1905
|
+
if (verdict.kind === "block") {
|
|
1906
|
+
return { blocked: JSON.stringify(verdict.block) };
|
|
1907
|
+
}
|
|
1908
|
+
this._workloadPermitPool ??= new WorkloadPermitPool({ capacity: decision.maxHeavyProcesses });
|
|
1909
|
+
const pool = this._workloadPermitPool;
|
|
1910
|
+
pool.setCapacity(decision.maxHeavyProcesses);
|
|
1911
|
+
const waitStartedMs = Date.now();
|
|
1912
|
+
this._resourceObservations?.record("workload_permit_wait_v1", {
|
|
1913
|
+
workloadClass: classification.workloadClass,
|
|
1914
|
+
weight: verdict.weight,
|
|
1915
|
+
});
|
|
1916
|
+
try {
|
|
1917
|
+
const permit = await pool.acquire({
|
|
1918
|
+
requestId: `bash-${randomUUID()}`,
|
|
1919
|
+
promptRunId: this._resourceLeaseController?.activeLease?.promptRunId ?? "unleased",
|
|
1920
|
+
workloadClass: classification.workloadClass,
|
|
1921
|
+
weight: verdict.weight,
|
|
1922
|
+
signal,
|
|
1923
|
+
// Bounded §10.3 timeout-aware wait; a settings knob can follow later.
|
|
1924
|
+
timeoutMs: 60_000,
|
|
1925
|
+
});
|
|
1926
|
+
this._resourceObservations?.record("workload_permit_acquired_v1", {
|
|
1927
|
+
workloadClass: classification.workloadClass,
|
|
1928
|
+
weight: verdict.weight,
|
|
1929
|
+
waitMs: Date.now() - waitStartedMs,
|
|
1930
|
+
});
|
|
1931
|
+
return {
|
|
1932
|
+
release: () => {
|
|
1933
|
+
permit.release();
|
|
1934
|
+
this._resourceObservations?.record("workload_permit_released_v1", {
|
|
1935
|
+
workloadClass: classification.workloadClass,
|
|
1936
|
+
weight: verdict.weight,
|
|
1937
|
+
});
|
|
1938
|
+
},
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
catch (error) {
|
|
1942
|
+
const code = error instanceof WorkloadPermitError ? error.code : "queue_overflow";
|
|
1943
|
+
return {
|
|
1944
|
+
blocked: JSON.stringify({
|
|
1945
|
+
kind: "resource_pressure",
|
|
1946
|
+
pressure: decision.pressure,
|
|
1947
|
+
action: "defer-heavy",
|
|
1948
|
+
reasonCodes: [...decision.reasons, `resource.permit.${code}`],
|
|
1949
|
+
requiredAction: RESOURCE_PRESSURE_REQUIRED_ACTION,
|
|
1950
|
+
}),
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
catch {
|
|
1955
|
+
return {};
|
|
1664
1956
|
}
|
|
1665
1957
|
}
|
|
1666
1958
|
async _handlePostAgentRun() {
|
|
@@ -1702,8 +1994,13 @@ export class AgentSession {
|
|
|
1702
1994
|
const expandPromptTemplates = options?.expandPromptTemplates ?? true;
|
|
1703
1995
|
const preflightResult = options?.preflightResult;
|
|
1704
1996
|
let currentText = redactSensitiveText(text);
|
|
1705
|
-
|
|
1706
|
-
let
|
|
1997
|
+
const defaultActiveSkills = this._getDefaultActiveSkills();
|
|
1998
|
+
let promptActiveSkillNames = mergePromptActiveSkillNames(defaultActiveSkills, options?.activeSkillNames ?? []);
|
|
1999
|
+
let promptActiveSkillSource = defaultActiveSkills.length > 0
|
|
2000
|
+
? options?.activeSkillSource
|
|
2001
|
+
? `settings+${options.activeSkillSource}`
|
|
2002
|
+
: "settings"
|
|
2003
|
+
: options?.activeSkillSource;
|
|
1707
2004
|
let isBangSkillInvocation = false;
|
|
1708
2005
|
if (expandPromptTemplates) {
|
|
1709
2006
|
const bangInvocation = parseBangInvocation(text, {
|
|
@@ -1715,7 +2012,8 @@ export class AgentSession {
|
|
|
1715
2012
|
? `/skill:${bangInvocation.skillName} ${bangInvocation.prompt}`
|
|
1716
2013
|
: `/skill:${bangInvocation.skillName}`;
|
|
1717
2014
|
promptActiveSkillNames = mergePromptActiveSkillNames(promptActiveSkillNames, bangInvocation.activeSkillNames);
|
|
1718
|
-
promptActiveSkillSource =
|
|
2015
|
+
promptActiveSkillSource =
|
|
2016
|
+
defaultActiveSkills.length > 0 ? `settings+${bangInvocation.source}` : bangInvocation.source;
|
|
1719
2017
|
}
|
|
1720
2018
|
}
|
|
1721
2019
|
let messages;
|
|
@@ -1819,8 +2117,8 @@ export class AgentSession {
|
|
|
1819
2117
|
const turnSystemPromptOptions = {
|
|
1820
2118
|
...this._baseSystemPromptOptions,
|
|
1821
2119
|
contextBudget: this._getContextBudgetOptions(expandedText),
|
|
1822
|
-
|
|
1823
|
-
|
|
2120
|
+
activeSkillNames: promptActiveSkillNames,
|
|
2121
|
+
activeSkillSource: promptActiveSkillSource,
|
|
1824
2122
|
};
|
|
1825
2123
|
const turnSystemPrompt = buildSystemPromptPlan(turnSystemPromptOptions);
|
|
1826
2124
|
// Emit before_agent_start extension event
|
|
@@ -2702,6 +3000,25 @@ export class AgentSession {
|
|
|
2702
3000
|
});
|
|
2703
3001
|
return model;
|
|
2704
3002
|
}
|
|
3003
|
+
/**
|
|
3004
|
+
* Authenticated failover candidates for compaction summarization, in
|
|
3005
|
+
* resilience-chain order, excluding the primary compaction model itself.
|
|
3006
|
+
* Used when the primary model's quota is exhausted (same-model retry is
|
|
3007
|
+
* useless until reset; another provider can still save the compaction).
|
|
3008
|
+
*/
|
|
3009
|
+
_compactionFailoverModels(primary) {
|
|
3010
|
+
const candidates = resolveFailoverCandidates(this.settingsManager.getProviderResilienceSettings());
|
|
3011
|
+
const available = this._modelRegistry.getAvailable();
|
|
3012
|
+
const models = [];
|
|
3013
|
+
for (const candidate of candidates) {
|
|
3014
|
+
if (candidate.provider === primary.provider && candidate.id === primary.id)
|
|
3015
|
+
continue;
|
|
3016
|
+
const resolved = available.find((m) => m.provider === candidate.provider && m.id === candidate.id);
|
|
3017
|
+
if (resolved)
|
|
3018
|
+
models.push(resolved);
|
|
3019
|
+
}
|
|
3020
|
+
return models;
|
|
3021
|
+
}
|
|
2705
3022
|
/**
|
|
2706
3023
|
* Manually compact the session context.
|
|
2707
3024
|
* Aborts current agent operation first.
|
|
@@ -2762,7 +3079,7 @@ export class AgentSession {
|
|
|
2762
3079
|
}
|
|
2763
3080
|
else {
|
|
2764
3081
|
// Generate compaction result
|
|
2765
|
-
const result = await compact(preparation, compactionModel, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason: "manual" }));
|
|
3082
|
+
const result = await compact(preparation, compactionModel, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason: "manual" }), this._compactionFailoverModels(compactionModel));
|
|
2766
3083
|
summary = result.summary;
|
|
2767
3084
|
firstKeptEntryId = result.firstKeptEntryId;
|
|
2768
3085
|
tokensBefore = result.tokensBefore;
|
|
@@ -2805,6 +3122,8 @@ export class AgentSession {
|
|
|
2805
3122
|
compactionCode = "aborted";
|
|
2806
3123
|
else if (stale)
|
|
2807
3124
|
compactionCode = "stale";
|
|
3125
|
+
else if (isQuotaExhaustionMessage(message))
|
|
3126
|
+
compactionCode = "quota_exhausted";
|
|
2808
3127
|
const timestamp = new Date().toISOString();
|
|
2809
3128
|
this._publishTermination(classifySessionTermination({
|
|
2810
3129
|
sessionId: this.sessionId,
|
|
@@ -2858,7 +3177,7 @@ export class AgentSession {
|
|
|
2858
3177
|
/**
|
|
2859
3178
|
* Effective context window for the upcoming turn.
|
|
2860
3179
|
* Image-bearing turns with a text-only session model are auto-routed to the
|
|
2861
|
-
* vision model (gpt-5.6-luna,
|
|
3180
|
+
* vision model (gpt-5.6-luna, 1M) — that window is the real limit. Text-only
|
|
2862
3181
|
* turns keep the session model's (typically much larger) window.
|
|
2863
3182
|
*/
|
|
2864
3183
|
_effectiveTurnContextWindow(pendingMessages, sessionWindow) {
|
|
@@ -3093,7 +3412,7 @@ export class AgentSession {
|
|
|
3093
3412
|
}
|
|
3094
3413
|
else {
|
|
3095
3414
|
// Generate compaction result
|
|
3096
|
-
const compactResult = await compact(preparation, compactionModel, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }));
|
|
3415
|
+
const compactResult = await compact(preparation, compactionModel, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }), this._compactionFailoverModels(compactionModel));
|
|
3097
3416
|
summary = compactResult.summary;
|
|
3098
3417
|
firstKeptEntryId = compactResult.firstKeptEntryId;
|
|
3099
3418
|
tokensBefore = compactResult.tokensBefore;
|
|
@@ -3708,6 +4027,41 @@ export class AgentSession {
|
|
|
3708
4027
|
// setModel itself blocks sticky targets, not sources — safe.
|
|
3709
4028
|
await this.setModel(next);
|
|
3710
4029
|
}
|
|
4030
|
+
/**
|
|
4031
|
+
* Rotate to another authenticated provider route serving the SAME model
|
|
4032
|
+
* family (e.g. openrouter/stealth/ox-alpha ↔ opencode-go/ox-alpha-free ↔
|
|
4033
|
+
* opencode/x-preview-f-free). Used when the current route fails with an
|
|
4034
|
+
* upstream availability error — same-model retry would hammer a dead
|
|
4035
|
+
* endpoint while the identical model is one route away. Visited and
|
|
4036
|
+
* unauthenticated routes are marked in {@link _refusedModels} so repeat
|
|
4037
|
+
* failures advance the rotation within the turn.
|
|
4038
|
+
*/
|
|
4039
|
+
async _maybeRotateModelRoutes() {
|
|
4040
|
+
const current = this.model;
|
|
4041
|
+
if (!current)
|
|
4042
|
+
return undefined;
|
|
4043
|
+
const candidates = sameModelRouteCandidates(current, this._modelRegistry.getAvailable());
|
|
4044
|
+
for (const candidate of candidates) {
|
|
4045
|
+
const key = failoverModelKey(candidate.provider, candidate.id);
|
|
4046
|
+
if (this._refusedModels.has(key))
|
|
4047
|
+
continue;
|
|
4048
|
+
const next = this._modelRegistry.find(candidate.provider, candidate.id);
|
|
4049
|
+
if (!next || !this._modelRegistry.hasConfiguredAuth(next)) {
|
|
4050
|
+
this._refusedModels.add(key);
|
|
4051
|
+
continue;
|
|
4052
|
+
}
|
|
4053
|
+
try {
|
|
4054
|
+
await this.setModel(next);
|
|
4055
|
+
// The old route just failed upstream — keep it out of this turn's retries.
|
|
4056
|
+
this._refusedModels.add(failoverModelKey(current.provider, current.id));
|
|
4057
|
+
return `${next.provider}/${next.id}`;
|
|
4058
|
+
}
|
|
4059
|
+
catch {
|
|
4060
|
+
this._refusedModels.add(key);
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
return undefined;
|
|
4064
|
+
}
|
|
3711
4065
|
/**
|
|
3712
4066
|
* Content/safety stops (stop_reason=refusal) are often false positives on coding turns
|
|
3713
4067
|
* for Fable AND Claude Opus/Sonnet. Retrying the same model usually reprints the refusal —
|
|
@@ -3769,12 +4123,19 @@ export class AgentSession {
|
|
|
3769
4123
|
if (attempt === undefined)
|
|
3770
4124
|
return false;
|
|
3771
4125
|
this._retryAttempt = attempt;
|
|
4126
|
+
// Upstream-unavailable (gateway 5xx / dropped stream): rotate to another
|
|
4127
|
+
// authenticated route of the SAME model family so the retry lands on a
|
|
4128
|
+
// live endpoint instead of hammering the dead one.
|
|
4129
|
+
const rotatedTo = isUpstreamUnavailableMessage(message.errorMessage)
|
|
4130
|
+
? await this._maybeRotateModelRoutes()
|
|
4131
|
+
: undefined;
|
|
3772
4132
|
// Content/safety stop (Fable/Opus/Sonnet): switch model BEFORE delay so retry is not same-model refusal.
|
|
3773
4133
|
const failoverTo = await this._maybeFailoverFromSafetyStop(message);
|
|
4134
|
+
const switchedVia = failoverTo ? `failover ${failoverTo}` : rotatedTo ? `route ${rotatedTo}` : undefined;
|
|
3774
4135
|
// Safety stops are usually immediate false positives — short delay after failover, full backoff otherwise.
|
|
3775
|
-
const delayMs = computeRetryDelayMs(settings.baseDelayMs, attempt,
|
|
3776
|
-
const errorMessage =
|
|
3777
|
-
? `${message.errorMessage || "content/safety stop"} →
|
|
4136
|
+
const delayMs = computeRetryDelayMs(settings.baseDelayMs, attempt, switchedVia !== undefined);
|
|
4137
|
+
const errorMessage = switchedVia
|
|
4138
|
+
? `${message.errorMessage || "content/safety stop"} → ${switchedVia}`
|
|
3778
4139
|
: message.errorMessage || "Unknown error";
|
|
3779
4140
|
this._emit({
|
|
3780
4141
|
type: "auto_retry_start",
|