@gajae-code/coding-agent 0.16.7 → 0.17.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 +180 -1
- package/dist/types/capability/fs.d.ts +37 -4
- package/dist/types/capability/index.d.ts +19 -0
- package/dist/types/capability/types.d.ts +9 -0
- package/dist/types/cli/doctor/args.d.ts +36 -0
- package/dist/types/cli/doctor/checks.d.ts +18 -0
- package/dist/types/cli/doctor/config-repairs.d.ts +38 -0
- package/dist/types/cli/doctor/context.d.ts +89 -0
- package/dist/types/cli/doctor/files.d.ts +27 -0
- package/dist/types/cli/doctor/ids.d.ts +20 -0
- package/dist/types/cli/doctor/install-repairs.d.ts +94 -0
- package/dist/types/cli/doctor/journal.d.ts +43 -0
- package/dist/types/cli/doctor/managed-link.d.ts +39 -0
- package/dist/types/cli/doctor/native-probe.d.ts +2 -0
- package/dist/types/cli/doctor/permission-repairs.d.ts +25 -0
- package/dist/types/cli/doctor/plugin-quarantine.d.ts +77 -0
- package/dist/types/cli/doctor/plugin-restore.d.ts +40 -0
- package/dist/types/cli/doctor/plugin-targets.d.ts +11 -0
- package/dist/types/cli/doctor/probe-client.d.ts +9 -0
- package/dist/types/cli/doctor/probe-types.d.ts +18 -0
- package/dist/types/cli/doctor/probe.d.ts +3 -0
- package/dist/types/cli/doctor/projection-probe.d.ts +2 -0
- package/dist/types/cli/doctor/repairs.d.ts +28 -0
- package/dist/types/cli/doctor/report.d.ts +27 -0
- package/dist/types/cli/doctor/runner.d.ts +4 -0
- package/dist/types/cli/doctor/safety.d.ts +10 -0
- package/dist/types/cli/doctor/service-repairs.d.ts +8 -0
- package/dist/types/cli/doctor/service-targets.d.ts +41 -0
- package/dist/types/cli/doctor/types.d.ts +145 -0
- package/dist/types/cli/doctor-cli.d.ts +7 -0
- package/dist/types/cli/doctor-supervisor.d.ts +121 -0
- package/dist/types/cli/doctor-worker.d.ts +29 -0
- package/dist/types/cli/install-activation.d.ts +158 -0
- package/dist/types/cli/macos-community-app.d.ts +41 -0
- package/dist/types/cli/mcp-cli.d.ts +0 -11
- package/dist/types/cli/update-cli.d.ts +41 -4
- package/dist/types/cli-main.d.ts +174 -0
- package/dist/types/cli.d.ts +1 -171
- package/dist/types/commands/doctor.d.ts +7 -0
- package/dist/types/commands/sdk.d.ts +17 -8
- package/dist/types/config/autorouting-tier-map.d.ts +19 -0
- package/dist/types/config/file-lock.d.ts +27 -3
- package/dist/types/config/model-preset-registry.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +12 -5
- package/dist/types/config/model-resolver.d.ts +16 -3
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-gate-codec.d.ts +1 -1
- package/dist/types/coordinator-mcp/session-reaper.d.ts +15 -5
- package/dist/types/cursor.d.ts +9 -9
- package/dist/types/discovery/agents-md.d.ts +6 -1
- package/dist/types/discovery/helpers.d.ts +36 -7
- package/dist/types/discovery/index.d.ts +1 -1
- package/dist/types/discovery/mcp-json.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +22 -1
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +14 -1
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
- package/dist/types/extensibility/plugins/manager.d.ts +33 -4
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +6 -0
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +186 -0
- package/dist/types/extensibility/plugins/marketplace/registry.d.ts +27 -0
- package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +16 -1
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +16 -3
- package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +6 -0
- package/dist/types/gjc-runtime/launch-worktree.d.ts +53 -0
- package/dist/types/gjc-runtime/session-layout.d.ts +8 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +15 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +6 -0
- package/dist/types/main.d.ts +19 -1
- package/dist/types/modes/acp/acp-agent.d.ts +40 -0
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/runtime-mcp/config-writer.d.ts +47 -0
- package/dist/types/runtime-mcp/policy-json-edit.d.ts +60 -0
- package/dist/types/runtime-mcp/startup-policy.d.ts +4 -0
- package/dist/types/runtime-mcp/types.d.ts +2 -2
- package/dist/types/sdk/acp/adapter.d.ts +5 -1
- package/dist/types/sdk/broker/broker.d.ts +51 -0
- package/dist/types/sdk/broker/daemon-entry.d.ts +39 -0
- package/dist/types/sdk/broker/discovery.d.ts +38 -0
- package/dist/types/sdk/broker/doctor-restart.d.ts +79 -0
- package/dist/types/sdk/broker/endpoint-authority.d.ts +7 -0
- package/dist/types/sdk/broker/ensure.d.ts +14 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +2 -0
- package/dist/types/sdk/broker/process-incarnation.d.ts +50 -0
- package/dist/types/sdk/broker/runtime.d.ts +26 -0
- package/dist/types/sdk/broker/session-index.d.ts +13 -4
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +14 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +16 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +101 -6
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +16 -0
- package/dist/types/sdk/bus/daemon-startup-exclusion.d.ts +43 -0
- package/dist/types/sdk/bus/doctor-daemon-restart-client.d.ts +51 -0
- package/dist/types/sdk/bus/doctor-daemon-restart.d.ts +39 -0
- package/dist/types/sdk/bus/index.d.ts +2 -0
- package/dist/types/sdk/bus/notification-service.d.ts +3 -0
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -0
- package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +1 -0
- package/dist/types/sdk/bus/telegram-daemon.d.ts +36 -5
- package/dist/types/sdk/cli/session-cli.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +4 -0
- package/dist/types/sdk/host/surface-policy.d.ts +3 -1
- package/dist/types/sdk/lifecycle/service.d.ts +44 -10
- package/dist/types/sdk/prompt-deadline-lease.d.ts +6 -3
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -2
- package/dist/types/sdk/prompt-failure.d.ts +47 -0
- package/dist/types/sdk/prompt-status.d.ts +19 -0
- package/dist/types/sdk/router/session-router.d.ts +3 -4
- package/dist/types/sdk/service-artifact-paths.d.ts +16 -0
- package/dist/types/sdk/session-authority.d.ts +9 -0
- package/dist/types/sdk/session.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/blob-store.d.ts +1 -1
- package/dist/types/session/fallback-chain-controller.d.ts +3 -6
- package/dist/types/session/internal/managed-session-scope.d.ts +44 -0
- package/dist/types/session/primary-control-surface.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +6 -0
- package/dist/types/tools/ask.d.ts +25 -0
- package/dist/types/tools/image-gen.d.ts +0 -4
- package/dist/types/tools/skill-discovery.d.ts +7 -5
- package/dist/types/tools/ultragoal-ask-guard.d.ts +7 -4
- package/dist/types/utils/changelog.d.ts +0 -9
- package/dist/types/utils/mupdf-wasm.d.ts +1 -0
- package/package.json +9 -8
- package/scripts/compile-args.ts +8 -4
- package/src/capability/fs.ts +395 -17
- package/src/capability/index.ts +273 -10
- package/src/capability/types.ts +9 -0
- package/src/cli/customize-doctor.ts +18 -12
- package/src/cli/doctor/args.ts +236 -0
- package/src/cli/doctor/checks.ts +431 -0
- package/src/cli/doctor/config-repairs.ts +517 -0
- package/src/cli/doctor/context.ts +149 -0
- package/src/cli/doctor/files.ts +149 -0
- package/src/cli/doctor/ids.ts +115 -0
- package/src/cli/doctor/install-repairs.ts +335 -0
- package/src/cli/doctor/journal.ts +423 -0
- package/src/cli/doctor/managed-link.ts +514 -0
- package/src/cli/doctor/native-probe.ts +13 -0
- package/src/cli/doctor/permission-repairs.ts +242 -0
- package/src/cli/doctor/plugin-quarantine.ts +377 -0
- package/src/cli/doctor/plugin-restore.ts +59 -0
- package/src/cli/doctor/plugin-targets.ts +373 -0
- package/src/cli/doctor/probe-client.ts +195 -0
- package/src/cli/doctor/probe-types.ts +21 -0
- package/src/cli/doctor/probe.ts +42 -0
- package/src/cli/doctor/projection-probe.ts +249 -0
- package/src/cli/doctor/repairs.ts +1157 -0
- package/src/cli/doctor/report.ts +176 -0
- package/src/cli/doctor/runner.ts +342 -0
- package/src/cli/doctor/safety.ts +40 -0
- package/src/cli/doctor/service-repairs.ts +452 -0
- package/src/cli/doctor/service-targets.ts +274 -0
- package/src/cli/doctor/types.ts +164 -0
- package/src/cli/doctor-cli.ts +62 -0
- package/src/cli/doctor-supervisor.ts +571 -0
- package/src/cli/doctor-worker.ts +181 -0
- package/src/cli/install-activation.ts +666 -0
- package/src/cli/macos-community-app.ts +1449 -0
- package/src/cli/mcp-cli.ts +11 -21
- package/src/cli/nofile-limit.ts +5 -1
- package/src/cli/plugin-cli.ts +6 -1
- package/src/cli/update-cli.ts +362 -264
- package/src/cli-main.ts +517 -0
- package/src/cli.ts +64 -433
- package/src/commands/autoresearch.ts +11 -5
- package/src/commands/completion.ts +1 -1
- package/src/commands/doctor.ts +13 -0
- package/src/commands/sdk.ts +58 -10
- package/src/commands/ultragoal.ts +11 -5
- package/src/config/autorouting-tier-map.ts +7 -0
- package/src/config/file-lock.ts +736 -71
- package/src/config/mcp-schema.json +3 -3
- package/src/config/model-preset-registry.ts +1 -0
- package/src/config/model-registry.ts +80 -27
- package/src/config/model-resolver.ts +33 -4
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +6 -2
- package/src/coordinator-mcp/question-gate-codec.ts +1 -0
- package/src/coordinator-mcp/server.ts +534 -51
- package/src/coordinator-mcp/session-reaper.ts +64 -8
- package/src/cursor.ts +223 -53
- package/src/discovery/agents-md.ts +74 -8
- package/src/discovery/agents.ts +32 -7
- package/src/discovery/builtin.ts +184 -75
- package/src/discovery/claude-plugins.ts +85 -17
- package/src/discovery/cline.ts +38 -10
- package/src/discovery/cursor.ts +19 -6
- package/src/discovery/gemini.ts +62 -22
- package/src/discovery/github.ts +13 -3
- package/src/discovery/helpers.ts +483 -89
- package/src/discovery/index.ts +1 -0
- package/src/discovery/mcp-json.ts +36 -10
- package/src/discovery/opencode.ts +36 -19
- package/src/discovery/ssh.ts +11 -8
- package/src/discovery/windsurf.ts +12 -3
- package/src/exec/bash-executor.ts +10 -3
- package/src/exec/isolated-shell.ts +15 -2
- package/src/extensibility/extensions/loader.ts +88 -26
- package/src/extensibility/gjc-plugins/lifecycle.ts +189 -0
- package/src/extensibility/gjc-plugins/paths.ts +4 -1
- package/src/extensibility/gjc-plugins/types.ts +27 -0
- package/src/extensibility/plugins/manager.ts +200 -22
- package/src/extensibility/plugins/marketplace/cache.ts +55 -2
- package/src/extensibility/plugins/marketplace/manager.ts +1262 -162
- package/src/extensibility/plugins/marketplace/registry.ts +153 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +116 -10
- package/src/extensibility/runtime-skill-discovery.ts +61 -9
- package/src/extensibility/skill-management.ts +7 -1
- package/src/gjc-runtime/autoresearch-runtime.ts +10 -1
- package/src/gjc-runtime/launch-worktree.test.ts +248 -0
- package/src/gjc-runtime/launch-worktree.ts +275 -38
- package/src/gjc-runtime/repository-binding.ts +26 -1
- package/src/gjc-runtime/session-layout.ts +10 -0
- package/src/gjc-runtime/session-state-lock.ts +330 -23
- package/src/gjc-runtime/session-state-sidecar.ts +328 -80
- package/src/gjc-runtime/ultragoal-runtime.ts +10 -1
- package/src/harness-control-plane/session-lease.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +28 -26
- package/src/main.ts +150 -29
- package/src/memories/index.ts +24 -2
- package/src/modes/acp/acp-agent.ts +522 -68
- package/src/modes/components/agent-dashboard.ts +3 -1
- package/src/modes/components/model-selector.ts +28 -19
- package/src/modes/components/plugin-settings.ts +2 -1
- package/src/modes/controllers/selector-controller.ts +15 -9
- package/src/modes/interactive-mode.ts +21 -3
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/move-session.md +4 -0
- package/src/prompts/tools/read.md +2 -1
- package/src/prompts/tools/skill-discovery.md +6 -5
- package/src/prompts/tools/task.md +1 -0
- package/src/runtime-mcp/config-writer.ts +228 -0
- package/src/runtime-mcp/oauth-discovery.ts +114 -14
- package/src/runtime-mcp/policy-json-edit.ts +129 -0
- package/src/runtime-mcp/startup-policy.ts +14 -0
- package/src/runtime-mcp/types.ts +2 -2
- package/src/sdk/acp/adapter.ts +27 -12
- package/src/sdk/broker/broker.ts +273 -44
- package/src/sdk/broker/daemon-entry.ts +119 -0
- package/src/sdk/broker/discovery.ts +173 -20
- package/src/sdk/broker/doctor-restart.ts +206 -0
- package/src/sdk/broker/endpoint-authority.ts +36 -0
- package/src/sdk/broker/ensure.ts +49 -14
- package/src/sdk/broker/lifecycle.ts +174 -42
- package/src/sdk/broker/process-incarnation.ts +86 -2
- package/src/sdk/broker/runtime.ts +145 -21
- package/src/sdk/broker/session-index.ts +136 -22
- package/src/sdk/broker/spawn-substrate.ts +4 -1
- package/src/sdk/broker/transport.ts +5 -0
- package/src/sdk/broker/worktree-occupancy.ts +21 -1
- package/src/sdk/bus/chat-daemon-cli.ts +63 -0
- package/src/sdk/bus/chat-daemon-control.ts +260 -64
- package/src/sdk/bus/chat-daemon-runtime.ts +141 -25
- package/src/sdk/bus/daemon-startup-exclusion.ts +119 -0
- package/src/sdk/bus/doctor-daemon-restart-client.ts +291 -0
- package/src/sdk/bus/doctor-daemon-restart.ts +92 -0
- package/src/sdk/bus/index.ts +252 -51
- package/src/sdk/bus/kind-aware-reconciliation.ts +76 -41
- package/src/sdk/bus/notification-service.ts +21 -3
- package/src/sdk/bus/reconciliation-store.ts +17 -11
- package/src/sdk/bus/telegram-daemon-cli.ts +91 -5
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/bus/telegram-daemon-control.ts +47 -0
- package/src/sdk/bus/telegram-daemon-owner-registry.ts +1 -1
- package/src/sdk/bus/telegram-daemon.ts +178 -13
- package/src/sdk/cli/session-cli.ts +100 -12
- package/src/sdk/host/host.ts +2 -7
- package/src/sdk/host/session-runtime.test.ts +329 -8
- package/src/sdk/host/session-runtime.ts +202 -50
- package/src/sdk/host/surface-policy.ts +8 -1
- package/src/sdk/lifecycle/service.ts +199 -22
- package/src/sdk/mcp/server.ts +1 -1
- package/src/sdk/prompt-deadline-lease.ts +12 -4
- package/src/sdk/prompt-deadline-manager.ts +5 -7
- package/src/sdk/prompt-failure.ts +148 -0
- package/src/sdk/prompt-status.ts +30 -1
- package/src/sdk/prompt-watchdog.ts +11 -3
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +20 -8
- package/src/sdk/router/session-router.ts +8 -3
- package/src/sdk/service-artifact-paths.ts +26 -0
- package/src/sdk/session-authority.ts +10 -0
- package/src/sdk/session.ts +361 -194
- package/src/session/agent-session.ts +398 -93
- package/src/session/blob-store.ts +9 -9
- package/src/session/fallback-chain-controller.ts +3 -7
- package/src/session/internal/managed-session-scope.ts +445 -29
- package/src/session/internal/managed-session-storage.ts +38 -6
- package/src/session/primary-control-surface.ts +12 -0
- package/src/session/session-manager.ts +11 -3
- package/src/session/session-storage.ts +1 -1
- package/src/session-import/codex.ts +6 -1
- package/src/session-import/redact.ts +7 -0
- package/src/skill-state/workflow-mutation-guard.ts +645 -39
- package/src/slash-commands/builtin-registry.ts +7 -3
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +36 -3
- package/src/task/types.ts +27 -5
- package/src/tools/ask.ts +114 -34
- package/src/tools/browser/actions.ts +2 -2
- package/src/tools/browser/tab-worker.ts +11 -8
- package/src/tools/descriptors.ts +6 -1
- package/src/tools/fetch.ts +7 -2
- package/src/tools/image-gen.ts +33 -34
- package/src/tools/read.ts +78 -32
- package/src/tools/skill-discovery.ts +17 -7
- package/src/tools/tool-catalog.generated.ts +8 -8
- package/src/tools/ultragoal-ask-guard.ts +16 -26
- package/src/utils/changelog.ts +7 -1
- package/src/utils/git.ts +3 -1
- package/src/utils/markit.ts +24 -2
- package/src/utils/mupdf-wasm.ts +26 -0
- package/src/web/search/providers/openai-compatible.ts +191 -14
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
type ManagedAttemptDecision,
|
|
43
43
|
type ManagedAttemptOutcome,
|
|
44
44
|
type MidRunMaintenanceOutcome,
|
|
45
|
+
markNonDispatchedToolEvent,
|
|
45
46
|
type RunCancellationDomain,
|
|
46
47
|
type RunCancellationDomainBridge,
|
|
47
48
|
type RunResourceProducerLease,
|
|
@@ -114,6 +115,7 @@ import {
|
|
|
114
115
|
isContextOverflow,
|
|
115
116
|
isFastModeEffectiveForProvider,
|
|
116
117
|
isUsageLimitError,
|
|
118
|
+
modelSupportsMaintenanceCalls,
|
|
117
119
|
modelSupportsServiceTier,
|
|
118
120
|
modelsAreEqual,
|
|
119
121
|
streamSimple,
|
|
@@ -315,10 +317,14 @@ import {
|
|
|
315
317
|
import {
|
|
316
318
|
assertNonEmptyGjcSessionId,
|
|
317
319
|
modeStatePath as sessionModeStatePath,
|
|
318
|
-
|
|
320
|
+
sessionRuntimeStatePath,
|
|
319
321
|
sessionStateDir,
|
|
320
322
|
} from "../gjc-runtime/session-layout";
|
|
321
|
-
import {
|
|
323
|
+
import {
|
|
324
|
+
isRetryableSessionStateLockContention,
|
|
325
|
+
sessionStateLockFailureFields,
|
|
326
|
+
shouldWarnPersistFailure,
|
|
327
|
+
} from "../gjc-runtime/session-state-lock";
|
|
322
328
|
import {
|
|
323
329
|
type CoordinatorToolObservation,
|
|
324
330
|
clearCoordinatorRuntimeStateRescope,
|
|
@@ -947,7 +953,7 @@ export interface AgentSessionConfig {
|
|
|
947
953
|
discoveryMode?: "off" | "mcp-only" | "all";
|
|
948
954
|
/** MCP tool names to activate for the current session when discovery mode is enabled. */
|
|
949
955
|
initialSelectedMCPToolNames?: string[];
|
|
950
|
-
/** Keep persisted MCP names until a deferred exact catalog becomes available. */
|
|
956
|
+
/** Keep persisted MCP names until a deferred (exact or conventional) catalog becomes available. */
|
|
951
957
|
preserveUnavailableInitialMCPToolSelection?: boolean;
|
|
952
958
|
/** Built-in discoverable tool names restored for the current all-discovery session. */
|
|
953
959
|
initialSelectedDiscoveredBuiltinToolNames?: string[];
|
|
@@ -1673,13 +1679,10 @@ function dedupeIrcReply(text: string): string {
|
|
|
1673
1679
|
}
|
|
1674
1680
|
let result = out.join("\n");
|
|
1675
1681
|
if (Buffer.byteLength(result, "utf8") > IRC_REPLY_MAX_BYTES) {
|
|
1676
|
-
//
|
|
1677
|
-
// glyphs at the boundary without splitting them.
|
|
1682
|
+
// Bound the UTF-8 prefix without repeatedly measuring the shrinking reply.
|
|
1678
1683
|
const suffix = "\n[…truncated]";
|
|
1679
1684
|
const budget = IRC_REPLY_MAX_BYTES - Buffer.byteLength(suffix, "utf8");
|
|
1680
|
-
|
|
1681
|
-
result = result.slice(0, -1);
|
|
1682
|
-
}
|
|
1685
|
+
result = truncateHeadBytes(result, budget).text;
|
|
1683
1686
|
result += suffix;
|
|
1684
1687
|
}
|
|
1685
1688
|
return result;
|
|
@@ -2032,6 +2035,20 @@ export type BeforeAgentStartContributor = (event: {
|
|
|
2032
2035
|
const AGENT_END_WORKER_INTEGRATION_TIMEOUT_MS = 5_000;
|
|
2033
2036
|
const POST_PUBLICATION_ERROR_MAX_BYTES = 512;
|
|
2034
2037
|
|
|
2038
|
+
/**
|
|
2039
|
+
* Bounded repair for a coordinator runtime-state update that lost its lock outright.
|
|
2040
|
+
*
|
|
2041
|
+
* The lock itself already waits out a busy peer, so reaching this path means a whole
|
|
2042
|
+
* acquisition budget elapsed without the lock ever moving. Retrying covers the remaining
|
|
2043
|
+
* case that budget cannot: a holder whose single critical section legitimately outlives
|
|
2044
|
+
* it. The delay is far longer than the lock's own backoff because the contention being
|
|
2045
|
+
* waited out is a whole foreign write, not a scheduling hiccup, and the retries stay small
|
|
2046
|
+
* so a persistently wedged lock is still reported promptly instead of stalling the
|
|
2047
|
+
* session's persist queue.
|
|
2048
|
+
*/
|
|
2049
|
+
const COORDINATOR_PERSIST_CONTENTION_RETRIES = 3;
|
|
2050
|
+
const COORDINATOR_PERSIST_RETRY_DELAY_MS = 2_000;
|
|
2051
|
+
|
|
2035
2052
|
export type WorkerIntegrationOutcome =
|
|
2036
2053
|
| { status: "completed" }
|
|
2037
2054
|
| { status: "failed"; error: string }
|
|
@@ -2876,6 +2893,7 @@ export class AgentSession {
|
|
|
2876
2893
|
this.#retryReplayUnsafeEpoch = undefined;
|
|
2877
2894
|
this.#firstEventTimeoutRetryStartedAt = Date.now();
|
|
2878
2895
|
this.#providerRetryMaxAttempts = undefined;
|
|
2896
|
+
this.#codexCredentialModelUnavailableRetried = false;
|
|
2879
2897
|
}
|
|
2880
2898
|
|
|
2881
2899
|
#markRetryReplayUnsafe(): void {
|
|
@@ -2914,6 +2932,8 @@ export class AgentSession {
|
|
|
2914
2932
|
#retryNowRequested = false;
|
|
2915
2933
|
#firstEventTimeoutRetryStartedAt: number | undefined;
|
|
2916
2934
|
#providerRetryMaxAttempts: number | undefined;
|
|
2935
|
+
/** One content-free retry is allowed after Codex says the active account lacks the model. */
|
|
2936
|
+
#codexCredentialModelUnavailableRetried = false;
|
|
2917
2937
|
#retryAttempt = 0;
|
|
2918
2938
|
#retryPromise: Promise<void> | undefined = undefined;
|
|
2919
2939
|
#retryResolve: (() => void) | undefined = undefined;
|
|
@@ -3048,11 +3068,14 @@ export class AgentSession {
|
|
|
3048
3068
|
*/
|
|
3049
3069
|
#registerRuntimeStateFinalizer(): void {
|
|
3050
3070
|
this.#unregisterRuntimeStateFinalizer?.();
|
|
3051
|
-
const currentContext = () =>
|
|
3052
|
-
sessionId: this.sessionId,
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3071
|
+
const currentContext = () => {
|
|
3072
|
+
const identity = { sessionId: this.sessionId, cwd: this.sessionManager.getCwd() };
|
|
3073
|
+
return {
|
|
3074
|
+
...identity,
|
|
3075
|
+
sessionFile: this.sessionManager.getSessionFile(),
|
|
3076
|
+
stateFile: this.#runtimeStateMarkerFile(identity),
|
|
3077
|
+
};
|
|
3078
|
+
};
|
|
3056
3079
|
this.#unregisterRuntimeStateFinalizer = registerCoordinatorRuntimeStateFinalizer(
|
|
3057
3080
|
currentContext(),
|
|
3058
3081
|
currentContext,
|
|
@@ -4399,6 +4422,11 @@ export class AgentSession {
|
|
|
4399
4422
|
this.agent.setProvisionalAssistantMessageEventInterceptor((message, assistantMessageEvent) => {
|
|
4400
4423
|
const contentIndex = assistantMessageEvent.contentIndex ?? 0;
|
|
4401
4424
|
const block = message.content[contentIndex];
|
|
4425
|
+
if (
|
|
4426
|
+
block?.type === "toolCall" &&
|
|
4427
|
+
(block.escapedNonAsciiArguments === true || block.escapedUnicodeArgumentEvidence !== undefined)
|
|
4428
|
+
)
|
|
4429
|
+
return false;
|
|
4402
4430
|
if (block?.type === "toolCall" && block.id) this.#provisionalStreamingToolCallIds.add(block.id);
|
|
4403
4431
|
if (
|
|
4404
4432
|
assistantMessageEvent.type !== "toolcall_start" &&
|
|
@@ -4467,6 +4495,9 @@ export class AgentSession {
|
|
|
4467
4495
|
newCwdIdentity: move.newCwdIdentity,
|
|
4468
4496
|
previousSessionFile: move.previousSessionFile ?? null,
|
|
4469
4497
|
newSessionFile: move.newSessionFile ?? null,
|
|
4498
|
+
// The rescope family must decide pin-vs-derived from the marker THIS session
|
|
4499
|
+
// owns, never from the ambient pin (#5473).
|
|
4500
|
+
stateFile: this.#runtimeStateMarkerFile({ sessionId: this.sessionId, cwd: move.previousCwd }),
|
|
4470
4501
|
});
|
|
4471
4502
|
});
|
|
4472
4503
|
this.#unregisterMoveAbortListener = this.sessionManager.registerMoveAbortListener(async move => {
|
|
@@ -4478,6 +4509,7 @@ export class AgentSession {
|
|
|
4478
4509
|
sessionId: this.sessionId,
|
|
4479
4510
|
cwd: move.newCwd,
|
|
4480
4511
|
sessionFile: move.newSessionFile ?? null,
|
|
4512
|
+
stateFile: this.#runtimeStateMarkerFile({ sessionId: this.sessionId, cwd: move.newCwd }),
|
|
4481
4513
|
},
|
|
4482
4514
|
moveId,
|
|
4483
4515
|
move.previousCwd,
|
|
@@ -4496,6 +4528,7 @@ export class AgentSession {
|
|
|
4496
4528
|
sessionId: this.sessionId,
|
|
4497
4529
|
cwd: move.newCwd,
|
|
4498
4530
|
sessionFile: move.newSessionFile ?? null,
|
|
4531
|
+
stateFile: this.#runtimeStateMarkerFile({ sessionId: this.sessionId, cwd: move.newCwd }),
|
|
4499
4532
|
},
|
|
4500
4533
|
move.previousCwd,
|
|
4501
4534
|
moveId,
|
|
@@ -4507,14 +4540,19 @@ export class AgentSession {
|
|
|
4507
4540
|
// fence refuses every later persist) and rebinds the postmortem finalizer, which
|
|
4508
4541
|
// otherwise keeps writing terminal state to the launch root's cwd/session file.
|
|
4509
4542
|
this.#unregisterAfterMoveListener = this.sessionManager.registerAfterMoveListener(async move => {
|
|
4510
|
-
let completed = false;
|
|
4511
4543
|
try {
|
|
4544
|
+
// A move can promote an explicit --session-dir destination to managed
|
|
4545
|
+
// storage without changing the session id. Its local root changes from
|
|
4546
|
+
// artifacts/local to scratch, so complete migration before releasing
|
|
4547
|
+
// the rescope barrier or allowing the next prompt's sync resolver.
|
|
4548
|
+
await initializeLocalRoot(this.#localProtocolOptions());
|
|
4512
4549
|
const relocated = await relocateCoordinatorRuntimeStateForRescope(
|
|
4513
4550
|
{
|
|
4514
4551
|
sessionId: this.sessionId,
|
|
4515
4552
|
cwd: move.newCwd,
|
|
4516
4553
|
sessionFile: this.sessionManager.getSessionFile() ?? null,
|
|
4517
4554
|
previousSessionFile: move.previousSessionFile ?? null,
|
|
4555
|
+
stateFile: this.#runtimeStateMarkerFile({ sessionId: this.sessionId, cwd: move.newCwd }),
|
|
4518
4556
|
},
|
|
4519
4557
|
move.previousCwd,
|
|
4520
4558
|
);
|
|
@@ -4524,17 +4562,50 @@ export class AgentSession {
|
|
|
4524
4562
|
sessionId: this.sessionId,
|
|
4525
4563
|
cwd: move.newCwd,
|
|
4526
4564
|
sessionFile: this.sessionManager.getSessionFile() ?? null,
|
|
4565
|
+
stateFile: this.#runtimeStateMarkerFile({ sessionId: this.sessionId, cwd: move.newCwd }),
|
|
4527
4566
|
},
|
|
4528
4567
|
this.#coordinatorRescopeMoveId,
|
|
4529
4568
|
move.previousCwd,
|
|
4530
4569
|
);
|
|
4531
|
-
|
|
4570
|
+
} catch (error) {
|
|
4571
|
+
// Publication has already committed, so the move-abort listener cannot
|
|
4572
|
+
// recover this state. Retry coordinator relocation independently of the
|
|
4573
|
+
// failed local-root gate so later persists cannot remain behind a barrier
|
|
4574
|
+
// that no longer has a rollback path.
|
|
4575
|
+
try {
|
|
4576
|
+
const relocated = await relocateCoordinatorRuntimeStateForRescope(
|
|
4577
|
+
{
|
|
4578
|
+
sessionId: this.sessionId,
|
|
4579
|
+
cwd: move.newCwd,
|
|
4580
|
+
sessionFile: this.sessionManager.getSessionFile() ?? null,
|
|
4581
|
+
previousSessionFile: move.previousSessionFile ?? null,
|
|
4582
|
+
stateFile: this.#runtimeStateMarkerFile({ sessionId: this.sessionId, cwd: move.newCwd }),
|
|
4583
|
+
},
|
|
4584
|
+
move.previousCwd,
|
|
4585
|
+
);
|
|
4586
|
+
if (!relocated) throw new Error("Coordinator runtime state rescope recovery was refused.");
|
|
4587
|
+
await clearCoordinatorRuntimeStateRescope(
|
|
4588
|
+
{
|
|
4589
|
+
sessionId: this.sessionId,
|
|
4590
|
+
cwd: move.newCwd,
|
|
4591
|
+
sessionFile: this.sessionManager.getSessionFile() ?? null,
|
|
4592
|
+
stateFile: this.#runtimeStateMarkerFile({ sessionId: this.sessionId, cwd: move.newCwd }),
|
|
4593
|
+
},
|
|
4594
|
+
this.#coordinatorRescopeMoveId,
|
|
4595
|
+
move.previousCwd,
|
|
4596
|
+
);
|
|
4597
|
+
} catch (recoveryError) {
|
|
4598
|
+
logger.error("Failed to recover coordinator runtime state after committed session move", {
|
|
4599
|
+
cwd: move.newCwd,
|
|
4600
|
+
error: String(error),
|
|
4601
|
+
recoveryError: String(recoveryError),
|
|
4602
|
+
});
|
|
4603
|
+
}
|
|
4604
|
+
throw error;
|
|
4532
4605
|
} finally {
|
|
4533
4606
|
this.#registerRuntimeStateFinalizer();
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
this.#endCoordinatorRescopeBarrier();
|
|
4537
|
-
}
|
|
4607
|
+
this.#coordinatorRescopeMoveId = undefined;
|
|
4608
|
+
this.#endCoordinatorRescopeBarrier();
|
|
4538
4609
|
}
|
|
4539
4610
|
});
|
|
4540
4611
|
// Power assertions are taken per turn (see #beginInFlight); nothing acquired here.
|
|
@@ -4553,6 +4624,10 @@ export class AgentSession {
|
|
|
4553
4624
|
sessionId: this.sessionId,
|
|
4554
4625
|
cwd: this.sessionManager.getCwd(),
|
|
4555
4626
|
sessionFile: this.sessionManager.getSessionFile() ?? null,
|
|
4627
|
+
stateFile: this.#runtimeStateMarkerFile({
|
|
4628
|
+
sessionId: this.sessionId,
|
|
4629
|
+
cwd: this.sessionManager.getCwd(),
|
|
4630
|
+
}),
|
|
4556
4631
|
};
|
|
4557
4632
|
if (hasCoordinatorRuntimeStateRescopeJournal(rescopeRecoveryContext)) {
|
|
4558
4633
|
this.extendStartupTurnBarrier(recoverCoordinatorRuntimeStateRescope(rescopeRecoveryContext));
|
|
@@ -6120,19 +6195,30 @@ export class AgentSession {
|
|
|
6120
6195
|
);
|
|
6121
6196
|
}
|
|
6122
6197
|
|
|
6198
|
+
/**
|
|
6199
|
+
* The runtime-state marker THIS session owns, or `null` when it has none.
|
|
6200
|
+
*
|
|
6201
|
+
* The process-wide `GJC_COORDINATOR_SESSION_STATE_FILE` pin describes exactly one session:
|
|
6202
|
+
* the one the launcher minted it for. A nested in-process session — the role-agent/subagent
|
|
6203
|
+
* fan-out — is a different session, so it must never read or write the parent's marker. The
|
|
6204
|
+
* sidecar's identity fence refuses every such write (the reported 102x rejection while a
|
|
6205
|
+
* fan-out was live), and a write that did land would rewrite the parent's lifecycle under
|
|
6206
|
+
* the child's identity. A nested session therefore owns its own session-derived marker.
|
|
6207
|
+
*/
|
|
6208
|
+
#runtimeStateMarkerFile(input: { sessionId: string; cwd: string }): string | null {
|
|
6209
|
+
if (!input.sessionId.trim()) return null;
|
|
6210
|
+
const pinned = process.env[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]?.trim();
|
|
6211
|
+
if (this.taskDepth > 0) return sessionRuntimeStatePath(input.cwd, input.sessionId);
|
|
6212
|
+
return pinned || sessionRuntimeStatePath(input.cwd, input.sessionId);
|
|
6213
|
+
}
|
|
6214
|
+
|
|
6123
6215
|
#captureCoordinatorRuntimeStatePersistContext(): CoordinatorRuntimeStatePersistContext {
|
|
6124
6216
|
const context = {
|
|
6125
6217
|
sessionId: this.sessionId,
|
|
6126
6218
|
cwd: this.sessionManager.getCwd(),
|
|
6127
6219
|
sessionFile: this.sessionManager.getSessionFile(),
|
|
6128
6220
|
};
|
|
6129
|
-
|
|
6130
|
-
const stateFile =
|
|
6131
|
-
explicitStateFile ||
|
|
6132
|
-
(context.sessionId.trim()
|
|
6133
|
-
? path.join(sessionRuntimeDir(context.cwd, context.sessionId), "runtime-state.json")
|
|
6134
|
-
: null);
|
|
6135
|
-
return { ...context, stateFile };
|
|
6221
|
+
return { ...context, stateFile: this.#runtimeStateMarkerFile(context) };
|
|
6136
6222
|
}
|
|
6137
6223
|
|
|
6138
6224
|
/**
|
|
@@ -6179,17 +6265,30 @@ export class AgentSession {
|
|
|
6179
6265
|
observation: CoordinatorToolObservation | undefined,
|
|
6180
6266
|
propagateFailure: boolean,
|
|
6181
6267
|
): Promise<void> {
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6268
|
+
for (let attempt = 0; ; attempt++) {
|
|
6269
|
+
try {
|
|
6270
|
+
await persistCoordinatorRuntimeStateFromEvent(event, context, observation);
|
|
6271
|
+
return;
|
|
6272
|
+
} catch (error) {
|
|
6273
|
+
// Losing the lock drops this update permanently: nothing re-derives the
|
|
6274
|
+
// snapshot afterwards, so the coordinator keeps reporting stale lifecycle
|
|
6275
|
+
// and tool activity for this session until an unrelated event happens to
|
|
6276
|
+
// win. A pure-contention refusal is therefore retried before it is
|
|
6277
|
+
// reported; every other failure is a standing condition, reported at once.
|
|
6278
|
+
if (attempt < COORDINATOR_PERSIST_CONTENTION_RETRIES && isRetryableSessionStateLockContention(error)) {
|
|
6279
|
+
await Bun.sleep(COORDINATOR_PERSIST_RETRY_DELAY_MS);
|
|
6280
|
+
continue;
|
|
6281
|
+
}
|
|
6282
|
+
this.#warnPersistFailure(
|
|
6283
|
+
"Failed to persist coordinator runtime state",
|
|
6284
|
+
error,
|
|
6285
|
+
context.stateFile,
|
|
6286
|
+
context.sessionId,
|
|
6287
|
+
{ event: event.type, ...(attempt > 0 ? { retries: attempt } : {}) },
|
|
6288
|
+
);
|
|
6289
|
+
if (propagateFailure) throw error;
|
|
6290
|
+
return;
|
|
6291
|
+
}
|
|
6193
6292
|
}
|
|
6194
6293
|
}
|
|
6195
6294
|
|
|
@@ -6371,6 +6470,18 @@ export class AgentSession {
|
|
|
6371
6470
|
): Promise<void> => {
|
|
6372
6471
|
const attemptScope = (event as AgentEvent & { scope?: AttemptScope }).scope;
|
|
6373
6472
|
|
|
6473
|
+
// These lifecycle boundaries can be delivered without awaiting this listener.
|
|
6474
|
+
// Revoke streaming-edit cache generations before any admission, spill, or
|
|
6475
|
+
// extension work so a completed read cannot publish across the boundary.
|
|
6476
|
+
if (event.type === "turn_start") this.#resetStreamingEditState();
|
|
6477
|
+
if (event.type === "message_end" && event.message.role === "toolResult") {
|
|
6478
|
+
const details = event.message.details;
|
|
6479
|
+
if (event.message.toolName === "edit" && details && typeof details === "object" && "path" in details) {
|
|
6480
|
+
const editPath = (details as { path?: unknown }).path;
|
|
6481
|
+
if (typeof editPath === "string") this.#invalidateFileCacheForPath(editPath);
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
|
|
6374
6485
|
if (
|
|
6375
6486
|
event.type === "tool_execution_start" ||
|
|
6376
6487
|
event.type === "tool_execution_update" ||
|
|
@@ -6725,7 +6836,6 @@ export class AgentSession {
|
|
|
6725
6836
|
}
|
|
6726
6837
|
|
|
6727
6838
|
if (event.type === "turn_start") {
|
|
6728
|
-
this.#resetStreamingEditState();
|
|
6729
6839
|
// TTSR: Reset buffer on turn start
|
|
6730
6840
|
this.#ttsrManager?.resetBuffer();
|
|
6731
6841
|
}
|
|
@@ -6969,10 +7079,6 @@ export class AgentSession {
|
|
|
6969
7079
|
isError?: boolean;
|
|
6970
7080
|
content?: Array<TextContent | ImageContent>;
|
|
6971
7081
|
};
|
|
6972
|
-
// Invalidate streaming edit cache when edit tool completes to prevent stale data
|
|
6973
|
-
if (toolName === "edit" && details?.path) {
|
|
6974
|
-
this.#invalidateFileCacheForPath(details.path);
|
|
6975
|
-
}
|
|
6976
7082
|
if (toolName === "todo_write" && !isError && Array.isArray(details?.phases)) {
|
|
6977
7083
|
this.setTodoPhases(details.phases);
|
|
6978
7084
|
}
|
|
@@ -8220,6 +8326,9 @@ export class AgentSession {
|
|
|
8220
8326
|
this.#streamingEditPrecheckedToolCallIds.clear();
|
|
8221
8327
|
this.#streamingEditParsedToolCallCache.clear();
|
|
8222
8328
|
this.#streamingEditFileCache.clear();
|
|
8329
|
+
for (const resolvedPath of this.#streamingEditPrecachePending.keys()) {
|
|
8330
|
+
this.#invalidateStreamingEditPrecachePath(resolvedPath);
|
|
8331
|
+
}
|
|
8223
8332
|
}
|
|
8224
8333
|
|
|
8225
8334
|
#getStreamingEditToolCall(event: AgentEvent): StreamingEditParsedToolCall | undefined {
|
|
@@ -8300,26 +8409,50 @@ export class AgentSession {
|
|
|
8300
8409
|
return block?.type === "toolCall" && this.#provisionalStreamingToolCallIds.has(block.id);
|
|
8301
8410
|
}
|
|
8302
8411
|
|
|
8303
|
-
#streamingEditPrecachePending = new
|
|
8412
|
+
#streamingEditPrecachePending = new Map<string, symbol>();
|
|
8413
|
+
#streamingEditPrecacheStale = new Map<string, Set<symbol>>();
|
|
8304
8414
|
async #preCacheFileAsync(resolvedPath: string): Promise<void> {
|
|
8305
8415
|
if (this.#streamingEditFileCache.has(resolvedPath)) return;
|
|
8306
8416
|
if (this.#streamingEditPrecachePending.has(resolvedPath)) return;
|
|
8307
|
-
|
|
8417
|
+
// Keep at most one invalidated read alongside the current generation. Once
|
|
8418
|
+
// two reads are already in flight, further invalidations wait for one to
|
|
8419
|
+
// settle instead of accumulating another whole-file read.
|
|
8420
|
+
if ((this.#streamingEditPrecacheStale.get(resolvedPath)?.size ?? 0) >= 2) return;
|
|
8421
|
+
const token = Symbol();
|
|
8422
|
+
this.#streamingEditPrecachePending.set(resolvedPath, token);
|
|
8308
8423
|
try {
|
|
8309
8424
|
const stat = await fs.promises.stat(resolvedPath);
|
|
8310
8425
|
if (stat.size > MAX_EDIT_FILE_BYTES) return;
|
|
8311
8426
|
|
|
8427
|
+
if (this.#streamingEditPrecachePending.get(resolvedPath) !== token) return;
|
|
8312
8428
|
const rawText = await fs.promises.readFile(resolvedPath, "utf-8");
|
|
8429
|
+
if (this.#streamingEditPrecachePending.get(resolvedPath) !== token) return;
|
|
8313
8430
|
if (this.#streamingEditFileCache.has(resolvedPath)) return;
|
|
8314
8431
|
const { text } = stripBom(rawText);
|
|
8315
8432
|
this.#streamingEditFileCache.set(resolvedPath, normalizeToLF(text));
|
|
8316
8433
|
} catch {
|
|
8317
8434
|
// Don't cache on read errors (including ENOENT) - let the edit tool handle them
|
|
8318
8435
|
} finally {
|
|
8319
|
-
this.#streamingEditPrecachePending.
|
|
8436
|
+
if (this.#streamingEditPrecachePending.get(resolvedPath) === token) {
|
|
8437
|
+
this.#streamingEditPrecachePending.delete(resolvedPath);
|
|
8438
|
+
}
|
|
8439
|
+
const stale = this.#streamingEditPrecacheStale.get(resolvedPath);
|
|
8440
|
+
if (stale?.delete(token) && stale.size === 0) this.#streamingEditPrecacheStale.delete(resolvedPath);
|
|
8320
8441
|
}
|
|
8321
8442
|
}
|
|
8322
8443
|
|
|
8444
|
+
#invalidateStreamingEditPrecachePath(resolvedPath: string): void {
|
|
8445
|
+
const token = this.#streamingEditPrecachePending.get(resolvedPath);
|
|
8446
|
+
if (token === undefined) return;
|
|
8447
|
+
this.#streamingEditPrecachePending.delete(resolvedPath);
|
|
8448
|
+
let stale = this.#streamingEditPrecacheStale.get(resolvedPath);
|
|
8449
|
+
if (!stale) {
|
|
8450
|
+
stale = new Set();
|
|
8451
|
+
this.#streamingEditPrecacheStale.set(resolvedPath, stale);
|
|
8452
|
+
}
|
|
8453
|
+
stale.add(token);
|
|
8454
|
+
}
|
|
8455
|
+
|
|
8323
8456
|
#ensureFileCache(resolvedPath: string): void {
|
|
8324
8457
|
if (this.#streamingEditFileCache.has(resolvedPath)) return;
|
|
8325
8458
|
|
|
@@ -8337,9 +8470,27 @@ export class AgentSession {
|
|
|
8337
8470
|
|
|
8338
8471
|
/** Invalidate cache for a file after an edit completes to prevent stale data */
|
|
8339
8472
|
#invalidateFileCacheForPath(filePath: string): void {
|
|
8340
|
-
|
|
8473
|
+
let resolvedPath: string | undefined;
|
|
8474
|
+
try {
|
|
8475
|
+
resolvedPath = this.#resolveSessionFsPath(filePath);
|
|
8476
|
+
} catch (error) {
|
|
8477
|
+
// Tool-result admission must survive malformed paths and unavailable local roots.
|
|
8478
|
+
// Without a resolved key, revoke every generation before any lifecycle await.
|
|
8479
|
+
this.#streamingEditFileCache.clear();
|
|
8480
|
+
this.#streamingEditPrecachePending.delete(filePath);
|
|
8481
|
+
this.#streamingEditPrecacheStale.delete(filePath);
|
|
8482
|
+
for (const pendingPath of this.#streamingEditPrecachePending.keys()) {
|
|
8483
|
+
this.#invalidateStreamingEditPrecachePath(pendingPath);
|
|
8484
|
+
}
|
|
8485
|
+
logger.debug("Failed to resolve streaming-edit cache invalidation path", {
|
|
8486
|
+
path: filePath,
|
|
8487
|
+
error: String(error),
|
|
8488
|
+
});
|
|
8489
|
+
return;
|
|
8490
|
+
}
|
|
8341
8491
|
if (resolvedPath === undefined) return;
|
|
8342
8492
|
this.#streamingEditFileCache.delete(resolvedPath);
|
|
8493
|
+
this.#invalidateStreamingEditPrecachePath(resolvedPath);
|
|
8343
8494
|
}
|
|
8344
8495
|
|
|
8345
8496
|
/**
|
|
@@ -8789,6 +8940,7 @@ export class AgentSession {
|
|
|
8789
8940
|
args: event.args,
|
|
8790
8941
|
intent: event.intent,
|
|
8791
8942
|
};
|
|
8943
|
+
if (isNonDispatchedToolEvent(event)) markNonDispatchedToolEvent(extensionEvent);
|
|
8792
8944
|
await this.#extensionRunner.emit(extensionEvent, undefined, deliveryScope);
|
|
8793
8945
|
} else if (event.type === "tool_execution_update") {
|
|
8794
8946
|
const extensionEvent: ToolExecutionUpdateEvent = {
|
|
@@ -8807,6 +8959,7 @@ export class AgentSession {
|
|
|
8807
8959
|
result: event.result,
|
|
8808
8960
|
isError: event.isError ?? false,
|
|
8809
8961
|
};
|
|
8962
|
+
if (isNonDispatchedToolEvent(event)) markNonDispatchedToolEvent(extensionEvent);
|
|
8810
8963
|
await this.#extensionRunner.emit(extensionEvent, undefined, deliveryScope);
|
|
8811
8964
|
} else if (event.type === "auto_compaction_start") {
|
|
8812
8965
|
await this.#extensionRunner.emit(
|
|
@@ -10281,13 +10434,13 @@ export class AgentSession {
|
|
|
10281
10434
|
const bridge = this.#clientBridge;
|
|
10282
10435
|
const acpEnabled = Boolean(bridge?.capabilities.requestPermission && bridge.requestPermission);
|
|
10283
10436
|
const sdkEnabled = this.#sdkPermissionProvider !== undefined;
|
|
10284
|
-
|
|
10285
|
-
|
|
10437
|
+
// The wrapped behavior depends only on live session state (cwd, session id,
|
|
10438
|
+
// session agent dir) plus the ACP/SDK permission surfaces below. Active-skill
|
|
10439
|
+
// state is deliberately absent: the Ultragoal ask guard binds to the caller
|
|
10440
|
+
// session id alone, so a skill transition does not change any wrapper.
|
|
10286
10441
|
return [
|
|
10287
10442
|
"workflow-mutation-v1",
|
|
10288
|
-
|
|
10289
|
-
"ultragoal-ask-v1",
|
|
10290
|
-
`active=${activeSkill}:${activeSkillSession}`,
|
|
10443
|
+
"ultragoal-ask-v2",
|
|
10291
10444
|
`acp=${acpEnabled ? "on" : "off"}:sdk=${sdkEnabled ? "on" : "off"}:${this.#acpPermissionWrapperVersion}`,
|
|
10292
10445
|
].join("|");
|
|
10293
10446
|
}
|
|
@@ -10297,6 +10450,7 @@ export class AgentSession {
|
|
|
10297
10450
|
let wrappersByVersion = this.#guardedToolWrapperCache.get(tool);
|
|
10298
10451
|
const cached = wrappersByVersion?.get(cacheKey);
|
|
10299
10452
|
if (cached) return cached as T;
|
|
10453
|
+
wrappersByVersion?.clear();
|
|
10300
10454
|
const innerTool = tool instanceof ExtensionToolWrapper ? tool.getInnerTool() : tool;
|
|
10301
10455
|
const guarded = this.#wrapToolForCwdTransitionFence(
|
|
10302
10456
|
this.#wrapToolForWorkflowMutationGuard(
|
|
@@ -10304,10 +10458,7 @@ export class AgentSession {
|
|
|
10304
10458
|
guardToolForUltragoalAsk(
|
|
10305
10459
|
innerTool,
|
|
10306
10460
|
() => this.sessionManager.getCwd(),
|
|
10307
|
-
() => ({
|
|
10308
|
-
activeSkillState: this.getActiveSkillState(),
|
|
10309
|
-
sessionId: this.sessionManager.getSessionId(),
|
|
10310
|
-
}),
|
|
10461
|
+
() => ({ sessionId: this.sessionManager.getSessionId() }),
|
|
10311
10462
|
() => this.getSessionAgentDir(),
|
|
10312
10463
|
),
|
|
10313
10464
|
),
|
|
@@ -13089,11 +13240,12 @@ export class AgentSession {
|
|
|
13089
13240
|
claimsGenuineUserIntent: options?.claimsGenuineUserIntent,
|
|
13090
13241
|
onPromoted: options?.onPromoted,
|
|
13091
13242
|
sdkRunToken: options?.sdkRunToken,
|
|
13243
|
+
scheduleNonAdmittedWake: false,
|
|
13092
13244
|
onQueued: message => {
|
|
13093
13245
|
if (this.#abortUnwind && !options?.forceOneAtATime) this.#abortUnwindSteerFallbacks.push(message);
|
|
13094
13246
|
},
|
|
13095
13247
|
});
|
|
13096
|
-
this.#
|
|
13248
|
+
this.#scheduleNonAdmittedQueuedContinuation();
|
|
13097
13249
|
return;
|
|
13098
13250
|
}
|
|
13099
13251
|
if (options?.forceOneAtATime) this.#sequentialSteerMessages.add(message);
|
|
@@ -13111,15 +13263,14 @@ export class AgentSession {
|
|
|
13111
13263
|
}
|
|
13112
13264
|
|
|
13113
13265
|
/**
|
|
13114
|
-
* A
|
|
13266
|
+
* A queued message that was not admitted to the live loop needs a wakeup when
|
|
13267
|
+
* the loop has already ended but the session is still unwinding its prompt.
|
|
13115
13268
|
* `#queueFollowUp` already schedules delivery when the session is idle; this
|
|
13116
|
-
* covers the unwind window where the session still reports busy
|
|
13117
|
-
*
|
|
13118
|
-
*
|
|
13119
|
-
* loop, a prompt still in preflight, or a drained queue makes the scheduled
|
|
13120
|
-
* continue a no-op.
|
|
13269
|
+
* covers the unwind window where the public session still reports busy even
|
|
13270
|
+
* though no Agent loop owns the queue. A live loop, a prompt still in
|
|
13271
|
+
* preflight, or a drained queue makes the scheduled continue a no-op.
|
|
13121
13272
|
*/
|
|
13122
|
-
#
|
|
13273
|
+
#scheduleNonAdmittedQueuedContinuation(): void {
|
|
13123
13274
|
// `#queueFollowUp` already schedules delivery when the session is idle; this
|
|
13124
13275
|
// only covers the window where that gate refuses (a finished prompt is
|
|
13125
13276
|
// unwinding) while no loop owns the queue. The resumable-tail requirement
|
|
@@ -13158,6 +13309,7 @@ export class AgentSession {
|
|
|
13158
13309
|
onPromoted?: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
|
|
13159
13310
|
sdkRunToken?: string;
|
|
13160
13311
|
onQueued?: (message: AgentMessage) => void;
|
|
13312
|
+
scheduleNonAdmittedWake?: boolean;
|
|
13161
13313
|
},
|
|
13162
13314
|
): Promise<QueuedFollowUpOwner> {
|
|
13163
13315
|
this.#assertNoHandoffTransition();
|
|
@@ -13189,10 +13341,12 @@ export class AgentSession {
|
|
|
13189
13341
|
// without waiting for the next user turn. A later accepted follow-up must
|
|
13190
13342
|
// not start unrelated queued work ahead of it, because that work has a
|
|
13191
13343
|
// different cancellation and terminal owner.
|
|
13192
|
-
if (queueWasEmpty)
|
|
13344
|
+
if (queueWasEmpty) {
|
|
13193
13345
|
this.#scheduleQueuedFollowUpContinuation(() =>
|
|
13194
13346
|
this.agent.snapshotFollowUp().some(candidate => candidate === message),
|
|
13195
13347
|
);
|
|
13348
|
+
if (options?.scheduleNonAdmittedWake !== false) this.#scheduleNonAdmittedQueuedContinuation();
|
|
13349
|
+
}
|
|
13196
13350
|
return {
|
|
13197
13351
|
cancel: () => {
|
|
13198
13352
|
const deferredIndex = this.#deferredSdkFollowUps.indexOf(message);
|
|
@@ -19774,6 +19928,10 @@ export class AgentSession {
|
|
|
19774
19928
|
* provider session state and configured WebSocket transport preference
|
|
19775
19929
|
* instead of falling back to a fresh HTTP/SSE session. Mirrors the
|
|
19776
19930
|
* `providerSessionId ?? sessionId` affinity the agent loop sends per turn.
|
|
19931
|
+
*
|
|
19932
|
+
* `maintenanceCall` is what lets an agent-level provider (for example Devin
|
|
19933
|
+
* over ACP) refuse work it cannot serve instead of forwarding a
|
|
19934
|
+
* summarization prompt to a billed upstream agent.
|
|
19777
19935
|
*/
|
|
19778
19936
|
#maintenanceProviderTransport(): {
|
|
19779
19937
|
sessionId: string | undefined;
|
|
@@ -19781,6 +19939,7 @@ export class AgentSession {
|
|
|
19781
19939
|
providerSessionState: Map<string, ProviderSessionState>;
|
|
19782
19940
|
preferWebsockets: boolean | undefined;
|
|
19783
19941
|
remoteCompactionFallbackHealth: RemoteCompactionFallbackHealthHooks;
|
|
19942
|
+
maintenanceCall: boolean;
|
|
19784
19943
|
} {
|
|
19785
19944
|
const providerSessionId = this.agent.providerSessionId ?? this.agent.sessionId;
|
|
19786
19945
|
return {
|
|
@@ -19789,6 +19948,7 @@ export class AgentSession {
|
|
|
19789
19948
|
providerSessionState: this.#providerSessionState,
|
|
19790
19949
|
preferWebsockets: this.agent.preferWebsockets,
|
|
19791
19950
|
remoteCompactionFallbackHealth: this.#remoteCompactionFallbackHealth,
|
|
19951
|
+
maintenanceCall: true,
|
|
19792
19952
|
};
|
|
19793
19953
|
}
|
|
19794
19954
|
|
|
@@ -19933,6 +20093,35 @@ export class AgentSession {
|
|
|
19933
20093
|
// disabled/off settings so a resource-floor breach still compacts before OOM.
|
|
19934
20094
|
if (!options?.force && compactionSettings.strategy === "off") return { kind: "skipped" };
|
|
19935
20095
|
if (!options?.force && reason !== "idle" && !compactionSettings.enabled) return { kind: "skipped" };
|
|
20096
|
+
// Agent-level providers (e.g. Devin over ACP) own conversation history and
|
|
20097
|
+
// refuse GJC maintenance calls by contract. Context-full maintenance walks
|
|
20098
|
+
// the candidate chain while handoff generation calls the session model
|
|
20099
|
+
// directly, so when every model the selected action can reach is
|
|
20100
|
+
// agent-level the attempt can only report a guaranteed refusal on every
|
|
20101
|
+
// threshold crossing — treat it as a benign skip like maintenance being off.
|
|
20102
|
+
// A session_before_compact hook can still serve context-full maintenance
|
|
20103
|
+
// without a model call, so it keeps the session eligible.
|
|
20104
|
+
const handoffSelected = compactionSettings.strategy === "handoff" && reason !== "overflow";
|
|
20105
|
+
const reachableModels =
|
|
20106
|
+
handoffSelected && this.model
|
|
20107
|
+
? [this.model]
|
|
20108
|
+
: this.#getCompactionModelCandidates(this.#modelRegistry.getAvailable());
|
|
20109
|
+
const hookServesMaintenance =
|
|
20110
|
+
!handoffSelected && this.#extensionRunner?.hasHandlers("session_before_compact") === true;
|
|
20111
|
+
if (
|
|
20112
|
+
reachableModels.length > 0 &&
|
|
20113
|
+
!reachableModels.some(modelSupportsMaintenanceCalls) &&
|
|
20114
|
+
!hookServesMaintenance
|
|
20115
|
+
) {
|
|
20116
|
+
logger.debug(
|
|
20117
|
+
"Auto-compaction skipped: every reachable model is an agent-level provider that refuses maintenance calls",
|
|
20118
|
+
{
|
|
20119
|
+
reason,
|
|
20120
|
+
provider: this.model?.provider,
|
|
20121
|
+
},
|
|
20122
|
+
);
|
|
20123
|
+
return { kind: "skipped" };
|
|
20124
|
+
}
|
|
19936
20125
|
const generation = this.#promptGeneration;
|
|
19937
20126
|
if (
|
|
19938
20127
|
options?.deferHandoffMaintenance !== false &&
|
|
@@ -20190,7 +20379,24 @@ export class AgentSession {
|
|
|
20190
20379
|
details = compactionPrep.details;
|
|
20191
20380
|
preserveData = compactionPrep.preserveData;
|
|
20192
20381
|
} else {
|
|
20193
|
-
|
|
20382
|
+
// Agent-level providers refuse maintenance calls by contract, so they
|
|
20383
|
+
// can only add a guaranteed refusal to the failure list — never spend a
|
|
20384
|
+
// call on one while a text-model candidate exists. The pre-flight
|
|
20385
|
+
// reachability check normally catches the all-agent-level case; this
|
|
20386
|
+
// covers a mid-flight model swap landing here.
|
|
20387
|
+
const candidates =
|
|
20388
|
+
this.#getCompactionModelCandidates(availableModels).filter(modelSupportsMaintenanceCalls);
|
|
20389
|
+
if (candidates.length === 0) {
|
|
20390
|
+
await this.#emitSessionEvent({
|
|
20391
|
+
type: "auto_compaction_end",
|
|
20392
|
+
action,
|
|
20393
|
+
result: undefined,
|
|
20394
|
+
aborted: false,
|
|
20395
|
+
willRetry: false,
|
|
20396
|
+
skipped: true,
|
|
20397
|
+
});
|
|
20398
|
+
return { kind: "skipped" };
|
|
20399
|
+
}
|
|
20194
20400
|
maintenanceAttemptSignature = this.#buildAutoMaintenanceAttemptSignature(
|
|
20195
20401
|
action,
|
|
20196
20402
|
preparation,
|
|
@@ -20533,6 +20739,30 @@ export class AgentSession {
|
|
|
20533
20739
|
);
|
|
20534
20740
|
}
|
|
20535
20741
|
|
|
20742
|
+
#isCodexCredentialModelUnavailable(message: AssistantMessage): boolean {
|
|
20743
|
+
return (
|
|
20744
|
+
message.api === "openai-codex-responses" &&
|
|
20745
|
+
message.provider === "openai-codex" &&
|
|
20746
|
+
message.transportFailure?.credentialModelUnavailable === true
|
|
20747
|
+
);
|
|
20748
|
+
}
|
|
20749
|
+
|
|
20750
|
+
#canRotateCodexCredential(message: AssistantMessage): boolean {
|
|
20751
|
+
if (!this.#isCodexCredentialModelUnavailable(message)) return false;
|
|
20752
|
+
if (this.#codexCredentialModelUnavailableRetried || assistantMessageHasVisibleOrToolContent(message))
|
|
20753
|
+
return false;
|
|
20754
|
+
const authStorage = this.#modelRegistry.authStorage;
|
|
20755
|
+
const provider = message.provider;
|
|
20756
|
+
const owner = this.#modelRegistry.getAuthStorageOwner();
|
|
20757
|
+
return (
|
|
20758
|
+
authStorage.getSessionCredentialType(provider, this.credentialSessionId) === "oauth" &&
|
|
20759
|
+
!authStorage.hasRuntimeApiKey(provider) &&
|
|
20760
|
+
!authStorage.hasRuntimeCredentialSelector(provider) &&
|
|
20761
|
+
!authStorage.hasSessionCredentialSelector(provider, this.credentialSessionId) &&
|
|
20762
|
+
!authStorage.hasConfigApiKey(provider, owner)
|
|
20763
|
+
);
|
|
20764
|
+
}
|
|
20765
|
+
|
|
20536
20766
|
#isRetryableError(message: AssistantMessage): boolean {
|
|
20537
20767
|
if (this.#isTerminalProviderFirstEventTimeout(message)) return false;
|
|
20538
20768
|
if (message.errorMessage?.startsWith("Model fallback chain exhausted;")) return false;
|
|
@@ -20545,6 +20775,16 @@ export class AgentSession {
|
|
|
20545
20775
|
const contextWindow = this.model?.contextWindow ?? 0;
|
|
20546
20776
|
if (classifyContextOverflow(message, transportFailure, contextWindow)) return false;
|
|
20547
20777
|
const managedFallback = this.#defaultFallbackChain().chain.entries.length > 1;
|
|
20778
|
+
// An account-specific model rejection that cannot rotate to another
|
|
20779
|
+
// credential stays terminal only on the session's own retry path; managed
|
|
20780
|
+
// fallback still advances the model chain through the controller.
|
|
20781
|
+
if (
|
|
20782
|
+
this.#isCodexCredentialModelUnavailable(message) &&
|
|
20783
|
+
!this.#canRotateCodexCredential(message) &&
|
|
20784
|
+
!managedFallback
|
|
20785
|
+
) {
|
|
20786
|
+
return false;
|
|
20787
|
+
}
|
|
20548
20788
|
if (!managedFallback) {
|
|
20549
20789
|
const classification = this.#classifyErrorForRetry(message);
|
|
20550
20790
|
return (
|
|
@@ -20675,6 +20915,9 @@ export class AgentSession {
|
|
|
20675
20915
|
if (message.errorKind === "local_buffer_overflow") return "local_buffer_overflow";
|
|
20676
20916
|
if (this.#isTypedFirstEventTimeout(message)) return "first_event_timeout";
|
|
20677
20917
|
if (this.#isTypedEmptyResponse(message)) return "empty_response";
|
|
20918
|
+
if (this.#isCodexCredentialModelUnavailable(message)) {
|
|
20919
|
+
return this.#canRotateCodexCredential(message) ? "unknown" : "terminal";
|
|
20920
|
+
}
|
|
20678
20921
|
if (!message.errorMessage) return "none";
|
|
20679
20922
|
const err = message.errorMessage;
|
|
20680
20923
|
// Managed-attempt local failures from restored sessions may lack the
|
|
@@ -21490,7 +21733,7 @@ export class AgentSession {
|
|
|
21490
21733
|
* Marks the credential that just failed and reports whether the session
|
|
21491
21734
|
* actually moved to a DIFFERENT stored credential.
|
|
21492
21735
|
*
|
|
21493
|
-
*
|
|
21736
|
+
* Credential mutation invariants:
|
|
21494
21737
|
*
|
|
21495
21738
|
* 1. **Pin guard, first and for every trigger class.** A pinned credential
|
|
21496
21739
|
* (`--api-key` or `--credential`) must never be mutated or rotated away
|
|
@@ -21500,18 +21743,23 @@ export class AgentSession {
|
|
|
21500
21743
|
* path invalidated pinned credentials outright.
|
|
21501
21744
|
* 2. **A terminal `forbidden` never mutates credential state.** Rotation
|
|
21502
21745
|
* would hide an authorization defect and cycle through healthy rows.
|
|
21503
|
-
* 3.
|
|
21504
|
-
*
|
|
21505
|
-
*
|
|
21506
|
-
*
|
|
21507
|
-
* require the active key to have actually changed.
|
|
21746
|
+
* 3. Auth failures retain their existing key-change proof. Quota, rate-limit
|
|
21747
|
+
* and OAuth account-model rejections mark before resolving and require two known,
|
|
21748
|
+
* different stored row IDs before reporting rotation. This is mark-time
|
|
21749
|
+
* identity, not dispatch-bound attribution across shared sessions.
|
|
21508
21750
|
*/
|
|
21509
21751
|
async #markFailedCredential(trigger: {
|
|
21510
21752
|
class: FallbackTriggerClass;
|
|
21511
21753
|
retryAfterMs?: number;
|
|
21512
21754
|
authDisposition?: AuthDisposition;
|
|
21513
21755
|
}): Promise<"rotated" | "exhausted" | "unchanged"> {
|
|
21514
|
-
if (
|
|
21756
|
+
if (
|
|
21757
|
+
!this.model ||
|
|
21758
|
+
(trigger.class !== "auth" &&
|
|
21759
|
+
trigger.class !== "quota" &&
|
|
21760
|
+
trigger.class !== "rate_limit" &&
|
|
21761
|
+
trigger.class !== "credential")
|
|
21762
|
+
) {
|
|
21515
21763
|
return "unchanged";
|
|
21516
21764
|
}
|
|
21517
21765
|
// (2) Terminal forbidden: no credential state may change.
|
|
@@ -21520,27 +21768,43 @@ export class AgentSession {
|
|
|
21520
21768
|
const authStorage = this.#modelRegistry.authStorage;
|
|
21521
21769
|
const provider = this.model.provider;
|
|
21522
21770
|
// (1) Pin guard, before any mutation and for every branch.
|
|
21523
|
-
if (
|
|
21771
|
+
if (
|
|
21772
|
+
authStorage.hasRuntimeApiKey(provider) ||
|
|
21773
|
+
authStorage.hasRuntimeCredentialSelector(provider) ||
|
|
21774
|
+
authStorage.hasSessionCredentialSelector(provider, this.credentialSessionId) ||
|
|
21775
|
+
authStorage.hasConfigApiKey(provider, this.#modelRegistry.getAuthStorageOwner())
|
|
21776
|
+
) {
|
|
21524
21777
|
return "unchanged";
|
|
21525
21778
|
}
|
|
21526
21779
|
|
|
21527
21780
|
const credentialSessionId = this.credentialSessionId;
|
|
21528
|
-
|
|
21529
|
-
|
|
21530
|
-
|
|
21531
|
-
|
|
21532
|
-
|
|
21533
|
-
|
|
21534
|
-
|
|
21535
|
-
|
|
21536
|
-
|
|
21537
|
-
|
|
21538
|
-
} else {
|
|
21539
|
-
remaining = await authStorage.markUsageLimitReached(provider, credentialSessionId, {
|
|
21540
|
-
retryAfterMs: trigger.retryAfterMs,
|
|
21781
|
+
if (trigger.class !== "auth") {
|
|
21782
|
+
if (
|
|
21783
|
+
trigger.class === "credential" &&
|
|
21784
|
+
authStorage.getSessionCredentialType(provider, credentialSessionId) !== "oauth"
|
|
21785
|
+
)
|
|
21786
|
+
return "unchanged";
|
|
21787
|
+
const before = authStorage.getSessionCredentialRowId(provider, credentialSessionId);
|
|
21788
|
+
const remaining = await authStorage.markUsageLimitReached(provider, credentialSessionId, {
|
|
21789
|
+
// Account-model rejection retains default backoff, not response retry-after.
|
|
21790
|
+
...(trigger.class === "credential" ? {} : { retryAfterMs: trigger.retryAfterMs }),
|
|
21541
21791
|
owner: this.#modelRegistry.getAuthStorageOwner(),
|
|
21792
|
+
...(before === undefined ? {} : { rowId: before }),
|
|
21542
21793
|
});
|
|
21794
|
+
if (!remaining) return "exhausted";
|
|
21795
|
+
await this.#modelRegistry.getApiKey(this.model, credentialSessionId);
|
|
21796
|
+
const after = authStorage.getSessionCredentialRowId(provider, credentialSessionId);
|
|
21797
|
+
if (before !== undefined && after !== undefined && before !== after) return "rotated";
|
|
21798
|
+
return "unchanged";
|
|
21543
21799
|
}
|
|
21800
|
+
const activeApiKey = await this.#modelRegistry.getApiKey(this.model, credentialSessionId);
|
|
21801
|
+
|
|
21802
|
+
if (!isAuthenticated(activeApiKey)) return "unchanged";
|
|
21803
|
+
const remaining = await authStorage.invalidateCredentialMatching(provider, activeApiKey, {
|
|
21804
|
+
sessionId: credentialSessionId,
|
|
21805
|
+
owner: this.#modelRegistry.getAuthStorageOwner(),
|
|
21806
|
+
});
|
|
21807
|
+
if (!remaining) return "unchanged";
|
|
21544
21808
|
|
|
21545
21809
|
// (3) Distinct-row proof.
|
|
21546
21810
|
if ((await this.#modelRegistry.getApiKey(this.model, credentialSessionId)) !== activeApiKey) {
|
|
@@ -21626,10 +21890,19 @@ export class AgentSession {
|
|
|
21626
21890
|
}
|
|
21627
21891
|
: false;
|
|
21628
21892
|
}
|
|
21893
|
+
const canRotateCodexCredential = this.#canRotateCodexCredential(message);
|
|
21894
|
+
if (this.#isCodexCredentialModelUnavailable(message) && !canRotateCodexCredential && !managedFallback) {
|
|
21895
|
+
return managedOutcome
|
|
21896
|
+
? {
|
|
21897
|
+
type: "terminal",
|
|
21898
|
+
terminal: { stopReason: "error", messages: [message] },
|
|
21899
|
+
}
|
|
21900
|
+
: false;
|
|
21901
|
+
}
|
|
21629
21902
|
// retry.enabled=false always surfaces immediately, matching the explicit
|
|
21630
21903
|
// user opt-out. The managed provider-fallback chain keeps its own
|
|
21631
21904
|
// availability policy.
|
|
21632
|
-
if (!retrySettings.enabled && !managedFallback) {
|
|
21905
|
+
if (!retrySettings.enabled && !managedFallback && !canRotateCodexCredential) {
|
|
21633
21906
|
return managedOutcome
|
|
21634
21907
|
? {
|
|
21635
21908
|
type: "terminal",
|
|
@@ -21709,6 +21982,19 @@ export class AgentSession {
|
|
|
21709
21982
|
// extension lifecycle participation. Mark the failed credential and
|
|
21710
21983
|
// retry with the next stored credential of the same provider.
|
|
21711
21984
|
let credentialRotated = false;
|
|
21985
|
+
if (canRotateCodexCredential && trigger.class === "credential") {
|
|
21986
|
+
const mark = await this.#markFailedCredential(trigger);
|
|
21987
|
+
this.#codexCredentialModelUnavailableRetried = true;
|
|
21988
|
+
credentialRotated = mark === "rotated";
|
|
21989
|
+
if (!credentialRotated && !managedFallback) {
|
|
21990
|
+
return managedOutcome
|
|
21991
|
+
? {
|
|
21992
|
+
type: "terminal",
|
|
21993
|
+
terminal: { stopReason: "error", messages: [message] },
|
|
21994
|
+
}
|
|
21995
|
+
: false;
|
|
21996
|
+
}
|
|
21997
|
+
}
|
|
21712
21998
|
if (
|
|
21713
21999
|
!managedFallback &&
|
|
21714
22000
|
!providerRetryCeilingReached &&
|
|
@@ -21771,6 +22057,15 @@ export class AgentSession {
|
|
|
21771
22057
|
if (providerRetryCeilingReached && outcome === "retry") {
|
|
21772
22058
|
outcome = controller.advance() ? "advance" : "exhausted";
|
|
21773
22059
|
}
|
|
22060
|
+
// The one content-free account retry was already spent: never spend
|
|
22061
|
+
// another same-model attempt on a third account, advance instead.
|
|
22062
|
+
if (
|
|
22063
|
+
outcome === "retry" &&
|
|
22064
|
+
this.#isCodexCredentialModelUnavailable(message) &&
|
|
22065
|
+
this.#codexCredentialModelUnavailableRetried
|
|
22066
|
+
) {
|
|
22067
|
+
outcome = controller.advance() ? "advance" : "exhausted";
|
|
22068
|
+
}
|
|
21774
22069
|
} else {
|
|
21775
22070
|
outcome = providerRetryCeilingReached
|
|
21776
22071
|
? "exhausted"
|
|
@@ -21778,6 +22073,15 @@ export class AgentSession {
|
|
|
21778
22073
|
? "retry"
|
|
21779
22074
|
: "exhausted";
|
|
21780
22075
|
}
|
|
22076
|
+
if (
|
|
22077
|
+
managedFallback &&
|
|
22078
|
+
outcome !== "retry" &&
|
|
22079
|
+
trigger.class === "rate_limit" &&
|
|
22080
|
+
trigger.retryAfterMs !== undefined &&
|
|
22081
|
+
failedSelector
|
|
22082
|
+
) {
|
|
22083
|
+
this.#modelRegistry.suppressSelector(failedSelector, Date.now() + trigger.retryAfterMs);
|
|
22084
|
+
}
|
|
21781
22085
|
// Credential rotation is unbounded: a fresh credential is a different
|
|
21782
22086
|
// retry dimension from transient-error backoff, so it overrides maxRetries
|
|
21783
22087
|
// exhaustion and forces an immediate same-model retry.
|
|
@@ -21841,15 +22145,11 @@ export class AgentSession {
|
|
|
21841
22145
|
credentialRotated || outcome === "advance"
|
|
21842
22146
|
? 0
|
|
21843
22147
|
: managedFallback
|
|
21844
|
-
? effectiveFallbackDelay(retrySettings.baseDelayMs, retrySettings.maxDelayMs, attemptsUsed
|
|
22148
|
+
? effectiveFallbackDelay(retrySettings.baseDelayMs, retrySettings.maxDelayMs, attemptsUsed)
|
|
21845
22149
|
: retryAfterMs !== undefined
|
|
21846
22150
|
? Math.min(retryAfterMs, retrySettings.maxDelayMs)
|
|
21847
22151
|
: cappedExponentialWithFullJitter(retrySettings.baseDelayMs, retrySettings.maxDelayMs, attemptsUsed);
|
|
21848
22152
|
|
|
21849
|
-
if (managedFallback && trigger.class === "rate_limit" && trigger.retryAfterMs !== undefined && failedSelector) {
|
|
21850
|
-
this.#modelRegistry.suppressSelector(failedSelector, Date.now() + trigger.retryAfterMs);
|
|
21851
|
-
}
|
|
21852
|
-
|
|
21853
22153
|
const retry = async (ownership?: ManagedAttemptContinuationOwnership): Promise<void> => {
|
|
21854
22154
|
const activePromptHandle = this.activePromptHandle;
|
|
21855
22155
|
const cancellationSignal =
|
|
@@ -21858,7 +22158,12 @@ export class AgentSession {
|
|
|
21858
22158
|
if (ownership && (!ownership.isCurrent() || cancellationSignal?.aborted)) return;
|
|
21859
22159
|
if (retryCancelled()) return;
|
|
21860
22160
|
let quotaPoolExhausted = false;
|
|
21861
|
-
if (
|
|
22161
|
+
if (
|
|
22162
|
+
managedFallback &&
|
|
22163
|
+
!credentialRotated &&
|
|
22164
|
+
!providerRetryCeilingReached &&
|
|
22165
|
+
!(this.#isCodexCredentialModelUnavailable(message) && this.#codexCredentialModelUnavailableRetried)
|
|
22166
|
+
) {
|
|
21862
22167
|
const mark = await this.#markFailedCredential(trigger);
|
|
21863
22168
|
if (mark === "rotated") credentialRotated = true;
|
|
21864
22169
|
quotaPoolExhausted = mark === "exhausted";
|