@gajae-code/coding-agent 0.14.2 → 0.15.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 +120 -1
- package/dist/types/async/job-manager.d.ts +4 -1
- package/dist/types/autoresearch/dashboard.d.ts +29 -4
- package/dist/types/autoresearch/data-context.d.ts +25 -0
- package/dist/types/autoresearch/git.d.ts +65 -7
- package/dist/types/autoresearch/harness.d.ts +58 -0
- package/dist/types/autoresearch/prompts.d.ts +51 -0
- package/dist/types/autoresearch/runs.d.ts +173 -0
- package/dist/types/autoresearch/types.d.ts +9 -139
- package/dist/types/capability/types.d.ts +20 -0
- package/dist/types/cli/accounts-cli.d.ts +1 -0
- package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
- package/dist/types/config/autorouting-contract.d.ts +146 -0
- package/dist/types/config/autorouting-generator.d.ts +40 -0
- package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
- package/dist/types/config/autorouting.d.ts +35 -0
- package/dist/types/config/model-discovery-manager.d.ts +8 -1
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/provider-selection-policy.d.ts +23 -0
- package/dist/types/config/settings-schema.d.ts +61 -3
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/durability.d.ts +29 -0
- package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
- package/dist/types/coordinator-mcp/server.d.ts +14 -0
- package/dist/types/crash/index-store.d.ts +3 -0
- package/dist/types/crash/record-loader.d.ts +2 -0
- package/dist/types/crash/upstream/relay.d.ts +5 -3
- package/dist/types/cursor.d.ts +12 -0
- package/dist/types/defaults/gjc-defaults.d.ts +35 -1
- package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/edit/index.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +32 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
- package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
- package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
- package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
- package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
- package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
- package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
- package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
- package/dist/types/hooks/skill-keywords.d.ts +1 -1
- package/dist/types/main.d.ts +0 -10
- package/dist/types/modes/acp/acp-agent.d.ts +13 -6
- package/dist/types/modes/components/model-selector.d.ts +14 -0
- package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
- package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +9 -0
- package/dist/types/rlm/artifacts.d.ts +4 -4
- package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
- package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
- package/dist/types/runtime-mcp/config.d.ts +6 -0
- package/dist/types/runtime-mcp/manager.d.ts +10 -0
- package/dist/types/sdk/acp/adapter.d.ts +0 -4
- package/dist/types/sdk/broker/broker.d.ts +1 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
- package/dist/types/sdk/broker/session-index.d.ts +15 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/index.d.ts +8 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
- package/dist/types/sdk/client/client.d.ts +39 -4
- package/dist/types/sdk/host/host.d.ts +1 -0
- package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
- package/dist/types/sdk/host/model-pin.d.ts +29 -0
- package/dist/types/sdk/host/session-runtime.d.ts +2 -1
- package/dist/types/sdk/lifecycle-session.d.ts +13 -0
- package/dist/types/sdk/router/session-router.d.ts +27 -1
- package/dist/types/sdk/session.d.ts +11 -1
- package/dist/types/session/agent-session.d.ts +36 -6
- package/dist/types/session/artifacts.d.ts +16 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/session-manager.d.ts +84 -2
- package/dist/types/session/session-staging-paths.d.ts +3 -0
- package/dist/types/skill-state/canonical-skills.d.ts +1 -1
- package/dist/types/skill-state/workflow-hud.d.ts +12 -21
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
- package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
- package/dist/types/system-prompt.d.ts +23 -0
- package/dist/types/task/executor.d.ts +35 -4
- package/dist/types/task/index.d.ts +17 -3
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +144 -0
- package/dist/types/tools/atomic-file-write.d.ts +72 -0
- package/dist/types/tools/descriptors.d.ts +1 -0
- package/dist/types/tools/index.d.ts +36 -1
- package/dist/types/tools/move-session.d.ts +40 -0
- package/dist/types/tools/python.d.ts +12 -0
- package/dist/types/tools/resource-gc.d.ts +0 -3
- package/dist/types/tools/search.d.ts +8 -1
- package/dist/types/tools/skill.d.ts +9 -4
- package/dist/types/tools/subagent.d.ts +33 -3
- package/dist/types/utils/xml-escape.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
- package/package.json +11 -12
- package/scripts/build-sdk-package-smoke.ts +7 -5
- package/scripts/check-autorouting-tier-map.ts +183 -0
- package/scripts/generate-sdk-operation-inventory.ts +12 -0
- package/scripts/generate-tool-catalog.ts +40 -5
- package/src/async/job-manager.ts +5 -2
- package/src/autoresearch/dashboard.ts +134 -314
- package/src/autoresearch/data-context.ts +35 -0
- package/src/autoresearch/git.ts +186 -26
- package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
- package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
- package/src/autoresearch/prompts/prompt.md +103 -0
- package/src/autoresearch/prompts.ts +126 -0
- package/src/autoresearch/runs.ts +708 -0
- package/src/autoresearch/types.ts +9 -150
- package/src/capability/index.ts +4 -4
- package/src/capability/types.ts +20 -0
- package/src/cli/accounts-cli.ts +21 -5
- package/src/cli/config-cli.ts +25 -1
- package/src/cli/fast-help.ts +1 -2
- package/src/cli/list-models.ts +5 -8
- package/src/cli/setup-cli.ts +8 -0
- package/src/cli.ts +2 -21
- package/src/commands/autoresearch.ts +33 -0
- package/src/commands/coordinator.ts +15 -0
- package/src/commands/launch.ts +2 -4
- package/src/commands/quick-lane.ts +1 -1
- package/src/commands/sdk.ts +24 -0
- package/src/commands/state.ts +1 -1
- package/src/config/autorouting-contract.ts +474 -0
- package/src/config/autorouting-generator.ts +238 -0
- package/src/config/autorouting-tier-map.ts +6108 -0
- package/src/config/autorouting.ts +120 -0
- package/src/config/model-discovery-manager.ts +42 -11
- package/src/config/model-registry.ts +348 -68
- package/src/config/provider-selection-policy.ts +60 -9
- package/src/config/settings-schema.ts +195 -6
- package/src/config/settings.ts +62 -1
- package/src/config.ts +2 -3
- package/src/coordinator/contract.ts +0 -1
- package/src/coordinator-mcp/codex-handoff.ts +68 -42
- package/src/coordinator-mcp/durability.ts +200 -0
- package/src/coordinator-mcp/event-webhook.ts +278 -0
- package/src/coordinator-mcp/question-state.ts +109 -46
- package/src/coordinator-mcp/server.ts +932 -504
- package/src/crash/index-store.ts +126 -22
- package/src/crash/record-loader.ts +7 -0
- package/src/crash/upstream/relay.ts +82 -34
- package/src/cursor.ts +84 -1
- package/src/debug/index.ts +26 -0
- package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
- package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
- package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
- package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
- package/src/defaults/gjc-defaults.ts +89 -2
- package/src/defaults/gjc-skills.generated.ts +24 -17
- package/src/discovery/builtin.ts +18 -5
- package/src/discovery/helpers.ts +6 -6
- package/src/edit/index.ts +5 -2
- package/src/eval/py/executor.ts +8 -0
- package/src/extensibility/extensions/loader.ts +135 -13
- package/src/extensibility/extensions/wrapper.ts +1 -2
- package/src/extensibility/gjc-plugins/schema.ts +1 -1
- package/src/extensibility/runtime-skill-discovery.ts +2 -2
- package/src/extensibility/skill-management.ts +2 -3
- package/src/extensibility/skills.ts +16 -8
- package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
- package/src/gjc-runtime/gc-render.ts +0 -1
- package/src/gjc-runtime/gc-runtime.ts +1 -11
- package/src/gjc-runtime/launch-tmux.ts +3 -3
- package/src/gjc-runtime/psmux-detect.ts +2 -2
- package/src/gjc-runtime/python-transcript.ts +58 -0
- package/src/gjc-runtime/ralplan-runtime.ts +6 -18
- package/src/gjc-runtime/session-layout.ts +18 -7
- package/src/gjc-runtime/session-state-sidecar.ts +58 -9
- package/src/gjc-runtime/state-renderer.ts +0 -38
- package/src/gjc-runtime/state-runtime.ts +43 -22
- package/src/gjc-runtime/state-schema.ts +1 -1
- package/src/gjc-runtime/tmux-common.ts +7 -7
- package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
- package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
- package/src/gjc-runtime/workflow-command-ref.ts +15 -11
- package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
- package/src/gjc-runtime/workflow-manifest.ts +43 -83
- package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
- package/src/hooks/skill-keywords.ts +3 -3
- package/src/hooks/skill-state.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +26 -24
- package/src/lsp/index.ts +25 -5
- package/src/main.ts +2 -27
- package/src/modes/acp/acp-agent.ts +200 -75
- package/src/modes/components/model-selector.ts +216 -13
- package/src/modes/components/oauth-selector.ts +5 -5
- package/src/modes/components/skill-hud/render.ts +173 -35
- package/src/modes/components/smart-routing-panel.ts +531 -0
- package/src/modes/components/tool-status-header.ts +6 -3
- package/src/modes/controllers/command-controller.ts +17 -7
- package/src/modes/controllers/input-controller.ts +44 -9
- package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +298 -65
- package/src/modes/interactive-mode.ts +8 -1
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
- package/src/modes/types.ts +9 -1
- package/src/prompts/agents/planner.md +1 -1
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/volatile-project-context.md +2 -1
- package/src/prompts/tools/move-session.md +15 -0
- package/src/prompts/tools/python.md +12 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/prompts/tools/skill.md +2 -2
- package/src/prompts/tools/task-summary.md +1 -0
- package/src/prompts/tools/task.md +6 -0
- package/src/rlm/artifacts.ts +12 -10
- package/src/runtime/optional-runtime-services.ts +3 -2
- package/src/runtime/workspace-tree-service.ts +11 -3
- package/src/runtime-mcp/config.ts +8 -1
- package/src/runtime-mcp/manager.ts +33 -0
- package/src/sdk/acp/adapter.ts +48 -1
- package/src/sdk/broker/broker.ts +30 -2
- package/src/sdk/broker/discovery.ts +217 -2
- package/src/sdk/broker/lifecycle.ts +385 -59
- package/src/sdk/broker/session-index.ts +243 -48
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/chat-daemon-control.ts +8 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
- package/src/sdk/bus/index.ts +208 -11
- package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
- package/src/sdk/bus/reconciliation-store.ts +27 -5
- package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
- package/src/sdk/client/client.ts +204 -22
- package/src/sdk/host/host.ts +16 -0
- package/src/sdk/host/internal-autorouting-state.ts +13 -0
- package/src/sdk/host/model-pin.ts +101 -0
- package/src/sdk/host/reverse-leases.ts +2 -1
- package/src/sdk/host/session-runtime.test.ts +224 -1
- package/src/sdk/host/session-runtime.ts +125 -37
- package/src/sdk/lifecycle/service.ts +3 -1
- package/src/sdk/lifecycle-session.ts +42 -1
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/router/session-router.ts +375 -36
- package/src/sdk/session.ts +547 -47
- package/src/session/account-inventory.ts +1 -1
- package/src/session/agent-session.ts +765 -189
- package/src/session/artifacts.ts +234 -4
- package/src/session/internal/managed-session-storage.ts +91 -4
- package/src/session/provider-safety-stop-hint.ts +3 -2
- package/src/session/session-manager.ts +775 -16
- package/src/session/session-staging-paths.ts +8 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
- package/src/setup/host-plugin-setup.ts +2 -2
- package/src/skill-state/active-state.ts +2 -2
- package/src/skill-state/canonical-skills.ts +1 -1
- package/src/skill-state/initial-phase.ts +1 -1
- package/src/skill-state/workflow-hud.ts +41 -41
- package/src/skill-state/workflow-mutation-guard.ts +26 -7
- package/src/slash-commands/builtin-registry.ts +77 -0
- package/src/slash-commands/helpers/autorouting-status.ts +55 -0
- package/src/system-prompt.ts +102 -5
- package/src/task/discovery.ts +2 -3
- package/src/task/executor.ts +617 -43
- package/src/task/index.ts +262 -15
- package/src/task/receipt.ts +30 -0
- package/src/task/render.ts +11 -1
- package/src/task/types.ts +316 -0
- package/src/tools/atomic-file-write.ts +594 -0
- package/src/tools/bisect.ts +3 -2
- package/src/tools/descriptors.test.ts +3 -0
- package/src/tools/descriptors.ts +24 -0
- package/src/tools/image-gen.ts +5 -1
- package/src/tools/index.ts +62 -5
- package/src/tools/move-session.ts +107 -0
- package/src/tools/python.ts +205 -0
- package/src/tools/read.ts +88 -15
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resource-gc.ts +8 -177
- package/src/tools/search.ts +30 -6
- package/src/tools/skill-discovery.ts +1 -0
- package/src/tools/skill.ts +37 -7
- package/src/tools/subagent-render.ts +119 -13
- package/src/tools/subagent.ts +91 -74
- package/src/tools/tool-catalog.generated.ts +64 -2
- package/src/tools/write.ts +16 -12
- package/src/utils/xml-escape.ts +14 -0
- package/src/web/search/providers/codex.ts +2 -2
- package/src/workflow/workflow-intent-diff.ts +13 -12
- package/src/workflow/workflow-intent-report-consumer.ts +6 -6
- package/dist/types/autoresearch/helpers.d.ts +0 -24
- package/dist/types/autoresearch/index.d.ts +0 -2
- package/dist/types/autoresearch/state.d.ts +0 -17
- package/dist/types/autoresearch/storage.d.ts +0 -142
- package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
- package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
- package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
- package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
- package/dist/types/commands/team.d.ts +0 -24
- package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
- package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
- package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
- package/dist/types/gjc-runtime/team-store.d.ts +0 -168
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
- package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
- package/dist/types/rlm/index.d.ts +0 -47
- package/dist/types/rlm/preset.d.ts +0 -23
- package/dist/types/rlm/python-tool.d.ts +0 -16
- package/src/autoresearch/command-resume.md +0 -14
- package/src/autoresearch/index.ts +0 -536
- package/src/autoresearch/prompt.md +0 -103
- package/src/autoresearch/resume-message.md +0 -10
- package/src/autoresearch/state.ts +0 -273
- package/src/autoresearch/storage.ts +0 -699
- package/src/autoresearch/tools/init-experiment.ts +0 -272
- package/src/autoresearch/tools/log-experiment.ts +0 -524
- package/src/autoresearch/tools/run-experiment.ts +0 -475
- package/src/autoresearch/tools/update-notes.ts +0 -109
- package/src/commands/rlm.ts +0 -19
- package/src/commands/team.ts +0 -238
- package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
- package/src/defaults/gjc/skills/team/SKILL.md +0 -467
- package/src/gjc-runtime/team-gc.ts +0 -49
- package/src/gjc-runtime/team-launch.ts +0 -404
- package/src/gjc-runtime/team-notify.ts +0 -326
- package/src/gjc-runtime/team-runtime.ts +0 -5923
- package/src/gjc-runtime/team-store.ts +0 -878
- package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
- package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
- package/src/gjc-runtime/team-workers.ts +0 -1123
- package/src/prompts/system/rlm-report-command.md +0 -1
- package/src/prompts/system/rlm-research.md +0 -23
- package/src/rlm/index.ts +0 -365
- package/src/rlm/preset.ts +0 -76
- package/src/rlm/python-tool.ts +0 -68
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
type StablePrefixSnapshot,
|
|
49
49
|
ThinkingLevel,
|
|
50
50
|
} from "@gajae-code/agent-core";
|
|
51
|
-
import { normalizeMessagesForProvider } from "@gajae-code/agent-core/agent-loop";
|
|
51
|
+
import { ESCAPED_NONASCII_RECOVERY_PROMPT, normalizeMessagesForProvider } from "@gajae-code/agent-core/agent-loop";
|
|
52
52
|
import type { AttemptRunHandle, AttemptScope, AttemptScopeAuthority } from "@gajae-code/agent-core/attempt-scope";
|
|
53
53
|
import {
|
|
54
54
|
AUTO_HANDOFF_THRESHOLD_FOCUS,
|
|
@@ -104,6 +104,7 @@ import type {
|
|
|
104
104
|
TransportFailureFacts,
|
|
105
105
|
Usage,
|
|
106
106
|
UsageReport,
|
|
107
|
+
UserMessage,
|
|
107
108
|
} from "@gajae-code/ai/core";
|
|
108
109
|
import {
|
|
109
110
|
classifyContextOverflow,
|
|
@@ -309,8 +310,14 @@ import {
|
|
|
309
310
|
registerCoordinatorRuntimeStateFinalizer,
|
|
310
311
|
UNPROVEN_TOOL_LABEL,
|
|
311
312
|
} from "../gjc-runtime/session-state-sidecar";
|
|
312
|
-
import {
|
|
313
|
-
|
|
313
|
+
import {
|
|
314
|
+
isWorkflowRecoveryStalled,
|
|
315
|
+
projectLatestRalplanRun,
|
|
316
|
+
projectUltragoalRun,
|
|
317
|
+
trackWorkflowRecoveryZeroProgress,
|
|
318
|
+
type WorkflowRecoveryProjection,
|
|
319
|
+
type WorkflowRecoveryZeroProgressMemory,
|
|
320
|
+
} from "../gjc-runtime/workflow-recovery-projection";
|
|
314
321
|
import { GoalRuntime } from "../goals/runtime";
|
|
315
322
|
import type { Goal, GoalModeState } from "../goals/state";
|
|
316
323
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
@@ -486,12 +493,169 @@ import { ToolChoiceQueue } from "./tool-choice-queue";
|
|
|
486
493
|
import { pruneSupersededMaintenanceReminders, pruneSupersededVolatileProjectContext } from "./volatile-context-pruning";
|
|
487
494
|
import { YieldQueue } from "./yield-queue";
|
|
488
495
|
|
|
496
|
+
/**
|
|
497
|
+
* #4560: structured workflow recovery projection from canonical durable
|
|
498
|
+
* Ralplan/Ultragoal state, consumed by the compaction summary context and
|
|
499
|
+
* the post-compaction continuation prompt.
|
|
500
|
+
*/
|
|
489
501
|
interface CompactionStateSnapshot {
|
|
502
|
+
workflowRecovery?: WorkflowRecoveryProjection;
|
|
490
503
|
goal: { objective: string; status: Goal["status"]; enabled: boolean } | undefined;
|
|
491
504
|
openTodos: string[];
|
|
492
505
|
activeSkills: Array<{ skill: string; phase: string }>;
|
|
493
506
|
queuedMessages: boolean;
|
|
494
507
|
lastAssistantStopReason: StopReason | undefined;
|
|
508
|
+
recentFileMutations: string[];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const FILE_MUTATION_TOOLS = new Set(["write", "edit", "apply_patch", "ast_edit"]);
|
|
512
|
+
const MAX_RECENT_FILE_MUTATIONS = 12;
|
|
513
|
+
|
|
514
|
+
function collectFileMutationPaths(toolName: string, args: unknown): string[] {
|
|
515
|
+
if (!FILE_MUTATION_TOOLS.has(toolName) || !args || typeof args !== "object" || Array.isArray(args)) return [];
|
|
516
|
+
const record = args as Record<string, unknown>;
|
|
517
|
+
const paths: string[] = [];
|
|
518
|
+
const addPath = (value: unknown) => {
|
|
519
|
+
if (typeof value === "string" && value.length > 0 && !paths.includes(value)) paths.push(value);
|
|
520
|
+
};
|
|
521
|
+
const directPath = getStringProperty(record, "path") ?? getStringProperty(record, "file_path");
|
|
522
|
+
addPath(directPath);
|
|
523
|
+
|
|
524
|
+
for (const path of collectStringPaths(record.paths)) addPath(path);
|
|
525
|
+
const edits = Array.isArray(record.edits) ? record.edits : [];
|
|
526
|
+
for (const edit of edits) {
|
|
527
|
+
if (!edit || typeof edit !== "object" || Array.isArray(edit)) continue;
|
|
528
|
+
addPath(getStringProperty(edit as Record<string, unknown>, "rename"));
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const input = getStringProperty(record, "input");
|
|
532
|
+
if (input) {
|
|
533
|
+
try {
|
|
534
|
+
for (const entry of expandApplyPatchToEntries({ input })) {
|
|
535
|
+
addPath(entry.path);
|
|
536
|
+
addPath(entry.rename);
|
|
537
|
+
}
|
|
538
|
+
} catch {
|
|
539
|
+
// If the edit input is not an apply_patch envelope, retain direct paths.
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return paths;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function collectRecentFileMutations(messages: readonly AgentMessage[]): string[] {
|
|
546
|
+
const callsById = new Map<string, string[]>();
|
|
547
|
+
for (const message of messages) {
|
|
548
|
+
if (message.role !== "assistant") continue;
|
|
549
|
+
const content = (message as AssistantMessage).content;
|
|
550
|
+
if (!Array.isArray(content)) continue;
|
|
551
|
+
for (const block of content) {
|
|
552
|
+
if (block.type !== "toolCall" || !FILE_MUTATION_TOOLS.has(block.name)) continue;
|
|
553
|
+
const paths = collectFileMutationPaths(block.name, block.arguments).filter(path => path.length > 0);
|
|
554
|
+
if (paths.length > 0) callsById.set(block.id, paths);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const seen = new Set<string>();
|
|
558
|
+
const paths: string[] = [];
|
|
559
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
560
|
+
const message = messages[index];
|
|
561
|
+
if (message.role !== "toolResult") continue;
|
|
562
|
+
const result = message as { toolName?: string; toolCallId?: string; isError?: boolean };
|
|
563
|
+
if (result.isError || !result.toolName || !FILE_MUTATION_TOOLS.has(result.toolName)) continue;
|
|
564
|
+
const filePaths = result.toolCallId ? callsById.get(result.toolCallId) : undefined;
|
|
565
|
+
if (!filePaths) continue;
|
|
566
|
+
for (let pathIndex = filePaths.length - 1; pathIndex >= 0; pathIndex--) {
|
|
567
|
+
const filePath = filePaths[pathIndex];
|
|
568
|
+
if (!filePath || seen.has(filePath)) continue;
|
|
569
|
+
seen.add(filePath);
|
|
570
|
+
paths.push(filePath);
|
|
571
|
+
if (paths.length >= MAX_RECENT_FILE_MUTATIONS) break;
|
|
572
|
+
}
|
|
573
|
+
if (paths.length >= MAX_RECENT_FILE_MUTATIONS) break;
|
|
574
|
+
}
|
|
575
|
+
return paths;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* #4560: render the structured workflow recovery projection as bounded
|
|
580
|
+
* compaction-context lines. Scope lines mark accepted scope and non-goals so
|
|
581
|
+
* post-compaction continuation reloads the accepted contract instead of
|
|
582
|
+
* re-deriving (and potentially expanding) it from summary prose.
|
|
583
|
+
*/
|
|
584
|
+
function renderWorkflowRecoveryContext(recovery: WorkflowRecoveryProjection): string[] {
|
|
585
|
+
const lines: string[] = [];
|
|
586
|
+
const objective = sanitizeCompactionStateText(recovery.objective, 200);
|
|
587
|
+
lines.push(`Workflow contract (${recovery.skill}): ${objective}`);
|
|
588
|
+
const accepted = recovery.scope.filter(item => item.kind === "accepted").slice(0, 8);
|
|
589
|
+
if (accepted.length > 0) {
|
|
590
|
+
lines.push(`Accepted scope: ${accepted.map(item => sanitizeCompactionStateText(item.text, 120)).join("; ")}`);
|
|
591
|
+
}
|
|
592
|
+
const nonGoals = recovery.scope.filter(item => item.kind === "non_goal").slice(0, 6);
|
|
593
|
+
if (nonGoals.length > 0) {
|
|
594
|
+
lines.push(`Non-goals: ${nonGoals.map(item => sanitizeCompactionStateText(item.text, 120)).join("; ")}`);
|
|
595
|
+
}
|
|
596
|
+
if (recovery.acceptanceCriteria.length > 0) {
|
|
597
|
+
lines.push(
|
|
598
|
+
`Acceptance criteria: ${recovery.acceptanceCriteria.map(item => sanitizeCompactionStateText(item, 120)).join("; ")}`,
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
if (recovery.currentGoal) {
|
|
602
|
+
const goal = recovery.currentGoal;
|
|
603
|
+
lines.push(
|
|
604
|
+
`Current goal: ${sanitizeCompactionStateText(goal.goalId, 40)} status=${sanitizeCompactionStateText(goal.status, 40)} ${sanitizeCompactionStateText(goal.objective, 120)}`,
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
const progress = recovery.progress;
|
|
608
|
+
const progressParts: string[] = [];
|
|
609
|
+
if (progress.totalGoals !== undefined) {
|
|
610
|
+
progressParts.push(`goals ${progress.completedGoals ?? 0}/${progress.totalGoals}`);
|
|
611
|
+
}
|
|
612
|
+
if (progress.outstandingGoals !== undefined) progressParts.push(`outstanding ${progress.outstandingGoals}`);
|
|
613
|
+
if (progress.latestCohortSourceHash) progressParts.push(`sourceHash ${progress.latestCohortSourceHash}`);
|
|
614
|
+
if (progressParts.length > 0) lines.push(`Progress: ${progressParts.join(", ")}`);
|
|
615
|
+
lines.push(
|
|
616
|
+
`Next action: ${recovery.nextAction.actionClass}${recovery.nextAction.goalId ? ` (${recovery.nextAction.goalId})` : ""}`,
|
|
617
|
+
);
|
|
618
|
+
if (recovery.provenance.sha256) lines.push(`Contract digest: ${recovery.provenance.sha256}`);
|
|
619
|
+
return lines;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* #4560: post-compaction continuation for recognized active workflows.
|
|
624
|
+
* Returns undefined when no structured projection exists (generic
|
|
625
|
+
* auto-continue is preserved) or when every active workflow skill is
|
|
626
|
+
* continuation-inert (paused/terminal/unknown stay inert). The prompt keeps
|
|
627
|
+
* latest-user-intent supremacy and forbids silent scope expansion: any work
|
|
628
|
+
* beyond the accepted contract must be classified and recorded, never assumed.
|
|
629
|
+
*/
|
|
630
|
+
function buildWorkflowRecoveryContinuationPrompt(
|
|
631
|
+
recovery: WorkflowRecoveryProjection | undefined,
|
|
632
|
+
activeSkills: ReadonlyArray<{ skill: string; phase: string }>,
|
|
633
|
+
): string | undefined {
|
|
634
|
+
if (!recovery) return undefined;
|
|
635
|
+
const recognized = activeSkills.some(
|
|
636
|
+
entry => entry.skill === recovery.skill && !isWorkflowContinuationInert(entry.skill, entry.phase),
|
|
637
|
+
);
|
|
638
|
+
if (!recognized) return undefined;
|
|
639
|
+
const lines = [
|
|
640
|
+
"Compaction removed earlier conversation history. Resume the active workflow from its durable contract below — do not re-derive or expand scope from the summary.",
|
|
641
|
+
"",
|
|
642
|
+
"<workflow-recovery>",
|
|
643
|
+
...renderWorkflowRecoveryContext(recovery),
|
|
644
|
+
"</workflow-recovery>",
|
|
645
|
+
"",
|
|
646
|
+
"Rules:",
|
|
647
|
+
"- Reload this contract before acting; the durable workflow state (.gjc session state, plans, goals, ledger receipts) is authoritative over any summary prose.",
|
|
648
|
+
"- Resume the stated next action class unless the user's latest message supersedes it; user intent always wins.",
|
|
649
|
+
"- Do not expand accepted scope. Work beyond the accepted scope/non-goals must be classified as new scope and explicitly recorded (durable blocker or steering), never silently accepted.",
|
|
650
|
+
"- Do not repeat already-verified review generations when the recorded source hash and evidence basis are unchanged; continue from recorded progress instead.",
|
|
651
|
+
"- If the same next action has already been attempted with no measurable progress (same source hash, no completed obligations, same blocker state), record a durable blocker/escalation note instead of looping.",
|
|
652
|
+
];
|
|
653
|
+
if (recovery.zeroProgress?.stalled) {
|
|
654
|
+
lines.push(
|
|
655
|
+
`STALLED: durable progress has not changed across ${recovery.zeroProgress.unchangedObservations + 1} compaction recoveries. Do not repeat the same next action again. Record a durable blocker or escalate to the operator now.`,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return lines.join("\n");
|
|
495
659
|
}
|
|
496
660
|
|
|
497
661
|
/** Escape XML-ish metacharacters and flatten newlines so state text cannot break compaction prompt framing. */
|
|
@@ -657,12 +821,6 @@ export interface AgentSessionConfig {
|
|
|
657
821
|
workerIntegrationRequest?: (signal: AbortSignal) => Promise<void>;
|
|
658
822
|
/** Bound terminal worker-integration settlement for embedded hosts and deterministic lifecycle tests. */
|
|
659
823
|
workerIntegrationTimeoutMs?: number;
|
|
660
|
-
/**
|
|
661
|
-
* Override the runtime-owned `gjc team` worker heartbeat reporter. Defaults to a
|
|
662
|
-
* reporter derived from the team worker environment, or none outside a worker pane.
|
|
663
|
-
*/
|
|
664
|
-
teamWorkerHeartbeatReporter?: GjcTeamWorkerHeartbeatReporter;
|
|
665
|
-
|
|
666
824
|
/** Loaded skills (already discovered by SDK) */
|
|
667
825
|
skills?: Skill[];
|
|
668
826
|
/** Skill loading warnings (already captured by SDK) */
|
|
@@ -1050,6 +1208,8 @@ const PROVIDER_FIRST_EVENT_TIMEOUT_ERROR = "Provider stream timed out while wait
|
|
|
1050
1208
|
const WRAPPED_PROVIDER_FIRST_EVENT_TIMEOUT_ERROR = `Error: ${PROVIDER_FIRST_EVENT_TIMEOUT_ERROR}`;
|
|
1051
1209
|
const PROVIDER_FIRST_EVENT_TIMEOUT_WITHOUT_ARTICLE_ERROR = "Provider stream timed out while waiting for first event";
|
|
1052
1210
|
const BARE_DEFAULT_CODEX_OVERLOAD_ERROR = /^Codex error event(?:: .*)? \(code=server_is_overloaded(?:, [^)]+)*\)$/;
|
|
1211
|
+
/** Anthropic's typed capacity-overload `error.type`, the only overload code admitted below. */
|
|
1212
|
+
const ANTHROPIC_OVERLOADED_ERROR_TYPE = "overloaded_error";
|
|
1053
1213
|
const KIMI_CODE_FIRST_EVENT_TIMEOUT_MESSAGES = {
|
|
1054
1214
|
"anthropic-messages": new Set([
|
|
1055
1215
|
PROVIDER_FIRST_EVENT_TIMEOUT_ERROR,
|
|
@@ -1123,6 +1283,40 @@ function isBareDefaultCodexOverload(message: AssistantMessage): boolean {
|
|
|
1123
1283
|
);
|
|
1124
1284
|
}
|
|
1125
1285
|
|
|
1286
|
+
/**
|
|
1287
|
+
* True when the whole error message is Anthropic's own typed capacity-overload
|
|
1288
|
+
* envelope. The provider's `overloaded_error` can arrive as a statusless SSE
|
|
1289
|
+
* error event, so the envelope is the only structured evidence available: it is
|
|
1290
|
+
* parsed as JSON and both the outer `type` and the nested `error.type` must
|
|
1291
|
+
* match exactly. Prose is never inspected, so a message that merely mentions
|
|
1292
|
+
* being overloaded cannot authorize a replay.
|
|
1293
|
+
*/
|
|
1294
|
+
function isAnthropicOverloadedEnvelope(errorMessage: string | undefined): boolean {
|
|
1295
|
+
if (!errorMessage) return false;
|
|
1296
|
+
const trimmed = errorMessage.trim();
|
|
1297
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return false;
|
|
1298
|
+
let parsed: unknown;
|
|
1299
|
+
try {
|
|
1300
|
+
parsed = JSON.parse(trimmed);
|
|
1301
|
+
} catch {
|
|
1302
|
+
return false;
|
|
1303
|
+
}
|
|
1304
|
+
if (typeof parsed !== "object" || parsed === null) return false;
|
|
1305
|
+
if ((parsed as { type?: unknown }).type !== "error") return false;
|
|
1306
|
+
const error = (parsed as { error?: unknown }).error;
|
|
1307
|
+
if (typeof error !== "object" || error === null) return false;
|
|
1308
|
+
return (error as { type?: unknown }).type === ANTHROPIC_OVERLOADED_ERROR_TYPE;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function isBareDefaultAnthropicOverload(message: AssistantMessage): boolean {
|
|
1312
|
+
return (
|
|
1313
|
+
message.api === "anthropic-messages" &&
|
|
1314
|
+
isAnthropicOverloadedEnvelope(message.errorMessage) &&
|
|
1315
|
+
!hasBareDefaultRetryDisqualifyingFacts(message) &&
|
|
1316
|
+
!assistantMessageHasVisibleOrToolContent(message)
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1126
1320
|
function assistantMessageHasVisibleOrToolContent(message: AssistantMessage): boolean {
|
|
1127
1321
|
return message.content.some(content => {
|
|
1128
1322
|
if (content.type === "text") return content.text.length > 0;
|
|
@@ -1922,6 +2116,15 @@ export interface DefaultFallbackRuntimeState {
|
|
|
1922
2116
|
const AGENT_CONTINUE_BUSY_RESCHEDULE_BASE_DELAY_MS = 100;
|
|
1923
2117
|
const AGENT_CONTINUE_BUSY_RESCHEDULE_MAX_DELAY_MS = 5_000;
|
|
1924
2118
|
const AGENT_CONTINUE_BUSY_MAX_RESCHEDULES = 50;
|
|
2119
|
+
/**
|
|
2120
|
+
* Maximum un-charged managed-fallback retries for escaped-non-ASCII tool-call
|
|
2121
|
+
* turns within one logical run. Each retry is a fresh loop with its own
|
|
2122
|
+
* in-loop resample budget, so without this cap a deterministic escaper loops
|
|
2123
|
+
* forever: the fallback chain never sees a charge to exhaust on. Matching the
|
|
2124
|
+
* agent loop's own per-loop budget (MAX_ESCAPED_NONASCII_RESAMPLES + 1 wire
|
|
2125
|
+
* attempts) keeps one steering retry plus one blind retry before the run ends.
|
|
2126
|
+
*/
|
|
2127
|
+
const MAX_ESCAPED_NONASCII_MANAGED_RETRIES = 2;
|
|
1925
2128
|
|
|
1926
2129
|
function agentContinueBusyRescheduleDelayMs(attempt: number): number {
|
|
1927
2130
|
const exponential = AGENT_CONTINUE_BUSY_RESCHEDULE_BASE_DELAY_MS * 2 ** Math.max(0, attempt - 1);
|
|
@@ -2229,6 +2432,8 @@ export class AgentSession {
|
|
|
2229
2432
|
#retryPromise: Promise<void> | undefined = undefined;
|
|
2230
2433
|
#retryResolve: (() => void) | undefined = undefined;
|
|
2231
2434
|
#defaultFallbackController: FallbackChainController | undefined;
|
|
2435
|
+
/** Managed escaped-non-ASCII retries issued for the current logical run. Bounded so a deterministic escaper cannot loop forever through un-charged fallback retries. */
|
|
2436
|
+
#escapedNonAsciiManagedRetries = 0;
|
|
2232
2437
|
#overflowMaintenanceAttempts = 0;
|
|
2233
2438
|
#defaultFallbackExhaustedLastTurn = false;
|
|
2234
2439
|
#fallbackInvocationId = 0;
|
|
@@ -2269,7 +2474,7 @@ export class AgentSession {
|
|
|
2269
2474
|
*/
|
|
2270
2475
|
readonly #ownedAsyncJobManager: AsyncJobManager | undefined;
|
|
2271
2476
|
readonly #disposeAsyncJobManager: boolean;
|
|
2272
|
-
|
|
2477
|
+
#ownedMcpManager: MCPManager | undefined;
|
|
2273
2478
|
#startupTurnBarrier: Promise<void> | undefined;
|
|
2274
2479
|
#pendingPythonMessages: Array<{
|
|
2275
2480
|
message: PythonExecutionMessage;
|
|
@@ -2316,12 +2521,8 @@ export class AgentSession {
|
|
|
2316
2521
|
}
|
|
2317
2522
|
|
|
2318
2523
|
#turnIndex = 0;
|
|
2319
|
-
#workerIntegrationScheduler: WorkerIntegrationRequestScheduler;
|
|
2524
|
+
#workerIntegrationScheduler: WorkerIntegrationRequestScheduler | undefined;
|
|
2320
2525
|
#workerIntegrationRequestedForTurn = false;
|
|
2321
|
-
/** Runtime-owned team worker heartbeat; `undefined` outside a `gjc team` worker pane. */
|
|
2322
|
-
#teamWorkerHeartbeat: GjcTeamWorkerHeartbeatReporter | undefined;
|
|
2323
|
-
#teamWorkerTurnsInFlight = 0;
|
|
2324
|
-
#unregisterTeamWorkerAsyncJobChange: (() => void) | undefined;
|
|
2325
2526
|
// First-party internal before-agent-start contributors (not user hooks).
|
|
2326
2527
|
#beforeAgentStartContributors: BeforeAgentStartContributor[] = [];
|
|
2327
2528
|
|
|
@@ -2378,6 +2579,8 @@ export class AgentSession {
|
|
|
2378
2579
|
#workspaceTreeService: LazyService<WorkspaceTreeRuntime> | undefined;
|
|
2379
2580
|
#onWorkspaceTreeReady: ((tree: WorkspaceTree) => void | Promise<void>) | undefined;
|
|
2380
2581
|
#networkPrewarmService: LazyService<NetworkPrewarmRuntime> | undefined;
|
|
2582
|
+
/** Set by `applyRescopedCwdState`: forces the next turn to re-scan at the new cwd. */
|
|
2583
|
+
#pendingWorkspaceTreeRescope = false;
|
|
2381
2584
|
/** Throttle cache for the per-turn volatile workspace-tree scan (see #buildVolatileProjectContextMessage). */
|
|
2382
2585
|
#cachedWorkspaceTree: WorkspaceTree | undefined;
|
|
2383
2586
|
#cachedWorkspaceTreeAt = 0;
|
|
@@ -2437,6 +2640,18 @@ export class AgentSession {
|
|
|
2437
2640
|
* surface a red "Operation aborted" line; cleared by a later non-silent abort
|
|
2438
2641
|
* or by `abort`'s safety net when no aborted message_end is produced. */
|
|
2439
2642
|
#silentAbortPending = false;
|
|
2643
|
+
/** In-flight `abort()` unwind. Fresh prompts wait so they cannot steer into the dying turn. */
|
|
2644
|
+
#abortUnwind: Promise<void> | undefined;
|
|
2645
|
+
#abortEpoch = 0;
|
|
2646
|
+
/**
|
|
2647
|
+
* Monotonic count of ADMITTED abort requests, advanced synchronously on every
|
|
2648
|
+
* `#abortWithOutcome` entry — including an abort that shares an already
|
|
2649
|
+
* in-flight unwind rather than starting its own. A prompt that waits for an
|
|
2650
|
+
* unwind captures this value and refuses to resume if it advanced while it
|
|
2651
|
+
* waited: abort is terminal, so work admitted before the user's latest abort
|
|
2652
|
+
* must never start just because the physical unwind was shared.
|
|
2653
|
+
*/
|
|
2654
|
+
#abortAdmissionEpoch = 0;
|
|
2440
2655
|
/** Monotonic counter for `enqueueCustomMessageDisplay` tag generation;
|
|
2441
2656
|
* combined with `Date.now()` so tags stay unique even across rapid
|
|
2442
2657
|
* same-tick enqueues. */
|
|
@@ -2488,6 +2703,7 @@ export class AgentSession {
|
|
|
2488
2703
|
nonEditDeterminations: 0,
|
|
2489
2704
|
};
|
|
2490
2705
|
#promptInFlightCount = 0;
|
|
2706
|
+
#inFlightGenerations: number[] = [];
|
|
2491
2707
|
#agentEventHandlersInFlight = 0;
|
|
2492
2708
|
#queuedExtensionEventCount = 0;
|
|
2493
2709
|
#extensionTurnGeneration = 0;
|
|
@@ -2798,12 +3014,18 @@ export class AgentSession {
|
|
|
2798
3014
|
|
|
2799
3015
|
#beginInFlight(): void {
|
|
2800
3016
|
this.#promptInFlightCount++;
|
|
3017
|
+
this.#inFlightGenerations.push(this.#abortEpoch);
|
|
2801
3018
|
if (this.#promptInFlightCount === 1) {
|
|
2802
3019
|
this.#acquirePowerAssertion();
|
|
2803
|
-
// Runtime-owned worker liveness follows Agent turns and background jobs
|
|
2804
|
-
// in #refreshTeamWorkerHeartbeat(), including internally dispatched turns.
|
|
2805
3020
|
}
|
|
2806
3021
|
}
|
|
3022
|
+
|
|
3023
|
+
/** True while a live agent loop or a non-aborted in-flight prompt owns the session. */
|
|
3024
|
+
#isLiveTurnBusy(): boolean {
|
|
3025
|
+
if (this.agent.state.isStreaming) return true;
|
|
3026
|
+
if (!this.isStreaming) return false;
|
|
3027
|
+
return this.#inFlightGenerations.every(generation => generation === this.#abortEpoch);
|
|
3028
|
+
}
|
|
2807
3029
|
/**
|
|
2808
3030
|
* Allocate a FRESH prompt attempt/lineage for an allowed owned-completion
|
|
2809
3031
|
* delivery (corrected turn semantics). The new turn gets a new attempt epoch
|
|
@@ -2853,6 +3075,27 @@ export class AgentSession {
|
|
|
2853
3075
|
return signal.aborted || this.#promptGeneration !== generation;
|
|
2854
3076
|
}
|
|
2855
3077
|
|
|
3078
|
+
/**
|
|
3079
|
+
* Wait for the in-flight abort unwind, then prove no LATER abort was admitted
|
|
3080
|
+
* while waiting. Overlapping aborts share one physical unwind, so awaiting
|
|
3081
|
+
* `#abortUnwind` alone cannot distinguish "the abort I raced" from "a second
|
|
3082
|
+
* abort the user issued after me". Returns false when the caller must not
|
|
3083
|
+
* resume; the caller then rejects as a cancelled preflight rather than
|
|
3084
|
+
* starting a successor the user already aborted.
|
|
3085
|
+
*
|
|
3086
|
+
* Loops because an abort admitted during the wait can install its own unwind:
|
|
3087
|
+
* the fence only opens once no unwind is in flight and the admission epoch has
|
|
3088
|
+
* stopped advancing.
|
|
3089
|
+
*/
|
|
3090
|
+
async #awaitAbortUnwindFence(): Promise<boolean> {
|
|
3091
|
+
const admissionEpoch = this.#abortAdmissionEpoch;
|
|
3092
|
+
while (this.#abortUnwind !== undefined) {
|
|
3093
|
+
await this.#abortUnwind;
|
|
3094
|
+
if (this.#abortAdmissionEpoch !== admissionEpoch) return false;
|
|
3095
|
+
}
|
|
3096
|
+
return this.#abortAdmissionEpoch === admissionEpoch;
|
|
3097
|
+
}
|
|
3098
|
+
|
|
2856
3099
|
#throwIfPromptPreflightCancelled(generation: number, signal: AbortSignal): void {
|
|
2857
3100
|
if (this.#isPromptPreflightCancelled(generation, signal)) {
|
|
2858
3101
|
throw promptPreflightCancelledError();
|
|
@@ -3068,6 +3311,44 @@ export class AgentSession {
|
|
|
3068
3311
|
#wakeScopedSettlementWaiters(): void {
|
|
3069
3312
|
for (const check of [...this.#scopedSettlementWaiters]) check();
|
|
3070
3313
|
}
|
|
3314
|
+
#abortedTurnTerminalPending(): boolean {
|
|
3315
|
+
return (
|
|
3316
|
+
this.#promptInFlightCount > 0 ||
|
|
3317
|
+
this.#agentEventHandlersInFlight > 0 ||
|
|
3318
|
+
this.#pendingAgentEndEmit !== undefined ||
|
|
3319
|
+
this.#agentEndPublicationInFlight > 0
|
|
3320
|
+
);
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
/** Wait for the aborted turn's agent_end handler and publication, not just agent idle. */
|
|
3324
|
+
async #awaitAbortedTurnTerminal(): Promise<void> {
|
|
3325
|
+
await Promise.resolve();
|
|
3326
|
+
await this.#agentEndHandlingPromise;
|
|
3327
|
+
this.#flushPendingAgentEnd();
|
|
3328
|
+
await this.#agentEndPublicationPromise;
|
|
3329
|
+
while (this.#abortedTurnTerminalPending()) {
|
|
3330
|
+
const wake = Promise.withResolvers<void>();
|
|
3331
|
+
const check = () => {
|
|
3332
|
+
if (!this.#abortedTurnTerminalPending()) wake.resolve();
|
|
3333
|
+
};
|
|
3334
|
+
this.#scopedSettlementWaiters.add(check);
|
|
3335
|
+
check();
|
|
3336
|
+
try {
|
|
3337
|
+
// Wait ONLY on the state-change wake. Racing the agent_end handling or
|
|
3338
|
+
// publication promise here spins: an in-flight event handler that is not
|
|
3339
|
+
// an agent_end (a `message_end` extension handler, say) leaves both of
|
|
3340
|
+
// those already settled, so the race resolves immediately and the loop
|
|
3341
|
+
// re-runs as a microtask, starving timers until the handler finishes.
|
|
3342
|
+
// Every counter this loop reads wakes the scoped waiters when it drops.
|
|
3343
|
+
await wake.promise;
|
|
3344
|
+
} finally {
|
|
3345
|
+
this.#scopedSettlementWaiters.delete(check);
|
|
3346
|
+
}
|
|
3347
|
+
this.#flushPendingAgentEnd();
|
|
3348
|
+
await this.#agentEndHandlingPromise;
|
|
3349
|
+
await this.#agentEndPublicationPromise;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3071
3352
|
|
|
3072
3353
|
/**
|
|
3073
3354
|
* Wait for session settlement. `ignoreSelectionFenceGeneration` carries the
|
|
@@ -3107,10 +3388,10 @@ export class AgentSession {
|
|
|
3107
3388
|
|
|
3108
3389
|
#endInFlight(): unknown {
|
|
3109
3390
|
this.#promptInFlightCount = Math.max(0, this.#promptInFlightCount - 1);
|
|
3391
|
+
if (this.#inFlightGenerations.length > 0) this.#inFlightGenerations.shift();
|
|
3110
3392
|
if (this.#promptInFlightCount !== 0) return undefined;
|
|
3111
3393
|
|
|
3112
3394
|
this.#releasePowerAssertion();
|
|
3113
|
-
this.#refreshTeamWorkerHeartbeat();
|
|
3114
3395
|
let flushError: unknown;
|
|
3115
3396
|
try {
|
|
3116
3397
|
// The turn is over, so nothing can split a tool_use/tool_result pair any
|
|
@@ -3168,17 +3449,6 @@ export class AgentSession {
|
|
|
3168
3449
|
if (errors.length > 1) throw new AggregateError(errors, "Multiple deferred prompt messages failed to flush");
|
|
3169
3450
|
}
|
|
3170
3451
|
|
|
3171
|
-
#refreshTeamWorkerHeartbeat(): void {
|
|
3172
|
-
const hasOwnedBackgroundJob =
|
|
3173
|
-
this.#agentId !== undefined &&
|
|
3174
|
-
(this.#ownedAsyncJobManager?.getRunningJobs({ ownerId: this.#agentId }).length ?? 0) > 0;
|
|
3175
|
-
if (this.#teamWorkerTurnsInFlight > 0 || hasOwnedBackgroundJob) {
|
|
3176
|
-
this.#teamWorkerHeartbeat?.start();
|
|
3177
|
-
} else {
|
|
3178
|
-
this.#teamWorkerHeartbeat?.stop();
|
|
3179
|
-
}
|
|
3180
|
-
}
|
|
3181
|
-
|
|
3182
3452
|
#flushPendingAgentEnd(): void {
|
|
3183
3453
|
if (
|
|
3184
3454
|
this.#promptInFlightCount > 0 ||
|
|
@@ -3280,25 +3550,11 @@ export class AgentSession {
|
|
|
3280
3550
|
}
|
|
3281
3551
|
});
|
|
3282
3552
|
this.memoryBackend = config.memoryBackend ?? createMemoryBackendService(this.settings);
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
logger.warn("GJC team worker integration request failed", { error: String(error) });
|
|
3289
|
-
},
|
|
3290
|
-
);
|
|
3291
|
-
}),
|
|
3292
|
-
config.workerIntegrationTimeoutMs,
|
|
3293
|
-
);
|
|
3294
|
-
// One publisher per worker process: subagent sessions share this process and its
|
|
3295
|
-
// team env, so letting each create a reporter would run N timers writing the same
|
|
3296
|
-
// record. The top-level session owns the pane's liveness.
|
|
3297
|
-
this.#teamWorkerHeartbeat =
|
|
3298
|
-
config.teamWorkerHeartbeatReporter ??
|
|
3299
|
-
((config.taskDepth ?? 0) === 0
|
|
3300
|
-
? GjcTeamWorkerHeartbeatReporter.forProcess(() => this.sessionManager.getCwd())
|
|
3301
|
-
: undefined);
|
|
3553
|
+
// Worker integration is an injected seam only: hosts that need turn-end
|
|
3554
|
+
// integration supply the request, and no scheduler exists otherwise.
|
|
3555
|
+
this.#workerIntegrationScheduler = config.workerIntegrationRequest
|
|
3556
|
+
? new WorkerIntegrationRequestScheduler(config.workerIntegrationRequest, config.workerIntegrationTimeoutMs)
|
|
3557
|
+
: undefined;
|
|
3302
3558
|
this.notificationSessionController = config.notificationSessionController;
|
|
3303
3559
|
this.taskDepth = config.taskDepth ?? 0;
|
|
3304
3560
|
this.#workflowGatePublication = config.workflowGatePublication ?? "endpoint";
|
|
@@ -3542,9 +3798,6 @@ export class AgentSession {
|
|
|
3542
3798
|
this.#ttsrManager = config.ttsrManager;
|
|
3543
3799
|
this.#obfuscator = config.obfuscator;
|
|
3544
3800
|
this.#agentId = config.agentId;
|
|
3545
|
-
this.#unregisterTeamWorkerAsyncJobChange = this.#ownedAsyncJobManager?.onChange(() => {
|
|
3546
|
-
this.#refreshTeamWorkerHeartbeat();
|
|
3547
|
-
});
|
|
3548
3801
|
this.#agentRegistry = config.agentRegistry;
|
|
3549
3802
|
this.#providerSessionId = config.providerSessionId;
|
|
3550
3803
|
this.#credentialSessionId = config.credentialSessionId;
|
|
@@ -3768,6 +4021,61 @@ export class AgentSession {
|
|
|
3768
4021
|
...(this.#activeSkillState.sessionId ? { session_id: this.#activeSkillState.sessionId } : {}),
|
|
3769
4022
|
};
|
|
3770
4023
|
}
|
|
4024
|
+
/**
|
|
4025
|
+
* Live prompt marker or restored durable workflow — the effective state the
|
|
4026
|
+
* cwd-local mutation guard must honor after resume.
|
|
4027
|
+
*/
|
|
4028
|
+
getEffectiveActiveWorkflowSkillState(): { skill: string; sessionId: string } | undefined {
|
|
4029
|
+
const currentSessionId = this.sessionManager.getSessionId();
|
|
4030
|
+
const inMemory = this.#activeSkillState;
|
|
4031
|
+
if (
|
|
4032
|
+
inMemory &&
|
|
4033
|
+
(!inMemory.sessionId || inMemory.sessionId === currentSessionId) &&
|
|
4034
|
+
isCanonicalGjcWorkflowSkill(inMemory.skill)
|
|
4035
|
+
) {
|
|
4036
|
+
return { skill: inMemory.skill, sessionId: inMemory.sessionId ?? currentSessionId };
|
|
4037
|
+
}
|
|
4038
|
+
if (
|
|
4039
|
+
this.#restoredWorkflowSkillState?.sessionId === currentSessionId &&
|
|
4040
|
+
isCanonicalGjcWorkflowSkill(this.#restoredWorkflowSkillState.skill)
|
|
4041
|
+
) {
|
|
4042
|
+
return this.#restoredWorkflowSkillState;
|
|
4043
|
+
}
|
|
4044
|
+
return undefined;
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
/** Replace the session-owned MCP manager after a cwd rescope. */
|
|
4048
|
+
async replaceOwnedMcpManager(next: MCPManager | undefined): Promise<void> {
|
|
4049
|
+
const previous = this.#ownedMcpManager;
|
|
4050
|
+
if (previous && previous !== next) {
|
|
4051
|
+
await previous.disconnectAll().catch(() => {});
|
|
4052
|
+
if (MCPManager.instance() === previous) MCPManager.setInstance(undefined);
|
|
4053
|
+
}
|
|
4054
|
+
this.#ownedMcpManager = next;
|
|
4055
|
+
if (next && MCPManager.instance() === undefined) MCPManager.setInstance(next);
|
|
4056
|
+
}
|
|
4057
|
+
|
|
4058
|
+
/** Swap named custom/project tools after a cwd rescope. */
|
|
4059
|
+
async replaceNamedCustomTools(previousNames: readonly string[], nextTools: CustomTool[]): Promise<void> {
|
|
4060
|
+
const previous = new Set(previousNames);
|
|
4061
|
+
const previousActive = this.getActiveToolNames();
|
|
4062
|
+
for (const name of previous) this.#toolRegistry.delete(name);
|
|
4063
|
+
const getCustomToolContext = () => this.#getCustomToolContext();
|
|
4064
|
+
const added: string[] = [];
|
|
4065
|
+
for (const customTool of nextTools) {
|
|
4066
|
+
const wrapped = CustomToolAdapter.wrap(customTool, getCustomToolContext) as AgentTool;
|
|
4067
|
+
const finalTool = (
|
|
4068
|
+
this.#extensionRunner ? new ExtensionToolWrapper(wrapped, this.#extensionRunner) : wrapped
|
|
4069
|
+
) as AgentTool;
|
|
4070
|
+
this.#toolRegistry.set(finalTool.name, finalTool);
|
|
4071
|
+
added.push(finalTool.name);
|
|
4072
|
+
}
|
|
4073
|
+
this.#invalidateDiscoveryCaches();
|
|
4074
|
+
await this.#applyActiveToolsByName([
|
|
4075
|
+
...previousActive.filter(name => !previous.has(name)),
|
|
4076
|
+
...added.filter(name => !previous.has(name) || previousActive.includes(name)),
|
|
4077
|
+
]);
|
|
4078
|
+
}
|
|
3771
4079
|
|
|
3772
4080
|
/** Best-effort accessor for the active skill's `current_phase` field from
|
|
3773
4081
|
* its persisted mode-state file. Used by the `skill` tool to enforce the
|
|
@@ -3798,13 +4106,7 @@ export class AgentSession {
|
|
|
3798
4106
|
/** Provider-facing ask metadata must expose only the active deep-interview phase. */
|
|
3799
4107
|
getDeepInterviewAskStage(): "topology" | "post-topology" | undefined {
|
|
3800
4108
|
const currentSessionId = this.sessionManager.getSessionId();
|
|
3801
|
-
const
|
|
3802
|
-
const active =
|
|
3803
|
-
inMemory && (!inMemory.sessionId || inMemory.sessionId === currentSessionId)
|
|
3804
|
-
? inMemory
|
|
3805
|
-
: this.#restoredWorkflowSkillState?.sessionId === currentSessionId
|
|
3806
|
-
? this.#restoredWorkflowSkillState
|
|
3807
|
-
: undefined;
|
|
4109
|
+
const active = this.getEffectiveActiveWorkflowSkillState();
|
|
3808
4110
|
if (active?.skill !== "deep-interview") return undefined;
|
|
3809
4111
|
try {
|
|
3810
4112
|
assertNonEmptyGjcSessionId(currentSessionId, "AgentSession.getDeepInterviewAskStage");
|
|
@@ -4589,10 +4891,19 @@ export class AgentSession {
|
|
|
4589
4891
|
if (event.type === "agent_end" && !maintenanceCheckpoint) {
|
|
4590
4892
|
if (terminalOwner ? !terminalClaim?.ok : this.activePromptHandle !== undefined) return;
|
|
4591
4893
|
}
|
|
4592
|
-
//
|
|
4593
|
-
//
|
|
4594
|
-
//
|
|
4595
|
-
|
|
4894
|
+
// Terminal publication is ownership-sensitive and is handled above. A
|
|
4895
|
+
// message/turn lifecycle event is different: its canonical transcript
|
|
4896
|
+
// append is the evidence needed to recover the run. Do not drop that
|
|
4897
|
+
// evidence merely because the best-effort post-prompt lease could not be
|
|
4898
|
+
// acquired. The current-handle check still fences a genuinely late event
|
|
4899
|
+
// from a replaced run without allowing a successor handle to be borrowed.
|
|
4900
|
+
if (
|
|
4901
|
+
activePromptHandle &&
|
|
4902
|
+
!eventLease &&
|
|
4903
|
+
event.type !== "agent_end" &&
|
|
4904
|
+
this.activePromptHandle !== activePromptHandle
|
|
4905
|
+
)
|
|
4906
|
+
return;
|
|
4596
4907
|
if (eventLease && event.type === "agent_end")
|
|
4597
4908
|
this.#postPromptLeases.set(eventLease.resourceRunId, eventLease);
|
|
4598
4909
|
if (eventLease) {
|
|
@@ -4622,6 +4933,10 @@ export class AgentSession {
|
|
|
4622
4933
|
this.#agentEventHandlersInFlight = Math.max(0, this.#agentEventHandlersInFlight - 1);
|
|
4623
4934
|
this.#flushPendingAgentEnd();
|
|
4624
4935
|
agentEndHandled?.resolve();
|
|
4936
|
+
// Every other in-flight counter republishes settlement when it drops; this
|
|
4937
|
+
// one is read by the abort drain and the session settlement waiters too,
|
|
4938
|
+
// so a handler finishing has to wake them or they wait on nothing.
|
|
4939
|
+
this.#resolveSessionSettlement();
|
|
4625
4940
|
}
|
|
4626
4941
|
})();
|
|
4627
4942
|
if (eventLease) eventLease.track("post_prompt", "agent-session-event", handler);
|
|
@@ -4678,18 +4993,6 @@ export class AgentSession {
|
|
|
4678
4993
|
} else if (event.type === "turn_end") {
|
|
4679
4994
|
this.#closedExtensionTurnGeneration = this.#extensionTurnGeneration;
|
|
4680
4995
|
}
|
|
4681
|
-
if (event.type === "turn_start") {
|
|
4682
|
-
this.#teamWorkerTurnsInFlight++;
|
|
4683
|
-
this.#refreshTeamWorkerHeartbeat();
|
|
4684
|
-
} else if (event.type === "turn_end") {
|
|
4685
|
-
this.#teamWorkerTurnsInFlight = Math.max(0, this.#teamWorkerTurnsInFlight - 1);
|
|
4686
|
-
this.#refreshTeamWorkerHeartbeat();
|
|
4687
|
-
} else if (event.type === "agent_end") {
|
|
4688
|
-
// Failed Agent runs can emit agent_end without turn_end. Reset rather than
|
|
4689
|
-
// leaking a positive count that would report an idle/failed worker forever.
|
|
4690
|
-
this.#teamWorkerTurnsInFlight = 0;
|
|
4691
|
-
this.#refreshTeamWorkerHeartbeat();
|
|
4692
|
-
}
|
|
4693
4996
|
if (event.type === "message_update") {
|
|
4694
4997
|
// Fast path: message_update maps to no sidecar state, so we must not
|
|
4695
4998
|
// build the persistRuntimeState closure here (per-token hot path).
|
|
@@ -6197,14 +6500,24 @@ export class AgentSession {
|
|
|
6197
6500
|
return;
|
|
6198
6501
|
}
|
|
6199
6502
|
if (!(await continuationAuthorized(signal))) return;
|
|
6503
|
+
// #4560: recognized active workflows resume from their
|
|
6504
|
+
// durable structured contract instead of the generic
|
|
6505
|
+
// prompt; unknown/paused/terminal workflows keep the
|
|
6506
|
+
// generic continuation and latest-user-intent supremacy.
|
|
6507
|
+
const recoverySnapshot = await this.#compactionStateSnapshot();
|
|
6508
|
+
const recoveryPrompt = buildWorkflowRecoveryContinuationPrompt(
|
|
6509
|
+
recoverySnapshot.workflowRecovery,
|
|
6510
|
+
recoverySnapshot.activeSkills,
|
|
6511
|
+
);
|
|
6512
|
+
const promptText = recoveryPrompt ?? autoContinuePrompt;
|
|
6200
6513
|
await this.#promptWithMessage(
|
|
6201
6514
|
{
|
|
6202
6515
|
role: "developer",
|
|
6203
|
-
content: [{ type: "text", text:
|
|
6516
|
+
content: [{ type: "text", text: promptText }],
|
|
6204
6517
|
attribution: "agent",
|
|
6205
6518
|
timestamp: Date.now(),
|
|
6206
6519
|
},
|
|
6207
|
-
|
|
6520
|
+
promptText,
|
|
6208
6521
|
{
|
|
6209
6522
|
skipPostPromptRecoveryWait: true,
|
|
6210
6523
|
skipCompactionCheck: true,
|
|
@@ -6981,11 +7294,11 @@ export class AgentSession {
|
|
|
6981
7294
|
}
|
|
6982
7295
|
|
|
6983
7296
|
#requestWorkerIntegrationAttempt(): void {
|
|
6984
|
-
this.#workerIntegrationScheduler
|
|
7297
|
+
this.#workerIntegrationScheduler?.enqueue();
|
|
6985
7298
|
}
|
|
6986
7299
|
|
|
6987
7300
|
async #flushWorkerIntegrationAttempt(): Promise<void> {
|
|
6988
|
-
await this.#workerIntegrationScheduler
|
|
7301
|
+
await this.#workerIntegrationScheduler?.flush();
|
|
6989
7302
|
}
|
|
6990
7303
|
|
|
6991
7304
|
async #flushWorkerIntegrationForAgentEnd(): Promise<void> {
|
|
@@ -7377,6 +7690,7 @@ export class AgentSession {
|
|
|
7377
7690
|
}
|
|
7378
7691
|
|
|
7379
7692
|
async #dispose(): Promise<void> {
|
|
7693
|
+
await this.sessionManager.joinCwdTransition();
|
|
7380
7694
|
const admissionClosed = this.#closeSessionAdmission();
|
|
7381
7695
|
this.#isDisposed = true;
|
|
7382
7696
|
// Reject new direct Python starts as soon as disposal begins (synchronously,
|
|
@@ -7494,9 +7808,6 @@ export class AgentSession {
|
|
|
7494
7808
|
await disposeKernelSessionsByOwner(this.#evalKernelOwnerId);
|
|
7495
7809
|
await disposeVmContextsByOwner(this.#evalKernelOwnerId);
|
|
7496
7810
|
this.#releasePowerAssertion();
|
|
7497
|
-
this.#unregisterTeamWorkerAsyncJobChange?.();
|
|
7498
|
-
this.#unregisterTeamWorkerAsyncJobChange = undefined;
|
|
7499
|
-
this.#teamWorkerHeartbeat?.dispose();
|
|
7500
7811
|
// Disconnect the agent event listener BEFORE closing session resources so a late
|
|
7501
7812
|
// provider/tool message_end cannot append to the closing SessionManager.
|
|
7502
7813
|
this.#disconnectFromAgent();
|
|
@@ -7547,9 +7858,6 @@ export class AgentSession {
|
|
|
7547
7858
|
this.#unregisterResourceGc = undefined;
|
|
7548
7859
|
this.#unregisterSessionMemorySettings?.();
|
|
7549
7860
|
this.#unregisterSessionMemorySettings = undefined;
|
|
7550
|
-
this.#unregisterTeamWorkerAsyncJobChange?.();
|
|
7551
|
-
this.#unregisterTeamWorkerAsyncJobChange = undefined;
|
|
7552
|
-
this.#teamWorkerHeartbeat?.dispose();
|
|
7553
7861
|
const work = Promise.allSettled([
|
|
7554
7862
|
// kill:true so a forced exit also reaps spawned-app Chrome we own (headless
|
|
7555
7863
|
// always closes; connected/attached browsers only disconnect — never killed).
|
|
@@ -7562,15 +7870,22 @@ export class AgentSession {
|
|
|
7562
7870
|
disposeVmContextsByOwner(kernelOwnerId).catch((error: unknown) =>
|
|
7563
7871
|
logger.warn("signal teardown: disposeVmContextsByOwner failed", { error }),
|
|
7564
7872
|
),
|
|
7565
|
-
// Tool-owned resources (e.g.
|
|
7566
|
-
// kernel) register their own disposer
|
|
7567
|
-
//
|
|
7568
|
-
//
|
|
7569
|
-
//
|
|
7570
|
-
//
|
|
7873
|
+
// Tool-owned resources (e.g. the python tool's persistent per-session
|
|
7874
|
+
// kernel) register their own disposer keyed by an owner id that is NOT
|
|
7875
|
+
// #evalKernelOwnerId, so the two disposals above never reach them. Drain
|
|
7876
|
+
// BOTH cleanup sets here inside the same budget: the SDK binds a tool's
|
|
7877
|
+
// `registerSessionCleanup` to registerToolSessionTransitionCleanup, so a
|
|
7878
|
+
// python REPL kernel lands in the transition set while a directly-registered
|
|
7879
|
+
// cleanup lands in the other. Graceful dispose drains both; draining only
|
|
7880
|
+
// one here left signal exit orphaning the subprocess. Each runner clears
|
|
7881
|
+
// its set, so a graceful dispose running first makes this a no-op rather
|
|
7882
|
+
// than a double free.
|
|
7571
7883
|
this.#runToolSessionCleanups().catch((error: unknown) =>
|
|
7572
7884
|
logger.warn("signal teardown: tool session cleanups failed", { error }),
|
|
7573
7885
|
),
|
|
7886
|
+
this.#runToolSessionTransitionCleanups().catch((error: unknown) =>
|
|
7887
|
+
logger.warn("signal teardown: tool session transition cleanups failed", { error }),
|
|
7888
|
+
),
|
|
7574
7889
|
]);
|
|
7575
7890
|
await Promise.race([work, Bun.sleep(timeoutMs)]);
|
|
7576
7891
|
}
|
|
@@ -8204,6 +8519,35 @@ export class AgentSession {
|
|
|
8204
8519
|
},
|
|
8205
8520
|
}) as T;
|
|
8206
8521
|
}
|
|
8522
|
+
#wrapToolForCwdTransitionFence<T extends AgentTool>(tool: T): T {
|
|
8523
|
+
if (tool.name === "move_session") return tool;
|
|
8524
|
+
return new Proxy(tool, {
|
|
8525
|
+
get: (target, prop) => {
|
|
8526
|
+
if (prop !== "execute") return Reflect.get(target, prop, target);
|
|
8527
|
+
return async (
|
|
8528
|
+
toolCallId: string,
|
|
8529
|
+
args: unknown,
|
|
8530
|
+
signal: AbortSignal | undefined,
|
|
8531
|
+
onUpdate: never,
|
|
8532
|
+
ctx: never,
|
|
8533
|
+
) => {
|
|
8534
|
+
return await this.sessionManager.runWithCwdReadLease(async () => {
|
|
8535
|
+
const admittedGeneration = this.sessionManager.getCwdGeneration();
|
|
8536
|
+
const result = await target.execute(toolCallId, args as never, signal, onUpdate, ctx);
|
|
8537
|
+
// The lease keeps writers out for the whole execution, so this can
|
|
8538
|
+
// only trip if a caller bypassed the lease; surface it rather than
|
|
8539
|
+
// returning a result computed against a retired cwd.
|
|
8540
|
+
if (this.sessionManager.getCwdGeneration() !== admittedGeneration) {
|
|
8541
|
+
throw new Error(
|
|
8542
|
+
"Session working directory changed while this tool executed; retry against the new cwd.",
|
|
8543
|
+
);
|
|
8544
|
+
}
|
|
8545
|
+
return result;
|
|
8546
|
+
});
|
|
8547
|
+
};
|
|
8548
|
+
},
|
|
8549
|
+
}) as T;
|
|
8550
|
+
}
|
|
8207
8551
|
|
|
8208
8552
|
/**
|
|
8209
8553
|
/** Wrap a tool with the workflow mutation guard before permissions or execution. */
|
|
@@ -8251,16 +8595,18 @@ export class AgentSession {
|
|
|
8251
8595
|
let wrappersByVersion = this.#guardedToolWrapperCache.get(tool);
|
|
8252
8596
|
const cached = wrappersByVersion?.get(cacheKey);
|
|
8253
8597
|
if (cached) return cached as T;
|
|
8254
|
-
const wrapped = this.#
|
|
8255
|
-
this.#
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8598
|
+
const wrapped = this.#wrapToolForCwdTransitionFence(
|
|
8599
|
+
this.#wrapToolForWorkflowMutationGuard(
|
|
8600
|
+
this.#wrapToolForAcpPermission(
|
|
8601
|
+
guardToolForUltragoalAsk(
|
|
8602
|
+
tool,
|
|
8603
|
+
() => this.sessionManager.getCwd(),
|
|
8604
|
+
() => ({
|
|
8605
|
+
activeSkillState: this.getActiveSkillState(),
|
|
8606
|
+
sessionId: this.sessionManager.getSessionId(),
|
|
8607
|
+
}),
|
|
8608
|
+
() => this.getSessionAgentDir(),
|
|
8609
|
+
),
|
|
8264
8610
|
),
|
|
8265
8611
|
),
|
|
8266
8612
|
);
|
|
@@ -9771,18 +10117,26 @@ export class AgentSession {
|
|
|
9771
10117
|
this.#cachedWorkspaceTreeAt = Date.now();
|
|
9772
10118
|
this.#initialWorkspaceTree = undefined;
|
|
9773
10119
|
includeTree = this.#cachedWorkspaceTree;
|
|
9774
|
-
} else if (
|
|
10120
|
+
} else if (
|
|
10121
|
+
this.#pendingWorkspaceTreeRescope ||
|
|
10122
|
+
Date.now() - this.#cachedWorkspaceTreeAt >= VOLATILE_TREE_TTL_MS
|
|
10123
|
+
) {
|
|
10124
|
+
// A rescope retires the cached tree regardless of TTL, and must re-scan
|
|
10125
|
+
// rather than reuse the launch-root snapshot the service already holds.
|
|
10126
|
+
const rescoped = this.#pendingWorkspaceTreeRescope;
|
|
10127
|
+
this.#pendingWorkspaceTreeRescope = false;
|
|
9775
10128
|
if (this.#workspaceTreeService) {
|
|
9776
|
-
const firstWorkspaceTree = this.#cachedWorkspaceTreeAt === 0;
|
|
10129
|
+
const firstWorkspaceTree = this.#cachedWorkspaceTreeAt === 0 && !rescoped;
|
|
9777
10130
|
const runtime = await this.#workspaceTreeService.get("first-turn-barrier");
|
|
9778
10131
|
this.#cachedWorkspaceTree = firstWorkspaceTree ? runtime.snapshot : await runtime.refresh();
|
|
9779
|
-
publishStableWorkspaceTree = firstWorkspaceTree;
|
|
10132
|
+
publishStableWorkspaceTree = firstWorkspaceTree || rescoped;
|
|
9780
10133
|
} else {
|
|
9781
10134
|
try {
|
|
9782
10135
|
this.#cachedWorkspaceTree = await buildWorkspaceTree(cwd, { timeoutMs: 5000 });
|
|
9783
10136
|
} catch {
|
|
9784
10137
|
this.#cachedWorkspaceTree = undefined;
|
|
9785
10138
|
}
|
|
10139
|
+
publishStableWorkspaceTree = rescoped;
|
|
9786
10140
|
}
|
|
9787
10141
|
this.#cachedWorkspaceTreeAt = Date.now();
|
|
9788
10142
|
includeTree = this.#cachedWorkspaceTree;
|
|
@@ -9867,18 +10221,31 @@ export class AgentSession {
|
|
|
9867
10221
|
assertImagePlaceholdersHavePayload(expandedText, options?.images);
|
|
9868
10222
|
const workflowIntentDiff = options?.synthetic ? null : buildWorkflowIntentDiff(expandedText);
|
|
9869
10223
|
const claimsGenuineUserIntent = !options?.synthetic && options?.attribution !== "agent";
|
|
9870
|
-
|
|
9871
|
-
|
|
10224
|
+
let admissionGeneration = this.#promptGeneration;
|
|
10225
|
+
let admissionSignal = options?.preflightSignal
|
|
9872
10226
|
? AbortSignal.any([this.#promptPreflightAbortController.signal, options.preflightSignal])
|
|
9873
10227
|
: this.#promptPreflightAbortController.signal;
|
|
9874
10228
|
if (this.#pendingSelectionFences > 0) {
|
|
9875
10229
|
await awaitPromptInvocationPreflight(this.#selectionFenceTail, admissionSignal);
|
|
9876
10230
|
}
|
|
10231
|
+
let waitedForAbortUnwind = false;
|
|
10232
|
+
if (this.#abortUnwind) {
|
|
10233
|
+
// A LATER abort admitted while this prompt waited keeps abort terminal:
|
|
10234
|
+
// the successor is refused instead of refreshing its generation and
|
|
10235
|
+
// starting work the user already aborted twice.
|
|
10236
|
+
if (!(await this.#awaitAbortUnwindFence())) throw promptPreflightCancelledError();
|
|
10237
|
+
admissionGeneration = this.#promptGeneration;
|
|
10238
|
+
admissionSignal = options?.preflightSignal
|
|
10239
|
+
? AbortSignal.any([this.#promptPreflightAbortController.signal, options.preflightSignal])
|
|
10240
|
+
: this.#promptPreflightAbortController.signal;
|
|
10241
|
+
waitedForAbortUnwind = true;
|
|
10242
|
+
}
|
|
9877
10243
|
const deepInterviewUserIntentEpoch =
|
|
9878
10244
|
claimsGenuineUserIntent && !this.isStreaming ? this.#claimDeepInterviewUserIntent() : undefined;
|
|
9879
10245
|
|
|
9880
|
-
// If streaming, queue via steer() or followUp() based on option
|
|
9881
|
-
|
|
10246
|
+
// If streaming, queue via steer() or followUp() based on option.
|
|
10247
|
+
// Abort unwind is awaited above so a successor is not busy against leftover in-flight.
|
|
10248
|
+
if (this.#isLiveTurnBusy() && !waitedForAbortUnwind) {
|
|
9882
10249
|
if (!options?.streamingBehavior) {
|
|
9883
10250
|
throw new AgentBusyError();
|
|
9884
10251
|
}
|
|
@@ -9972,7 +10339,7 @@ export class AgentSession {
|
|
|
9972
10339
|
// workflow skills can always call it.
|
|
9973
10340
|
if (active && isCanonicalGjcWorkflowSkill(skill)) this.#attachAskTool();
|
|
9974
10341
|
const sessionId = this.sessionManager.getSessionId();
|
|
9975
|
-
// Canonical GJC workflow skills (deep-interview, ralplan, ultragoal,
|
|
10342
|
+
// Canonical GJC workflow skills (deep-interview, ralplan, ultragoal, autoresearch)
|
|
9976
10343
|
// own their `.gjc/state/skill-active-state.json` row through the
|
|
9977
10344
|
// `gjc state handoff` and `gjc state clear` runtime verbs. The prompt
|
|
9978
10345
|
// observer must not overwrite an existing row (that clobbered handoff
|
|
@@ -10989,7 +11356,7 @@ export class AgentSession {
|
|
|
10989
11356
|
if (this.isRetrying) return false;
|
|
10990
11357
|
const messages = this.agent.state.messages;
|
|
10991
11358
|
const last = messages[messages.length - 1];
|
|
10992
|
-
return last?.role === "assistant";
|
|
11359
|
+
return last?.role === "assistant" || last?.role === "bashExecution" || last?.role === "pythonExecution";
|
|
10993
11360
|
}
|
|
10994
11361
|
|
|
10995
11362
|
queueDeferredMessage(message: CustomMessage): void {
|
|
@@ -11440,10 +11807,21 @@ export class AgentSession {
|
|
|
11440
11807
|
if (images.length === 0) images = undefined;
|
|
11441
11808
|
}
|
|
11442
11809
|
|
|
11443
|
-
|
|
11810
|
+
let admissionSignal = options?.preflightSignal
|
|
11444
11811
|
? AbortSignal.any([this.#promptPreflightAbortController.signal, options.preflightSignal])
|
|
11445
11812
|
: this.#promptPreflightAbortController.signal;
|
|
11446
|
-
|
|
11813
|
+
let preflightCancellationGeneration = this.#promptPreflightCancellationGeneration;
|
|
11814
|
+
let waitedForAbortUnwind = false;
|
|
11815
|
+
if (this.#abortUnwind && options?.deliverAs === undefined) {
|
|
11816
|
+
// Same terminal-abort fence as `prompt()`: an overlapping second abort
|
|
11817
|
+
// refuses this retained submission instead of letting it start late.
|
|
11818
|
+
if (!(await this.#awaitAbortUnwindFence())) throw promptPreflightCancelledError();
|
|
11819
|
+
preflightCancellationGeneration = this.#promptPreflightCancellationGeneration;
|
|
11820
|
+
admissionSignal = options?.preflightSignal
|
|
11821
|
+
? AbortSignal.any([this.#promptPreflightAbortController.signal, options.preflightSignal])
|
|
11822
|
+
: this.#promptPreflightAbortController.signal;
|
|
11823
|
+
waitedForAbortUnwind = true;
|
|
11824
|
+
}
|
|
11447
11825
|
// Classify and reserve follow-up order synchronously, before any await
|
|
11448
11826
|
// (selection fence or durable acceptance): a follow-up dispatch that is
|
|
11449
11827
|
// not yet durably enqueued must already count as ahead, or a later plain
|
|
@@ -11451,7 +11829,14 @@ export class AgentSession {
|
|
|
11451
11829
|
// delivery/steering and overtake it. The reservation is released once
|
|
11452
11830
|
// the durable enqueue settles either way, so a rejected or cancelled
|
|
11453
11831
|
// acceptance leaves no phantom ordering behind.
|
|
11454
|
-
|
|
11832
|
+
// A busy SDK dispatch is only queued while the agent loop is live.
|
|
11833
|
+
// Abort unwind is not a live loop: queuedAtDispatch from that window
|
|
11834
|
+
// must not divert a fresh prompt into the dying turn's steer queue.
|
|
11835
|
+
// Keep the original busy-dispatch bit for promotion/ownership: after
|
|
11836
|
+
// unwind the successor starts its own turn and must fire onQueuedPromoted
|
|
11837
|
+
// so SDK pending correlation is not dropped.
|
|
11838
|
+
const dispatchedWhileBusy = options?.queuedAtDispatch === true && options.deliverAs === undefined;
|
|
11839
|
+
const queuedPlainPrompt = dispatchedWhileBusy;
|
|
11455
11840
|
const hasFollowUpAhead = (): boolean =>
|
|
11456
11841
|
this.#activeFollowUpReservationEpochs.size > 0 ||
|
|
11457
11842
|
this.agent.snapshotFollowUp().length > 0 ||
|
|
@@ -11462,6 +11847,7 @@ export class AgentSession {
|
|
|
11462
11847
|
!followUpAheadAtReservation &&
|
|
11463
11848
|
!this.agent.state.isStreaming &&
|
|
11464
11849
|
!this.#canAutoContinueForSteer();
|
|
11850
|
+
const promoteAfterAbortUnwind = waitedForAbortUnwind && dispatchedWhileBusy && !followUpAheadAtReservation;
|
|
11465
11851
|
const deliverAs =
|
|
11466
11852
|
options?.deliverAs ??
|
|
11467
11853
|
(queuedPlainPrompt
|
|
@@ -11539,7 +11925,9 @@ export class AgentSession {
|
|
|
11539
11925
|
// Compaction is intentionally NOT diverted here: prompt() handles an
|
|
11540
11926
|
// in-flight compaction internally, and #queueSteer would otherwise park
|
|
11541
11927
|
// the message in the steering queue with no turn to consume it.
|
|
11542
|
-
|
|
11928
|
+
// Abort unwind is awaited before classification so a successor is not
|
|
11929
|
+
// parked in the dying turn's steer queue.
|
|
11930
|
+
if (this.#isLiveTurnBusy() && !waitedForAbortUnwind) {
|
|
11543
11931
|
if (options?.onPreflightAcceptCommit) await options.onPreflightAcceptCommit();
|
|
11544
11932
|
assertPreflightStillOpen();
|
|
11545
11933
|
await this.#queueSteer(text, images, {
|
|
@@ -11554,7 +11942,7 @@ export class AgentSession {
|
|
|
11554
11942
|
// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion
|
|
11555
11943
|
let queuedPromotionFired = false;
|
|
11556
11944
|
const fireQueuedPromotion = () => {
|
|
11557
|
-
if (!freshAtReservation || queuedPromotionFired) return;
|
|
11945
|
+
if ((!freshAtReservation && !promoteAfterAbortUnwind) || queuedPromotionFired) return;
|
|
11558
11946
|
queuedPromotionFired = true;
|
|
11559
11947
|
options?.onQueuedPromoted?.();
|
|
11560
11948
|
};
|
|
@@ -11566,7 +11954,7 @@ export class AgentSession {
|
|
|
11566
11954
|
fireQueuedPromotion();
|
|
11567
11955
|
},
|
|
11568
11956
|
onPreflightAcceptCommit:
|
|
11569
|
-
options?.onPreflightAcceptCommit || freshAtReservation
|
|
11957
|
+
options?.onPreflightAcceptCommit || freshAtReservation || promoteAfterAbortUnwind
|
|
11570
11958
|
? async () => {
|
|
11571
11959
|
if (options?.onPreflightAcceptCommit) await options.onPreflightAcceptCommit();
|
|
11572
11960
|
else options?.onPreflightAccepted?.();
|
|
@@ -11599,6 +11987,21 @@ export class AgentSession {
|
|
|
11599
11987
|
get queuedMessageCount(): number {
|
|
11600
11988
|
return this.#steeringMessages.length + this.#followUpMessages.length + this.#pendingNextTurnMessages.length;
|
|
11601
11989
|
}
|
|
11990
|
+
/**
|
|
11991
|
+
* Number of pending messages a user-facing drain can actually deliver back:
|
|
11992
|
+
* exactly the steering and follow-up queues that `clearQueue()`,
|
|
11993
|
+
* `popLastQueuedMessage()`, and `getQueuedMessageEntries()` operate on.
|
|
11994
|
+
*
|
|
11995
|
+
* Hidden next-turn context is deliberately excluded. Those entries are
|
|
11996
|
+
* authored by the agent (e.g. a `todo_write` failure reminder queued with
|
|
11997
|
+
* `deliverAs: "nextTurn"` and no `triggerTurn`), are never returned by the
|
|
11998
|
+
* drain handlers, and deliberately survive turn completion — so a UI gate
|
|
11999
|
+
* that counted them would report permanently pending work that no key press
|
|
12000
|
+
* can clear, locking the user out of their own input (#4741).
|
|
12001
|
+
*/
|
|
12002
|
+
get drainableQueuedMessageCount(): number {
|
|
12003
|
+
return this.#steeringMessages.length + this.#followUpMessages.length;
|
|
12004
|
+
}
|
|
11602
12005
|
/** Typed pending-message counts per queue (steering, follow-up, next-turn). */
|
|
11603
12006
|
get pendingMessageCounts(): { steering: number; followUp: number; nextTurn: number } {
|
|
11604
12007
|
return {
|
|
@@ -11728,6 +12131,27 @@ export class AgentSession {
|
|
|
11728
12131
|
return this.#skills;
|
|
11729
12132
|
}
|
|
11730
12133
|
|
|
12134
|
+
/**
|
|
12135
|
+
* Install the skill set discovered at a newly rescoped cwd (`move_session`).
|
|
12136
|
+
* Project-scoped skills belong to the directory they were discovered in, so
|
|
12137
|
+
* they must not survive a move out of it.
|
|
12138
|
+
*/
|
|
12139
|
+
async replaceSkills(skills: Skill[]): Promise<void> {
|
|
12140
|
+
this.#skills = skills;
|
|
12141
|
+
await this.refreshBaseSystemPrompt();
|
|
12142
|
+
}
|
|
12143
|
+
|
|
12144
|
+
/**
|
|
12145
|
+
* Retire the cached workspace tree after a rescope so the next turn re-scans
|
|
12146
|
+
* at the new cwd instead of re-presenting the abandoned launcher root.
|
|
12147
|
+
*/
|
|
12148
|
+
retireWorkspaceTreeForRescope(): void {
|
|
12149
|
+
this.#initialWorkspaceTree = undefined;
|
|
12150
|
+
this.#cachedWorkspaceTree = undefined;
|
|
12151
|
+
this.#cachedWorkspaceTreeAt = 0;
|
|
12152
|
+
this.#pendingWorkspaceTreeRescope = true;
|
|
12153
|
+
}
|
|
12154
|
+
|
|
11731
12155
|
/** Skill loading warnings captured by SDK */
|
|
11732
12156
|
get skillWarnings(): readonly SkillWarning[] {
|
|
11733
12157
|
return this.#skillWarnings;
|
|
@@ -11751,13 +12175,16 @@ export class AgentSession {
|
|
|
11751
12175
|
this.setTodoPhases(phases.filter(p => p.tasks.length > 0));
|
|
11752
12176
|
}
|
|
11753
12177
|
|
|
11754
|
-
async #compactionStateSnapshot(
|
|
12178
|
+
async #compactionStateSnapshot(
|
|
12179
|
+
options: { trackWorkflowRecoveryProgress?: boolean } = {},
|
|
12180
|
+
): Promise<CompactionStateSnapshot> {
|
|
11755
12181
|
const snapshot: CompactionStateSnapshot = {
|
|
11756
12182
|
goal: undefined,
|
|
11757
12183
|
openTodos: [],
|
|
11758
12184
|
activeSkills: [],
|
|
11759
12185
|
queuedMessages: false,
|
|
11760
12186
|
lastAssistantStopReason: undefined,
|
|
12187
|
+
recentFileMutations: [],
|
|
11761
12188
|
};
|
|
11762
12189
|
try {
|
|
11763
12190
|
const goalState = this.getGoalModeState();
|
|
@@ -11780,6 +12207,9 @@ export class AgentSession {
|
|
|
11780
12207
|
error: error instanceof Error ? error.message : String(error),
|
|
11781
12208
|
});
|
|
11782
12209
|
}
|
|
12210
|
+
// #4560: clear before the read so a failed refresh can never leave a
|
|
12211
|
+
// previous run's workflow owner in place for the recovery projector.
|
|
12212
|
+
this.#lastCompactionActiveSkills = [];
|
|
11783
12213
|
try {
|
|
11784
12214
|
const state = await readVisibleSkillActiveState(this.sessionManager.getCwd(), this.sessionId, {
|
|
11785
12215
|
bypassCache: true,
|
|
@@ -11788,11 +12218,40 @@ export class AgentSession {
|
|
|
11788
12218
|
.filter(entry => entry.active !== false)
|
|
11789
12219
|
.slice(0, 5)
|
|
11790
12220
|
.map(entry => ({ skill: entry.skill, phase: entry.phase ?? "unknown" }));
|
|
12221
|
+
this.#lastCompactionActiveSkills = snapshot.activeSkills;
|
|
11791
12222
|
} catch (error) {
|
|
11792
12223
|
logger.warn("Failed to read workflow state for compaction snapshot", {
|
|
11793
12224
|
error: error instanceof Error ? error.message : String(error),
|
|
11794
12225
|
});
|
|
11795
12226
|
}
|
|
12227
|
+
try {
|
|
12228
|
+
// #4560: reload the durable workflow contract for recognized
|
|
12229
|
+
// Ralplan/Ultragoal runs so compaction carries a structured
|
|
12230
|
+
// recovery projection instead of summary prose alone. Degrades
|
|
12231
|
+
// safely: malformed/stale/tampered durable state leaves the
|
|
12232
|
+
snapshot.workflowRecovery =
|
|
12233
|
+
snapshot.goal?.status === "paused" ? undefined : await this.#projectWorkflowRecovery();
|
|
12234
|
+
if (snapshot.workflowRecovery && options.trackWorkflowRecoveryProgress) {
|
|
12235
|
+
this.#workflowRecoveryMemory = trackWorkflowRecoveryZeroProgress(
|
|
12236
|
+
this.#workflowRecoveryMemory,
|
|
12237
|
+
snapshot.workflowRecovery,
|
|
12238
|
+
);
|
|
12239
|
+
}
|
|
12240
|
+
if (snapshot.workflowRecovery && this.#workflowRecoveryMemory) {
|
|
12241
|
+
snapshot.workflowRecovery = {
|
|
12242
|
+
...snapshot.workflowRecovery,
|
|
12243
|
+
zeroProgress: {
|
|
12244
|
+
...snapshot.workflowRecovery.zeroProgress,
|
|
12245
|
+
unchangedObservations: this.#workflowRecoveryMemory.unchangedObservations,
|
|
12246
|
+
stalled: isWorkflowRecoveryStalled(this.#workflowRecoveryMemory),
|
|
12247
|
+
},
|
|
12248
|
+
};
|
|
12249
|
+
}
|
|
12250
|
+
} catch (error) {
|
|
12251
|
+
logger.warn("Failed to project workflow recovery state for compaction snapshot", {
|
|
12252
|
+
error: error instanceof Error ? error.message : String(error),
|
|
12253
|
+
});
|
|
12254
|
+
}
|
|
11796
12255
|
try {
|
|
11797
12256
|
snapshot.queuedMessages = this.agent.hasQueuedMessages() || this.#pendingNextTurnMessages.length > 0;
|
|
11798
12257
|
} catch (error) {
|
|
@@ -11812,9 +12271,43 @@ export class AgentSession {
|
|
|
11812
12271
|
error: error instanceof Error ? error.message : String(error),
|
|
11813
12272
|
});
|
|
11814
12273
|
}
|
|
12274
|
+
try {
|
|
12275
|
+
snapshot.recentFileMutations = collectRecentFileMutations(this.messages);
|
|
12276
|
+
} catch (error) {
|
|
12277
|
+
logger.warn("Failed to read recent file mutations for compaction snapshot", {
|
|
12278
|
+
error: error instanceof Error ? error.message : String(error),
|
|
12279
|
+
});
|
|
12280
|
+
}
|
|
11815
12281
|
return snapshot;
|
|
11816
12282
|
}
|
|
11817
12283
|
|
|
12284
|
+
/**
|
|
12285
|
+
* #4560: derive the structured workflow recovery projection for an
|
|
12286
|
+
* active recognized workflow from its canonical durable state. Returns
|
|
12287
|
+
* undefined for inactive/unrecognized workflows (generic behavior is
|
|
12288
|
+
* preserved) and for malformed durable state (safe degradation).
|
|
12289
|
+
*/
|
|
12290
|
+
async #projectWorkflowRecovery(): Promise<WorkflowRecoveryProjection | undefined> {
|
|
12291
|
+
const entries = (this.#lastCompactionActiveSkills ?? []).filter(
|
|
12292
|
+
entry => !isWorkflowContinuationInert(entry.skill, entry.phase),
|
|
12293
|
+
);
|
|
12294
|
+
const cwd = this.sessionManager.getCwd();
|
|
12295
|
+
// Ultragoal runs own the live execution contract; prefer their plan.
|
|
12296
|
+
if (entries.some(entry => entry.skill === "ultragoal")) {
|
|
12297
|
+
const projection = await projectUltragoalRun({ cwd, sessionId: this.sessionId }).catch(() => undefined);
|
|
12298
|
+
if (projection) return projection;
|
|
12299
|
+
}
|
|
12300
|
+
if (entries.some(entry => entry.skill === "ralplan")) {
|
|
12301
|
+
const projection = await projectLatestRalplanRun({ cwd, sessionId: this.sessionId }).catch(() => undefined);
|
|
12302
|
+
if (projection) return projection;
|
|
12303
|
+
}
|
|
12304
|
+
return undefined;
|
|
12305
|
+
}
|
|
12306
|
+
/** #4560: zero-progress memory across compaction observations. */
|
|
12307
|
+
#workflowRecoveryMemory: WorkflowRecoveryZeroProgressMemory | undefined;
|
|
12308
|
+
/** #4560: active skills observed by the latest compaction snapshot. */
|
|
12309
|
+
#lastCompactionActiveSkills: Array<{ skill: string; phase: string }> = [];
|
|
12310
|
+
|
|
11818
12311
|
#compactionStateContext(snapshot: CompactionStateSnapshot): string[] {
|
|
11819
12312
|
const context: string[] = [];
|
|
11820
12313
|
const goal = snapshot.goal;
|
|
@@ -11834,6 +12327,12 @@ export class AgentSession {
|
|
|
11834
12327
|
const todos = snapshot.openTodos.map(todo => sanitizeCompactionStateText(todo, 120));
|
|
11835
12328
|
context.push(`Open todos: ${todos.join("; ")}`);
|
|
11836
12329
|
}
|
|
12330
|
+
if (snapshot.recentFileMutations.length > 0) {
|
|
12331
|
+
const files = snapshot.recentFileMutations.map(filePath => sanitizeCompactionStateText(filePath, 120));
|
|
12332
|
+
context.push(`Recent file mutations: ${files.join("; ")}`);
|
|
12333
|
+
}
|
|
12334
|
+
const recovery = snapshot.workflowRecovery;
|
|
12335
|
+
if (recovery) context.push(...renderWorkflowRecoveryContext(recovery));
|
|
11837
12336
|
return context;
|
|
11838
12337
|
}
|
|
11839
12338
|
|
|
@@ -11957,6 +12456,7 @@ export class AgentSession {
|
|
|
11957
12456
|
this.#markRetryReplayUnsafe();
|
|
11958
12457
|
this.abortRetry();
|
|
11959
12458
|
this.#promptGeneration++;
|
|
12459
|
+
this.#abortEpoch++;
|
|
11960
12460
|
this.#promptPreflightCancellationGeneration++;
|
|
11961
12461
|
this.#promptPreflightAbortController.abort();
|
|
11962
12462
|
this.#promptPreflightAbortController = new AbortController();
|
|
@@ -11980,66 +12480,94 @@ export class AgentSession {
|
|
|
11980
12480
|
| "internal";
|
|
11981
12481
|
silent?: boolean;
|
|
11982
12482
|
}): Promise<AbortOutcome> {
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
|
|
11987
|
-
this
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12483
|
+
// Advance the admission epoch SYNCHRONOUSLY, before any await and before the
|
|
12484
|
+
// shared-unwind branch. A second abort that piggybacks on the first unwind
|
|
12485
|
+
// still creates a cancellation fence, so a prompt admitted between the two
|
|
12486
|
+
// aborts is refused rather than started after the shared unwind resolves.
|
|
12487
|
+
this.#abortAdmissionEpoch++;
|
|
12488
|
+
if (this.#abortUnwind) {
|
|
12489
|
+
// The shared unwind already ran `#abortOptions` for the FIRST abort, so this
|
|
12490
|
+
// abort must still apply the effects that are specific to its own request.
|
|
12491
|
+
//
|
|
12492
|
+
// Cancellation fence: a prompt admitted before this abort keeps a live
|
|
12493
|
+
// generation and preflight signal otherwise, and would start after the
|
|
12494
|
+
// shared unwind resolves — work the user has already aborted twice.
|
|
12495
|
+
this.#promptPreflightCancellationGeneration++;
|
|
12496
|
+
this.#promptPreflightAbortController.abort();
|
|
12497
|
+
this.#promptPreflightAbortController = new AbortController();
|
|
12498
|
+
// Abort visibility is per-request: a later real abort must not inherit an
|
|
12499
|
+
// earlier silent abort's suppression and swallow the user-visible notice.
|
|
12500
|
+
if (options?.silent !== true) this.#silentAbortPending = false;
|
|
12501
|
+
await this.#abortUnwind;
|
|
12502
|
+
return { kind: "settled" };
|
|
12503
|
+
}
|
|
12504
|
+
const unwind = Promise.withResolvers<void>();
|
|
12505
|
+
this.#abortUnwind = unwind.promise;
|
|
12506
|
+
try {
|
|
12507
|
+
this.#abortOptions(options);
|
|
12508
|
+
const postPromptDrain = this.#cancelPostPromptTasks();
|
|
12509
|
+
const managedLogicalRunId =
|
|
12510
|
+
this.#defaultFallbackChain().chain.entries.length > 1 ? this.agent.currentManagedLogicalRunId : undefined;
|
|
12511
|
+
this.agent.abort();
|
|
12512
|
+
const cleanup = Promise.all([postPromptDrain, this.agent.waitForIdle()]).then(
|
|
12513
|
+
() => ({ kind: "settled" as const }),
|
|
12514
|
+
(cause: unknown) => ({ kind: "error" as const, cause }),
|
|
12515
|
+
);
|
|
12516
|
+
cleanup.catch(() => {});
|
|
12517
|
+
let outcome: AbortOutcome;
|
|
12518
|
+
if (options?.timeoutMs !== undefined && options.timeoutMs > 0) {
|
|
12519
|
+
outcome = await Promise.race([
|
|
12520
|
+
cleanup,
|
|
12521
|
+
Bun.sleep(options.timeoutMs).then(() => ({ kind: "timeout" as const })),
|
|
12522
|
+
]);
|
|
12523
|
+
if (outcome.kind === "timeout") {
|
|
12524
|
+
this.#abandonPostPromptTasks();
|
|
12525
|
+
const forceAbortLogicalRunId = this.agent.currentManagedLogicalRunId ?? this.#activeLogicalRunId;
|
|
12526
|
+
try {
|
|
12527
|
+
if (forceAbortLogicalRunId !== undefined)
|
|
12528
|
+
this.agent.forceAbort("Abort cleanup timed out", forceAbortLogicalRunId);
|
|
12529
|
+
else this.agent.forceAbort("Abort cleanup timed out");
|
|
12530
|
+
} catch {
|
|
12531
|
+
this.agent.forceAbort("Abort cleanup timed out");
|
|
12532
|
+
}
|
|
12533
|
+
this.emitNotice(
|
|
12534
|
+
"warning",
|
|
12535
|
+
"Abort cleanup timed out; forced session recovery. The previous provider stream or tool may still be unwinding in the background.",
|
|
12536
|
+
"abort",
|
|
12537
|
+
);
|
|
12008
12538
|
}
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
"Abort cleanup timed out; forced session recovery. The previous provider stream or tool may still be unwinding in the background.",
|
|
12012
|
-
"abort",
|
|
12013
|
-
);
|
|
12539
|
+
} else {
|
|
12540
|
+
outcome = await cleanup;
|
|
12014
12541
|
}
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12542
|
+
try {
|
|
12543
|
+
await this.#goalRuntime.onTaskAborted({ reason: options?.goalReason ?? "interrupted" });
|
|
12544
|
+
if (managedLogicalRunId !== undefined)
|
|
12545
|
+
this.agent.requestRunTerminal(managedLogicalRunId, { stopReason: "cancelled" });
|
|
12546
|
+
this.#flushPendingBackgroundExchanges();
|
|
12547
|
+
await this.#awaitAbortedTurnTerminal();
|
|
12548
|
+
if (
|
|
12549
|
+
!this.#cancelAndSubmitInProgress &&
|
|
12550
|
+
(options?.cause ?? "internal") === "user_interrupt" &&
|
|
12551
|
+
this.agent.hasQueuedSteering()
|
|
12552
|
+
) {
|
|
12553
|
+
this.#scheduleAgentContinue({
|
|
12554
|
+
delayMs: 1,
|
|
12555
|
+
generation: this.#promptGeneration,
|
|
12556
|
+
shouldContinue: () => this.agent.hasQueuedSteering(),
|
|
12557
|
+
rescheduleOnBusy: true,
|
|
12558
|
+
continueQueuedOnly: true,
|
|
12559
|
+
});
|
|
12560
|
+
}
|
|
12561
|
+
return outcome;
|
|
12562
|
+
} catch (cause) {
|
|
12563
|
+
return { kind: "error", cause };
|
|
12564
|
+
} finally {
|
|
12565
|
+
this.#silentAbortPending = false;
|
|
12566
|
+
if (this.#toolChoiceQueue.hasInFlight) this.#toolChoiceQueue.reject("aborted");
|
|
12036
12567
|
}
|
|
12037
|
-
return outcome;
|
|
12038
|
-
} catch (cause) {
|
|
12039
|
-
return { kind: "error", cause };
|
|
12040
12568
|
} finally {
|
|
12041
|
-
this.#
|
|
12042
|
-
|
|
12569
|
+
this.#abortUnwind = undefined;
|
|
12570
|
+
unwind.resolve();
|
|
12043
12571
|
}
|
|
12044
12572
|
}
|
|
12045
12573
|
|
|
@@ -14952,7 +15480,7 @@ export class AgentSession {
|
|
|
14952
15480
|
const compactionAbortController = new AbortController();
|
|
14953
15481
|
this.#compactionAbortController = compactionAbortController;
|
|
14954
15482
|
// Take this invocation's state snapshot for the summarizer context.
|
|
14955
|
-
const compactionStateSnapshot = await this.#compactionStateSnapshot();
|
|
15483
|
+
const compactionStateSnapshot = await this.#compactionStateSnapshot({ trackWorkflowRecoveryProgress: true });
|
|
14956
15484
|
|
|
14957
15485
|
try {
|
|
14958
15486
|
if (!this.model) {
|
|
@@ -15061,7 +15589,6 @@ export class AgentSession {
|
|
|
15061
15589
|
if (compactionAbortController.signal.aborted) {
|
|
15062
15590
|
throw new CompactionCancelledError();
|
|
15063
15591
|
}
|
|
15064
|
-
|
|
15065
15592
|
const compactionEntryId = this.sessionManager.appendCompaction(
|
|
15066
15593
|
summary,
|
|
15067
15594
|
shortSummary,
|
|
@@ -17007,7 +17534,7 @@ export class AgentSession {
|
|
|
17007
17534
|
if (autoCompactionSignal.aborted) return { kind: "aborted", source: "signal" };
|
|
17008
17535
|
await this.#emitSessionEvent({ type: "auto_compaction_start", reason, action });
|
|
17009
17536
|
if (autoCompactionSignal.aborted) return await emitAborted();
|
|
17010
|
-
const compactionStateSnapshot = await this.#compactionStateSnapshot();
|
|
17537
|
+
const compactionStateSnapshot = await this.#compactionStateSnapshot({ trackWorkflowRecoveryProgress: true });
|
|
17011
17538
|
if (autoCompactionSignal.aborted || this.#isDisposed || this.#promptGeneration !== generation) {
|
|
17012
17539
|
return await emitAborted();
|
|
17013
17540
|
}
|
|
@@ -17555,6 +18082,10 @@ export class AgentSession {
|
|
|
17555
18082
|
if (this.#isTerminalProviderFirstEventTimeout(message)) return false;
|
|
17556
18083
|
if (message.errorMessage?.startsWith("Model fallback chain exhausted;")) return false;
|
|
17557
18084
|
const transportFailure = message.transportFailure;
|
|
18085
|
+
// Transport facts normally bypass typed classification in managed fallback.
|
|
18086
|
+
// A provider safety stop must still remain terminal on that path.
|
|
18087
|
+
if (message.stopReason === "error" && message.errorKind === "provider_safety_stop" && transportFailure)
|
|
18088
|
+
return false;
|
|
17558
18089
|
const contextWindow = this.model?.contextWindow ?? 0;
|
|
17559
18090
|
if (classifyContextOverflow(message, transportFailure, contextWindow)) return false;
|
|
17560
18091
|
const managedFallback = this.#defaultFallbackChain().chain.entries.length > 1;
|
|
@@ -17796,6 +18327,15 @@ export class AgentSession {
|
|
|
17796
18327
|
return undefined;
|
|
17797
18328
|
}
|
|
17798
18329
|
|
|
18330
|
+
#escapedNonAsciiRecoveryMessage(): UserMessage {
|
|
18331
|
+
return {
|
|
18332
|
+
role: "user",
|
|
18333
|
+
content: ESCAPED_NONASCII_RECOVERY_PROMPT,
|
|
18334
|
+
synthetic: true,
|
|
18335
|
+
timestamp: Date.now(),
|
|
18336
|
+
};
|
|
18337
|
+
}
|
|
18338
|
+
|
|
17799
18339
|
#managedFallbackPromptOptions(): {
|
|
17800
18340
|
fallbackManaged?: boolean;
|
|
17801
18341
|
nextFallbackAttempt?: (model: Model) => FallbackAttemptToken;
|
|
@@ -17821,6 +18361,9 @@ export class AgentSession {
|
|
|
17821
18361
|
}
|
|
17822
18362
|
|
|
17823
18363
|
async #resetDefaultFallbackForNewTurn(): Promise<void> {
|
|
18364
|
+
// A fresh user turn gets a fresh escaped-non-ASCII retry budget: the
|
|
18365
|
+
// defect is per-turn wire luck, not a sticky model property.
|
|
18366
|
+
this.#escapedNonAsciiManagedRetries = 0;
|
|
17824
18367
|
const controller = this.#defaultFallbackChain();
|
|
17825
18368
|
if (this.#defaultFallbackExhaustedLastTurn) {
|
|
17826
18369
|
this.#defaultFallbackExhaustedLastTurn = false;
|
|
@@ -17924,15 +18467,35 @@ export class AgentSession {
|
|
|
17924
18467
|
// evidence: never charge the attempt, advance the chain, or suppress the
|
|
17925
18468
|
// selector. The loop already removed the defective turn from history and
|
|
17926
18469
|
// bounded its own resample budget, so this decision just re-issues the
|
|
17927
|
-
// same request on the same model.
|
|
17928
|
-
//
|
|
17929
|
-
//
|
|
18470
|
+
// same request on the same model. The re-issue carries the transient
|
|
18471
|
+
// steering instruction exactly once (when the discarded attempt did not
|
|
18472
|
+
// already have one), so a deterministic escaper has a reason to change
|
|
18473
|
+
// its spelling; the instruction never lands in durable history.
|
|
18474
|
+
//
|
|
18475
|
+
// The retries are un-charged by design, so a deterministic escaper
|
|
18476
|
+
// would otherwise loop forever: each continuation is a fresh loop with
|
|
18477
|
+
// a fresh in-loop resample budget, and the fallback chain never sees a
|
|
18478
|
+
// charge to exhaust on. Bound them per logical run and fail closed to
|
|
18479
|
+
// the terminal exhaustion message once the budget is spent — the same
|
|
18480
|
+
// fail-closed answer the unmanaged path gives via the per-call
|
|
18481
|
+
// rejection. New user turns reset the budget in #resetDefaultFallbackForNewTurn.
|
|
18482
|
+
this.#escapedNonAsciiManagedRetries += 1;
|
|
18483
|
+
if (this.#escapedNonAsciiManagedRetries > MAX_ESCAPED_NONASCII_MANAGED_RETRIES) {
|
|
18484
|
+
return this.#managedFallbackExhaustionDecision(
|
|
18485
|
+
outcome.message,
|
|
18486
|
+
`Managed fallback retried the escaped non-ASCII tool-call turn ${MAX_ESCAPED_NONASCII_MANAGED_RETRIES} times without a literal-UTF-8 re-issue; giving up so the run fails closed instead of looping.`,
|
|
18487
|
+
);
|
|
18488
|
+
}
|
|
17930
18489
|
this.#defaultFallbackChain().discardStartedAttempt();
|
|
18490
|
+
const steering = outcome.steeringPending === true;
|
|
17931
18491
|
return {
|
|
17932
18492
|
type: "retry",
|
|
17933
18493
|
continuation: async ownership => {
|
|
17934
18494
|
if (!ownership.isCurrent() || ownership.lease.signal.aborted) return;
|
|
17935
|
-
await this.agent.continue(
|
|
18495
|
+
await this.agent.continue({
|
|
18496
|
+
...this.#managedFallbackPromptOptions(),
|
|
18497
|
+
...(steering ? { transientRecoveryMessage: this.#escapedNonAsciiRecoveryMessage() } : {}),
|
|
18498
|
+
});
|
|
17936
18499
|
},
|
|
17937
18500
|
};
|
|
17938
18501
|
}
|
|
@@ -18027,6 +18590,7 @@ export class AgentSession {
|
|
|
18027
18590
|
// result is returned verbatim below. Narrowing it away here would silently
|
|
18028
18591
|
// disable the terminal-403 guard in `#markFailedCredential`.
|
|
18029
18592
|
): { class: FallbackTriggerClass; retryAfterMs?: number; authDisposition?: AuthDisposition } | undefined {
|
|
18593
|
+
if (message.stopReason === "error" && message.errorKind === "provider_safety_stop") return undefined;
|
|
18030
18594
|
if (classifyContextOverflow(message, transportFailure, this.model?.contextWindow ?? 0)) return undefined;
|
|
18031
18595
|
const transport = classifyFallbackTrigger(transportFailure ?? { status: message.errorStatus });
|
|
18032
18596
|
if (transport.class !== "other") return transport;
|
|
@@ -18247,6 +18811,18 @@ export class AgentSession {
|
|
|
18247
18811
|
}
|
|
18248
18812
|
: false;
|
|
18249
18813
|
}
|
|
18814
|
+
// A typed provider safety stop is terminal even when managed fallback is
|
|
18815
|
+
// enabled. Preserve the original refusal instead of treating the absent
|
|
18816
|
+
// fallback trigger as chain exhaustion; do not broaden this to every
|
|
18817
|
+
// untyped terminal classification.
|
|
18818
|
+
if (message.stopReason === "error" && message.errorKind === "provider_safety_stop") {
|
|
18819
|
+
return managedOutcome
|
|
18820
|
+
? {
|
|
18821
|
+
type: "terminal",
|
|
18822
|
+
terminal: { stopReason: "error", messages: [message] },
|
|
18823
|
+
}
|
|
18824
|
+
: false;
|
|
18825
|
+
}
|
|
18250
18826
|
// retry.enabled=false always surfaces immediately, matching the explicit
|
|
18251
18827
|
// user opt-out. The managed provider-fallback chain keeps its own
|
|
18252
18828
|
// availability policy.
|
|
@@ -18260,6 +18836,7 @@ export class AgentSession {
|
|
|
18260
18836
|
}
|
|
18261
18837
|
const firstEventTimeout = classification === "first_event_timeout";
|
|
18262
18838
|
const emptyResponse = classification === "empty_response";
|
|
18839
|
+
const canReplayProviderOverload = isBareDefaultCodexOverload(message) || isBareDefaultAnthropicOverload(message);
|
|
18263
18840
|
const reportedRetryMaxAttempts = transportFailure?.retryMaxAttempts;
|
|
18264
18841
|
if (reportedRetryMaxAttempts !== undefined) {
|
|
18265
18842
|
this.#providerRetryMaxAttempts = Math.min(
|
|
@@ -18338,24 +18915,22 @@ export class AgentSession {
|
|
|
18338
18915
|
if (!managedFallback && assistantMessageHasVisibleOrToolContent(message)) {
|
|
18339
18916
|
return false;
|
|
18340
18917
|
}
|
|
18341
|
-
// Bare defaults retain their narrow watchdog and
|
|
18342
|
-
// first-event timeout adds the typed, content-free,
|
|
18343
|
-
// requirement above; other transient watchdogs preserve
|
|
18918
|
+
// Bare defaults retain their narrow watchdog and provider capacity-overload
|
|
18919
|
+
// admissions. A first-event timeout adds the typed, content-free,
|
|
18920
|
+
// current-clean-scope requirement above; other transient watchdogs preserve
|
|
18921
|
+
// legacy behavior. A provider overload is admitted only from that provider's
|
|
18922
|
+
// own typed overload code on a content-free attempt carrying no conflicting
|
|
18923
|
+
// transport facts, so replaying it cannot duplicate observable work.
|
|
18344
18924
|
const canReplayEmptyResponse = emptyResponse && (this.#retryAttempt === 0 || this.#hasCleanRetryReplaySafety);
|
|
18345
18925
|
if (!managedFallback && !legacyRetryConfigured && !canReplayRotatedCredential && !canReplayEmptyResponse) {
|
|
18346
|
-
const bareDefaultCodexOverload = isBareDefaultCodexOverload(message);
|
|
18347
|
-
const canReplayCodexOverload = bareDefaultCodexOverload;
|
|
18348
18926
|
if (
|
|
18349
|
-
(!
|
|
18927
|
+
(!canReplayProviderOverload &&
|
|
18350
18928
|
!this.#isTypedFirstEventTimeout(message) &&
|
|
18351
18929
|
!messageOnlyWatchdogTimeout &&
|
|
18352
18930
|
(hasBareDefaultRetryDisqualifyingFacts(message) ||
|
|
18353
18931
|
(classification !== "transient" && classification !== "first_event_timeout") ||
|
|
18354
18932
|
!BARE_DEFAULT_WATCHDOG_ERROR.test(message.errorMessage ?? ""))) ||
|
|
18355
|
-
(!
|
|
18356
|
-
!firstEventTimeout &&
|
|
18357
|
-
!messageOnlyWatchdogTimeout &&
|
|
18358
|
-
!this.#hasCleanRetryReplaySafety)
|
|
18933
|
+
(!firstEventTimeout && !messageOnlyWatchdogTimeout && !this.#hasCleanRetryReplaySafety)
|
|
18359
18934
|
) {
|
|
18360
18935
|
return false;
|
|
18361
18936
|
}
|
|
@@ -18363,6 +18938,7 @@ export class AgentSession {
|
|
|
18363
18938
|
const legacyUnbounded =
|
|
18364
18939
|
!managedFallback &&
|
|
18365
18940
|
classification === "transient" &&
|
|
18941
|
+
!canReplayProviderOverload &&
|
|
18366
18942
|
!this.#isIdleStreamStallErrorMessage(message.errorMessage ?? "");
|
|
18367
18943
|
|
|
18368
18944
|
const failedSelector = managedFallback ? controller.currentSelector() : undefined;
|