@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
package/src/task/executor.ts
CHANGED
|
@@ -23,10 +23,16 @@ import {
|
|
|
23
23
|
modelSupportsServiceTier,
|
|
24
24
|
type ServiceTier,
|
|
25
25
|
} from "@gajae-code/ai/core";
|
|
26
|
+
import {
|
|
27
|
+
classifyFallbackTrigger,
|
|
28
|
+
type TransportFailureFacts,
|
|
29
|
+
transportFailureFacts,
|
|
30
|
+
} from "@gajae-code/ai/utils/fallback-transport";
|
|
26
31
|
import { type JsonSchemaValidationIssue, validateJsonSchemaValue } from "@gajae-code/ai/utils/schema";
|
|
27
32
|
import * as canonicalSdk from "@gajae-code/coding-agent/sdk";
|
|
28
33
|
import { logger, prompt, untilAborted } from "@gajae-code/utils";
|
|
29
34
|
import { AsyncJobManager } from "../async";
|
|
35
|
+
import { AUTOROUTING_SELECTOR_MAX_LENGTH, type AutoroutingReasonCode } from "../config/autorouting-contract";
|
|
30
36
|
import { ModelRegistry } from "../config/model-registry";
|
|
31
37
|
import { formatModelString, resolveModelOverrideWithAuthFallback } from "../config/model-resolver";
|
|
32
38
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
@@ -45,7 +51,7 @@ import submitReminderTemplate from "../prompts/system/subagent-yield-reminder.md
|
|
|
45
51
|
import { AgentRegistry } from "../registry/agent-registry";
|
|
46
52
|
import { discoverAuthStorage } from "../sdk";
|
|
47
53
|
import type { AgentSession, AgentSessionEvent, ForkContextSeed } from "../session/agent-session";
|
|
48
|
-
import
|
|
54
|
+
import { ArtifactManager } from "../session/artifacts";
|
|
49
55
|
import type { AuthStorage } from "../session/auth-storage";
|
|
50
56
|
import { SKILL_PROMPT_MESSAGE_TYPE } from "../session/messages";
|
|
51
57
|
import { SessionManager, type SessionMemoryMode } from "../session/session-manager";
|
|
@@ -67,8 +73,15 @@ import { persistTaskTokenLog, taskTokenLogFromUsage } from "./token-log";
|
|
|
67
73
|
import {
|
|
68
74
|
type AgentDefinition,
|
|
69
75
|
type AgentProgress,
|
|
76
|
+
type AutoroutingAttempt,
|
|
77
|
+
type AutoroutingAttemptCode,
|
|
78
|
+
type AutoroutingPreflightFailure,
|
|
79
|
+
assertRoutingEvidenceInvariant,
|
|
80
|
+
createLocalErrorSummary,
|
|
70
81
|
createSetupFailureSummary,
|
|
71
82
|
hasCompleteUsageCostBreakdown,
|
|
83
|
+
isAssistantLocalErrorKind,
|
|
84
|
+
type LocalErrorSummary,
|
|
72
85
|
MAX_OUTPUT_BYTES,
|
|
73
86
|
MAX_OUTPUT_LINES,
|
|
74
87
|
type ModelSubstitutionWarning,
|
|
@@ -78,8 +91,12 @@ import {
|
|
|
78
91
|
TASK_SUBAGENT_EVENT_CHANNEL,
|
|
79
92
|
TASK_SUBAGENT_LIFECYCLE_CHANNEL,
|
|
80
93
|
TASK_SUBAGENT_PROGRESS_CHANNEL,
|
|
94
|
+
type TaskRoutingEvidence,
|
|
81
95
|
type TaskToolDetails,
|
|
82
96
|
} from "./types";
|
|
97
|
+
|
|
98
|
+
export type { AutoroutingPreflightFailure } from "./types";
|
|
99
|
+
|
|
83
100
|
import { type ExecutorExecutionMode, resolveUltragoalRedTeamActivation } from "./ultragoal-redteam-activation";
|
|
84
101
|
|
|
85
102
|
/** Agent event types to forward for progress tracking. */
|
|
@@ -211,6 +228,17 @@ export interface ExecutorOptions {
|
|
|
211
228
|
executionMode?: ExecutorExecutionMode;
|
|
212
229
|
context?: string;
|
|
213
230
|
description?: string;
|
|
231
|
+
routing?: TaskRoutingEvidence;
|
|
232
|
+
/** Ordered, normalized autorouting candidates for the cross-phase preflight ledger. */
|
|
233
|
+
autoroutingCandidates?: string[];
|
|
234
|
+
autoroutingSkips?: Array<{ selector: string; code: AutoroutingReasonCode }>;
|
|
235
|
+
autoroutingPreflightErrors?: Map<string, unknown>;
|
|
236
|
+
autoroutingPreflight?: boolean;
|
|
237
|
+
autoroutingAttemptId?: string;
|
|
238
|
+
preflightProbe?: boolean;
|
|
239
|
+
preflightDurable?: boolean;
|
|
240
|
+
preflightFenceCallback?: () => void;
|
|
241
|
+
|
|
214
242
|
index: number;
|
|
215
243
|
id: string;
|
|
216
244
|
modelOverride?: string | string[];
|
|
@@ -326,6 +354,23 @@ export class ManagedTaskPersistence {
|
|
|
326
354
|
this.#artifacts.assertManagedBinding();
|
|
327
355
|
return session;
|
|
328
356
|
}
|
|
357
|
+
async openStagedSession(attemptId = this.#taskId): Promise<SessionManager> {
|
|
358
|
+
const store = this.#artifacts.getManagedStore();
|
|
359
|
+
if (!store) throw new Error("Managed task persistence authority is unavailable");
|
|
360
|
+
this.#artifacts.assertManagedBinding();
|
|
361
|
+
const sessionFile = path.join(this.#artifacts.dir, `${this.#taskId}.jsonl`);
|
|
362
|
+
const session = await SessionManager.stagedNestedManaged(
|
|
363
|
+
sessionFile,
|
|
364
|
+
SessionManager.nestedManagedDestination(store, this.#artifacts.dir),
|
|
365
|
+
store,
|
|
366
|
+
undefined,
|
|
367
|
+
attemptId,
|
|
368
|
+
);
|
|
369
|
+
const stagedArtifacts = this.#artifacts.createAttemptStaging(attemptId);
|
|
370
|
+
session.adoptArtifactManager(stagedArtifacts, this.#artifacts);
|
|
371
|
+
this.#artifacts.assertManagedBinding();
|
|
372
|
+
return session;
|
|
373
|
+
}
|
|
329
374
|
|
|
330
375
|
async publishOutput(rawOutput: string, metadata: Uint8Array): Promise<void> {
|
|
331
376
|
await withArtifactManagerFinalizationTurn(this.#artifacts, () =>
|
|
@@ -556,6 +601,7 @@ interface FinalizeSubprocessOutputArgs {
|
|
|
556
601
|
rawOutput: string;
|
|
557
602
|
exitCode: number;
|
|
558
603
|
stderr: string;
|
|
604
|
+
terminalFailure?: boolean;
|
|
559
605
|
doneAborted: boolean;
|
|
560
606
|
signalAborted: boolean;
|
|
561
607
|
yieldItems?: YieldItem[];
|
|
@@ -619,7 +665,7 @@ function buildPlaceholderYieldOutcome(
|
|
|
619
665
|
|
|
620
666
|
export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): FinalizeSubprocessOutputResult {
|
|
621
667
|
let { rawOutput, exitCode, stderr } = args;
|
|
622
|
-
const { yieldItems, doneAborted, signalAborted, outputSchema } = args;
|
|
668
|
+
const { yieldItems, terminalFailure = false, doneAborted, signalAborted, outputSchema } = args;
|
|
623
669
|
let abortedViaYield = false;
|
|
624
670
|
const hasYield = Array.isArray(yieldItems) && yieldItems.length > 0;
|
|
625
671
|
|
|
@@ -673,8 +719,15 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
|
|
|
673
719
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
674
720
|
rawOutput = `{"error":"Failed to serialize yield data: ${errorMessage}"}`;
|
|
675
721
|
}
|
|
676
|
-
|
|
677
|
-
|
|
722
|
+
// A valid yield can preserve policy-safe public review output after a
|
|
723
|
+
// terminal provider failure, but it cannot convert that failed run
|
|
724
|
+
// into a successful subagent result. A normal yield starts with a
|
|
725
|
+
// non-zero provisional exit code, so use the explicit terminal fact
|
|
726
|
+
// rather than the provisional code to distinguish the two cases.
|
|
727
|
+
if (!terminalFailure) {
|
|
728
|
+
exitCode = 0;
|
|
729
|
+
stderr = "";
|
|
730
|
+
}
|
|
678
731
|
}
|
|
679
732
|
}
|
|
680
733
|
}
|
|
@@ -807,10 +860,135 @@ export function createSubagentSettings(baseSettings: Settings, inheritedServiceT
|
|
|
807
860
|
});
|
|
808
861
|
}
|
|
809
862
|
|
|
863
|
+
/**
|
|
864
|
+
* Finalize routing evidence at the executor return boundary: the effective
|
|
865
|
+
* model is the terminal provider-reported model when present, otherwise the
|
|
866
|
+
* auth-resolved model; substitution causes are appended in order.
|
|
867
|
+
*/
|
|
868
|
+
export function finalizeRoutingEvidence(
|
|
869
|
+
routing: TaskRoutingEvidence | undefined,
|
|
870
|
+
state: {
|
|
871
|
+
resolvedModelString: string | undefined;
|
|
872
|
+
lastAssistantModelString: string | undefined;
|
|
873
|
+
authFallbackUsed: boolean;
|
|
874
|
+
assistantModelMismatch: boolean;
|
|
875
|
+
},
|
|
876
|
+
): TaskRoutingEvidence | undefined {
|
|
877
|
+
if (!routing) return undefined;
|
|
878
|
+
const terminalModel = state.lastAssistantModelString ?? state.resolvedModelString;
|
|
879
|
+
const effectiveModel = terminalModel === undefined ? undefined : boundedSelector(terminalModel);
|
|
880
|
+
const substitutions: TaskRoutingEvidence["substitutions"] = [];
|
|
881
|
+
if (state.authFallbackUsed) substitutions.push("auth_substituted");
|
|
882
|
+
if (state.assistantModelMismatch) substitutions.push("assistant_model_mismatch");
|
|
883
|
+
if (!effectiveModel) {
|
|
884
|
+
if (!routing.notExecuted && !routing.terminal && !routing.attempts) return undefined;
|
|
885
|
+
const terminalEvidence = { ...routing, substitutions };
|
|
886
|
+
assertRoutingEvidenceInvariant(terminalEvidence);
|
|
887
|
+
return terminalEvidence;
|
|
888
|
+
}
|
|
889
|
+
const evidence = {
|
|
890
|
+
...routing,
|
|
891
|
+
effectiveModel,
|
|
892
|
+
...(state.resolvedModelString && state.resolvedModelString !== terminalModel
|
|
893
|
+
? { authResolvedModel: boundedSelector(state.resolvedModelString) }
|
|
894
|
+
: {}),
|
|
895
|
+
substitutions,
|
|
896
|
+
};
|
|
897
|
+
assertRoutingEvidenceInvariant(evidence);
|
|
898
|
+
return evidence;
|
|
899
|
+
}
|
|
900
|
+
|
|
810
901
|
/**
|
|
811
902
|
* Run a single agent in-process.
|
|
812
903
|
*/
|
|
813
|
-
|
|
904
|
+
class AutoroutingProbeAcceptedError extends Error {
|
|
905
|
+
readonly code = "autorouting_probe_accepted";
|
|
906
|
+
constructor() {
|
|
907
|
+
super("autorouting_probe_accepted");
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function toError(error: unknown): Error {
|
|
912
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function formatExecutionError(error: unknown): string {
|
|
916
|
+
if (error instanceof AggregateError) {
|
|
917
|
+
const causes = error.errors.map(cause => (cause instanceof Error ? cause.message : String(cause))).join("; ");
|
|
918
|
+
return causes ? `${error.message}: ${causes}` : error.message;
|
|
919
|
+
}
|
|
920
|
+
return error instanceof Error ? error.stack || error.message : String(error);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function transportFactsFromError(error: unknown): TransportFailureFacts | undefined {
|
|
924
|
+
if (!error || typeof error !== "object") return undefined;
|
|
925
|
+
const value = error as { transportFailure?: unknown };
|
|
926
|
+
if (!value.transportFailure || typeof value.transportFailure !== "object") return undefined;
|
|
927
|
+
return value.transportFailure as TransportFailureFacts;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* True only for the explicit "no credential found for this candidate" signal thrown at the
|
|
932
|
+
* auth_resolve preflight step, never for an unexpected exception the credential lookup itself
|
|
933
|
+
* raised (keychain access denied, corrupted store, I/O failure). auth_resolve is the one op
|
|
934
|
+
* that always advances to the next candidate regardless of any transient marker, so it must
|
|
935
|
+
* only ever be reached via this explicit, typed signal — not by inferring intent from which
|
|
936
|
+
* preflight phase happened to be executing when an unrelated error was thrown.
|
|
937
|
+
*/
|
|
938
|
+
function isCredentialMissingSignal(error: unknown): boolean {
|
|
939
|
+
return Boolean(
|
|
940
|
+
error && typeof error === "object" && (error as { credentialMissing?: unknown }).credentialMissing === true,
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
export function classifyAutoroutingPreflightFailure(
|
|
945
|
+
error: unknown,
|
|
946
|
+
op: Extract<AutoroutingPreflightFailure, { kind: "local" }>["op"],
|
|
947
|
+
): AutoroutingPreflightFailure {
|
|
948
|
+
const facts = transportFactsFromError(error) ?? transportFailureFacts(error);
|
|
949
|
+
if (facts) return { kind: "transport", class: classifyFallbackTrigger(facts).class };
|
|
950
|
+
const typedTransient =
|
|
951
|
+
error && typeof error === "object" && typeof (error as { transient?: unknown }).transient === "boolean"
|
|
952
|
+
? (error as { transient: boolean }).transient
|
|
953
|
+
: undefined;
|
|
954
|
+
// An exception raised while op is auth_resolve is only the deliberate missing-credential
|
|
955
|
+
// signal when it carries the explicit marker; any other error surfacing during that window
|
|
956
|
+
// (an unexpected keychain/config failure) must fail closed like every other unclassified
|
|
957
|
+
// local error, not silently advance as if credentials were simply absent.
|
|
958
|
+
const resolvedOp = op === "auth_resolve" && !isCredentialMissingSignal(error) ? "preflight_validation" : op;
|
|
959
|
+
return {
|
|
960
|
+
kind: "local",
|
|
961
|
+
op: resolvedOp,
|
|
962
|
+
// Local setup failures are fail-closed: only an explicit transient marker
|
|
963
|
+
// can advance a candidate. Unknown session/tool errors are terminal.
|
|
964
|
+
transient: typedTransient === true,
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Map a preflight failure to the attempt code that is recorded in routing evidence AND whether the
|
|
970
|
+
* ledger may advance to the next candidate. These are two different questions: a
|
|
971
|
+
* `config_invalid_terminal` / `unclassified_terminal` code is recorded *and* stops the ledger, so
|
|
972
|
+
* the caller must never infer "advance" from the mere presence of a code.
|
|
973
|
+
*/
|
|
974
|
+
function autoroutingAttemptDisposition(failure: AutoroutingPreflightFailure): {
|
|
975
|
+
code: AutoroutingAttemptCode;
|
|
976
|
+
advance: boolean;
|
|
977
|
+
} {
|
|
978
|
+
if (failure.kind === "local" && failure.op === "auth_resolve")
|
|
979
|
+
return { code: "credential_unavailable", advance: true };
|
|
980
|
+
if (
|
|
981
|
+
failure.kind === "local" &&
|
|
982
|
+
(failure.op === "session_open" || failure.op === "tool_bootstrap") &&
|
|
983
|
+
failure.transient
|
|
984
|
+
)
|
|
985
|
+
return { code: "spawn_transient_retry", advance: true };
|
|
986
|
+
if (failure.kind === "local" && (!failure.transient || failure.op === "preflight_validation"))
|
|
987
|
+
return { code: "config_invalid_terminal", advance: false };
|
|
988
|
+
return { code: "unclassified_terminal", advance: false };
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
export async function runSubprocessOnce(options: ExecutorOptions): Promise<SingleResult> {
|
|
814
992
|
const {
|
|
815
993
|
cwd,
|
|
816
994
|
agent,
|
|
@@ -948,6 +1126,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
948
1126
|
let pauseRequested = false;
|
|
949
1127
|
let paused = false;
|
|
950
1128
|
let modelSubstitutionWarning: ModelSubstitutionWarning | undefined;
|
|
1129
|
+
let authFallbackUsed = false;
|
|
1130
|
+
let assistantModelMismatch = false;
|
|
951
1131
|
let resolvedModelString: string | undefined;
|
|
952
1132
|
let lastAssistantModelString: string | undefined;
|
|
953
1133
|
let activeProviderModelString: string | undefined;
|
|
@@ -957,6 +1137,13 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
957
1137
|
let retryStartOrdinal = 0;
|
|
958
1138
|
const seenAssistantMessages = new WeakSet<AgentMessage>();
|
|
959
1139
|
let llmRequestStarted = false;
|
|
1140
|
+
let preflightFenceCrossed = false;
|
|
1141
|
+
let preflightFailure: AutoroutingPreflightFailure | undefined;
|
|
1142
|
+
let preflightCommitFailure = false;
|
|
1143
|
+
let lifecycleStarted = false;
|
|
1144
|
+
let liveHandleRegistered = false;
|
|
1145
|
+
let probeAccepted = false;
|
|
1146
|
+
let preflightOperation: Extract<AutoroutingPreflightFailure, { kind: "local" }>["op"] = "preflight_validation";
|
|
960
1147
|
const seenAssistantMessageIdentities = new Set<string>();
|
|
961
1148
|
|
|
962
1149
|
// Accumulate usage incrementally from message_end events (no memory for streaming events)
|
|
@@ -1384,6 +1571,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1384
1571
|
effective: assistantModel,
|
|
1385
1572
|
reason: "assistant_model_mismatch",
|
|
1386
1573
|
};
|
|
1574
|
+
assistantModelMismatch = true;
|
|
1575
|
+
|
|
1387
1576
|
progress.modelSubstitutionWarning = modelSubstitutionWarning;
|
|
1388
1577
|
activeSession?.sessionManager.appendModelChange(assistantModel, undefined, {
|
|
1389
1578
|
previousModel: resolvedModelString,
|
|
@@ -1462,14 +1651,23 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1462
1651
|
const runSubagent = async (): Promise<{
|
|
1463
1652
|
exitCode: number;
|
|
1464
1653
|
error?: string;
|
|
1654
|
+
localErrorSummary?: LocalErrorSummary;
|
|
1465
1655
|
aborted?: boolean;
|
|
1466
1656
|
abortReason?: string;
|
|
1467
1657
|
setupFailure?: SetupFailureSummary;
|
|
1658
|
+
probeAccepted?: boolean;
|
|
1659
|
+
preflightFailure?: AutoroutingPreflightFailure;
|
|
1660
|
+
preflightFenceCrossed?: boolean;
|
|
1661
|
+
preflightCommitFailure?: boolean;
|
|
1468
1662
|
durationMs: number;
|
|
1469
1663
|
}> => {
|
|
1664
|
+
let openedSessionManager: SessionManager | null = null;
|
|
1665
|
+
let liveHandleManager: AsyncJobManager | undefined;
|
|
1666
|
+
const liveSubagentId = options.subagentId ?? id;
|
|
1470
1667
|
const sessionAbortController = new AbortController();
|
|
1471
1668
|
let exitCode = 0;
|
|
1472
1669
|
let error: string | undefined;
|
|
1670
|
+
let localErrorSummary: LocalErrorSummary | undefined;
|
|
1473
1671
|
let aborted = false;
|
|
1474
1672
|
let abortReasonText: string | undefined;
|
|
1475
1673
|
let setupFailure: SetupFailureSummary | undefined;
|
|
@@ -1524,11 +1722,12 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1524
1722
|
|
|
1525
1723
|
const canonicalChildScope = getSubagentCanonicalScope(options.parentSessionId, options.subagentId ?? id);
|
|
1526
1724
|
|
|
1725
|
+
if (options.preflightProbe || options.preflightDurable) preflightOperation = "auth_resolve";
|
|
1527
1726
|
const {
|
|
1528
1727
|
model,
|
|
1529
1728
|
thinkingLevel: resolvedThinkingLevel,
|
|
1530
1729
|
explicitThinkingLevel,
|
|
1531
|
-
authFallbackUsed,
|
|
1730
|
+
authFallbackUsed: resolvedAuthFallbackUsed,
|
|
1532
1731
|
requestedModel,
|
|
1533
1732
|
fallbackReason,
|
|
1534
1733
|
activeIndex,
|
|
@@ -1537,7 +1736,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1537
1736
|
} = await awaitAbortable(
|
|
1538
1737
|
resolveModelOverrideWithAuthFallback(
|
|
1539
1738
|
modelPatterns,
|
|
1540
|
-
options.parentActiveModelPattern,
|
|
1739
|
+
options.preflightProbe || options.preflightDurable ? undefined : options.parentActiveModelPattern,
|
|
1541
1740
|
modelRegistry,
|
|
1542
1741
|
settings,
|
|
1543
1742
|
options.parentCredentialSessionId ?? options.parentSessionId,
|
|
@@ -1548,6 +1747,25 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1548
1747
|
canonicalChildScope,
|
|
1549
1748
|
),
|
|
1550
1749
|
);
|
|
1750
|
+
if (
|
|
1751
|
+
(options.preflightProbe || options.preflightDurable) &&
|
|
1752
|
+
model &&
|
|
1753
|
+
typeof modelRegistry.getApiKey === "function"
|
|
1754
|
+
) {
|
|
1755
|
+
preflightOperation = "auth_resolve";
|
|
1756
|
+
const exactKey = await awaitAbortable(
|
|
1757
|
+
modelRegistry.getApiKey(model, options.parentCredentialSessionId ?? options.parentSessionId),
|
|
1758
|
+
);
|
|
1759
|
+
if (!exactKey)
|
|
1760
|
+
throw Object.assign(new Error("autorouting credential unavailable"), {
|
|
1761
|
+
transient: false,
|
|
1762
|
+
// Explicit signal: credentials were looked up successfully and are simply absent for
|
|
1763
|
+
// this candidate. Distinguishes the deliberate "advance to next candidate" case from
|
|
1764
|
+
// an unexpected error the lookup itself threw (see classifyAutoroutingPreflightFailure).
|
|
1765
|
+
credentialMissing: true,
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
authFallbackUsed = resolvedAuthFallbackUsed;
|
|
1551
1769
|
if (model) {
|
|
1552
1770
|
resolvedModelString = formatModelString(model);
|
|
1553
1771
|
activeProviderModelString = resolvedModelString;
|
|
@@ -1588,23 +1806,55 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1588
1806
|
: (thinkingLevel ?? resolvedThinkingLevel);
|
|
1589
1807
|
effectiveThinkingLevelForWarning = effectiveThinkingLevel;
|
|
1590
1808
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
)
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1809
|
+
preflightOperation = "session_open";
|
|
1810
|
+
const sessionManager = options.preflightProbe
|
|
1811
|
+
? SessionManager.inMemory(worktree ?? cwd)
|
|
1812
|
+
: options.preflightDurable
|
|
1813
|
+
? options.managedPersistence
|
|
1814
|
+
? await awaitAbortable(
|
|
1815
|
+
options.managedPersistence.openStagedSession(options.autoroutingAttemptId ?? id),
|
|
1816
|
+
)
|
|
1817
|
+
: sessionFile
|
|
1818
|
+
? await awaitAbortable(
|
|
1819
|
+
SessionManager.openStaged(sessionFile, undefined, options.autoroutingAttemptId ?? id),
|
|
1820
|
+
)
|
|
1821
|
+
: SessionManager.inMemory(worktree ?? cwd)
|
|
1822
|
+
: options.managedPersistence
|
|
1823
|
+
? await awaitAbortable(
|
|
1824
|
+
options.managedPersistence.openSession(
|
|
1825
|
+
worktree ?? cwd,
|
|
1826
|
+
subagentSettings.get("sessionMemory.mode"),
|
|
1827
|
+
),
|
|
1828
|
+
)
|
|
1829
|
+
: sessionFile
|
|
1830
|
+
? await awaitAbortable(
|
|
1831
|
+
SessionManager.open(
|
|
1832
|
+
sessionFile,
|
|
1833
|
+
SessionManager.explicitDestination(path.dirname(sessionFile)),
|
|
1834
|
+
new FileSessionStorage(),
|
|
1835
|
+
subagentSettings.get("session.directoryMigration") === "disabled"
|
|
1836
|
+
? "disabled"
|
|
1837
|
+
: "copy-retain",
|
|
1838
|
+
subagentSettings.get("sessionMemory.mode"),
|
|
1839
|
+
),
|
|
1840
|
+
)
|
|
1841
|
+
: SessionManager.inMemory(worktree ?? cwd);
|
|
1842
|
+
openedSessionManager = sessionManager;
|
|
1843
|
+
if (options.parentArtifactManager && !options.preflightProbe && !options.preflightDurable) {
|
|
1607
1844
|
sessionManager.adoptArtifactManager(options.parentArtifactManager);
|
|
1845
|
+
} else if (options.preflightDurable && !options.managedPersistence) {
|
|
1846
|
+
// Only the unmanaged durable preflight adopts its attempt staging here. With
|
|
1847
|
+
// managedPersistence the session came from openStagedSession(), which already
|
|
1848
|
+
// adopted exactly one task-scoped attempt root; adopting a second instance over
|
|
1849
|
+
// the same root would leave the first unreachable from commit/discard.
|
|
1850
|
+
const attemptId = options.autoroutingAttemptId ?? id;
|
|
1851
|
+
const parentArtifacts =
|
|
1852
|
+
options.parentArtifactManager ??
|
|
1853
|
+
(sessionFile
|
|
1854
|
+
? new ArtifactManager(sessionFile.endsWith(".jsonl") ? sessionFile.slice(0, -6) : sessionFile)
|
|
1855
|
+
: undefined);
|
|
1856
|
+
if (parentArtifacts)
|
|
1857
|
+
sessionManager.adoptArtifactManager(parentArtifacts.createAttemptStaging(attemptId), parentArtifacts);
|
|
1608
1858
|
}
|
|
1609
1859
|
|
|
1610
1860
|
// Subagents do not inherit or discover MCP runtime tools in the GJC surface.
|
|
@@ -1685,6 +1935,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1685
1935
|
logger.warn("Failed to render GJC plugin agent prompt additions", { error });
|
|
1686
1936
|
}
|
|
1687
1937
|
|
|
1938
|
+
if (options.preflightProbe || options.preflightDurable) preflightOperation = "tool_bootstrap";
|
|
1688
1939
|
const { session } = await awaitAbortable(
|
|
1689
1940
|
canonicalSdk.createAgentSession({
|
|
1690
1941
|
cwd: worktree ?? cwd,
|
|
@@ -1773,7 +2024,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1773
2024
|
}
|
|
1774
2025
|
const liveSubagentId = options.subagentId ?? id;
|
|
1775
2026
|
const manager = options.asyncJobManager ?? AsyncJobManager.instance();
|
|
1776
|
-
|
|
2027
|
+
liveHandleManager = manager ?? undefined;
|
|
2028
|
+
const registerLiveHandle = (): void => {
|
|
2029
|
+
if (options.preflightProbe || liveHandleRegistered || !manager) return;
|
|
1777
2030
|
manager.registerLiveHandle(liveSubagentId, {
|
|
1778
2031
|
requestPause: () => {
|
|
1779
2032
|
pauseRequested = true;
|
|
@@ -1790,10 +2043,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1790
2043
|
await session.sendUserMessage(content, { deliverAs });
|
|
1791
2044
|
},
|
|
1792
2045
|
});
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
2046
|
+
liveHandleRegistered = true;
|
|
2047
|
+
};
|
|
2048
|
+
const emitLifecycleStart = (): void => {
|
|
2049
|
+
if (options.preflightProbe || lifecycleStarted || !options.eventBus) return;
|
|
1797
2050
|
options.eventBus.emit(TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
|
|
1798
2051
|
id,
|
|
1799
2052
|
agent: agent.name,
|
|
@@ -1803,6 +2056,11 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1803
2056
|
sessionFile: sessionFile ?? undefined,
|
|
1804
2057
|
index,
|
|
1805
2058
|
});
|
|
2059
|
+
lifecycleStarted = true;
|
|
2060
|
+
};
|
|
2061
|
+
if (!options.preflightProbe && !options.preflightDurable) {
|
|
2062
|
+
registerLiveHandle();
|
|
2063
|
+
emitLifecycleStart();
|
|
1806
2064
|
}
|
|
1807
2065
|
|
|
1808
2066
|
const subagentToolNames = session.getActiveToolNames();
|
|
@@ -1812,13 +2070,15 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1812
2070
|
await awaitAbortable(session.setActiveToolsByName(filteredSubagentTools));
|
|
1813
2071
|
}
|
|
1814
2072
|
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
2073
|
+
if (!options.preflightProbe && !options.preflightDurable) {
|
|
2074
|
+
session.sessionManager.appendSessionInit({
|
|
2075
|
+
systemPrompt: session.agent.state.systemPrompt.join("\n\n"),
|
|
2076
|
+
task,
|
|
2077
|
+
tools: session.getActiveToolNames(),
|
|
2078
|
+
outputSchema,
|
|
2079
|
+
forkContext: options.forkContextSeed?.metadata,
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
1822
2082
|
|
|
1823
2083
|
abortSignal.addEventListener(
|
|
1824
2084
|
"abort",
|
|
@@ -1836,7 +2096,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1836
2096
|
|
|
1837
2097
|
const extensionRunner = session.extensionRunner;
|
|
1838
2098
|
const pendingExtensionMessages: Promise<void>[] = [];
|
|
1839
|
-
|
|
2099
|
+
// Disposable preflight probes execute an in-memory candidate session purely to observe a
|
|
2100
|
+
// live model provider; they are discarded at the acceptance fence, so user/session
|
|
2101
|
+
// extensions (session_start emits and tool wrappers) must never run against them.
|
|
2102
|
+
if (extensionRunner && !options.preflightProbe) {
|
|
1840
2103
|
extensionRunner.initialize(
|
|
1841
2104
|
{
|
|
1842
2105
|
sendMessage: (message, options) => {
|
|
@@ -2021,9 +2284,50 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2021
2284
|
);
|
|
2022
2285
|
}
|
|
2023
2286
|
}
|
|
2287
|
+
if (options.preflightProbe || options.preflightDurable) preflightOperation = "preflight_validation";
|
|
2024
2288
|
const runMode = options.runMode ?? "initial";
|
|
2025
|
-
|
|
2289
|
+
let postFencePublication: Promise<void> | undefined;
|
|
2290
|
+
const publishPostFence = async (): Promise<void> => {
|
|
2291
|
+
if (!options.preflightDurable) return;
|
|
2292
|
+
if (postFencePublication) return postFencePublication;
|
|
2293
|
+
postFencePublication = (async () => {
|
|
2294
|
+
if (!openedSessionManager) throw new Error("preflight session manager unavailable");
|
|
2295
|
+
try {
|
|
2296
|
+
// The staged transcript and artifacts become visible only here, at
|
|
2297
|
+
// the real provider acceptance fence.
|
|
2298
|
+
await openedSessionManager.commitStaged({ deferArtifactFinalize: true });
|
|
2299
|
+
session.sessionManager.appendSessionInit({
|
|
2300
|
+
systemPrompt: session.agent.state.systemPrompt.join("\n\n"),
|
|
2301
|
+
task,
|
|
2302
|
+
tools: session.getActiveToolNames(),
|
|
2303
|
+
outputSchema,
|
|
2304
|
+
forkContext: options.forkContextSeed?.metadata,
|
|
2305
|
+
});
|
|
2306
|
+
await openedSessionManager.refreshStagedCommitSnapshot();
|
|
2307
|
+
registerLiveHandle();
|
|
2308
|
+
emitLifecycleStart();
|
|
2309
|
+
openedSessionManager.finalizeStagedCommit();
|
|
2310
|
+
} catch (error) {
|
|
2311
|
+
preflightCommitFailure = true;
|
|
2312
|
+
try {
|
|
2313
|
+
await openedSessionManager.rollbackCommittedStaged();
|
|
2314
|
+
} catch (cleanupError) {
|
|
2315
|
+
throw new AggregateError(
|
|
2316
|
+
[toError(error), toError(cleanupError)],
|
|
2317
|
+
"Post-fence publication and cleanup both failed.",
|
|
2318
|
+
);
|
|
2319
|
+
}
|
|
2320
|
+
throw error;
|
|
2321
|
+
}
|
|
2322
|
+
})();
|
|
2323
|
+
return postFencePublication;
|
|
2324
|
+
};
|
|
2325
|
+
const markLlmRequestStarted = async () => {
|
|
2326
|
+
if (options.preflightProbe) throw new AutoroutingProbeAcceptedError();
|
|
2327
|
+
if (options.preflightDurable) await publishPostFence();
|
|
2026
2328
|
llmRequestStarted = true;
|
|
2329
|
+
preflightFenceCrossed = true;
|
|
2330
|
+
options.preflightFenceCallback?.();
|
|
2027
2331
|
};
|
|
2028
2332
|
const promptOptions = {
|
|
2029
2333
|
attribution: "agent" as const,
|
|
@@ -2098,6 +2402,29 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2098
2402
|
} else if (lastAssistant.stopReason === "error") {
|
|
2099
2403
|
exitCode = 1;
|
|
2100
2404
|
error ??= lastAssistant.errorMessage || "Subagent failed";
|
|
2405
|
+
// A terminal local (non-provider) failure carries an actionable
|
|
2406
|
+
// kind: surface it in the parent receipt instead of the generic
|
|
2407
|
+
// "Task failed; error recorded." preview (#4618). Overflow
|
|
2408
|
+
// summaries are built ONLY from the structured, identity-checked
|
|
2409
|
+
// `bufferOverflow` shape - never the free-form `errorMessage`,
|
|
2410
|
+
// which a foreign self-labeled error can fill with arbitrary
|
|
2411
|
+
// text. A self-labeled overflow WITHOUT the shape degrades to a
|
|
2412
|
+
// neutral sentence instead of forwarding message text.
|
|
2413
|
+
if (isAssistantLocalErrorKind(lastAssistant.errorKind)) {
|
|
2414
|
+
localErrorSummary = createLocalErrorSummary(
|
|
2415
|
+
lastAssistant.errorKind,
|
|
2416
|
+
lastAssistant.errorMessage ?? error,
|
|
2417
|
+
lastAssistant.bufferOverflow,
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
if (options.preflightDurable && preflightFenceCrossed) {
|
|
2422
|
+
const facts =
|
|
2423
|
+
(lastAssistant as AssistantMessage & { transportFailure?: TransportFailureFacts }).transportFailure ??
|
|
2424
|
+
transportFailureFacts(lastAssistant);
|
|
2425
|
+
preflightFailure = facts
|
|
2426
|
+
? { kind: "transport", class: classifyFallbackTrigger(facts).class }
|
|
2427
|
+
: { kind: "transport", class: "other" };
|
|
2101
2428
|
}
|
|
2102
2429
|
if (paused) {
|
|
2103
2430
|
exitCode = 0;
|
|
@@ -2111,14 +2438,42 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2111
2438
|
reason: "assistant_model_mismatch",
|
|
2112
2439
|
};
|
|
2113
2440
|
progress.modelSubstitutionWarning = modelSubstitutionWarning;
|
|
2441
|
+
assistantModelMismatch = true;
|
|
2114
2442
|
}
|
|
2115
2443
|
} catch (err) {
|
|
2116
2444
|
exitCode = 1;
|
|
2117
|
-
if (
|
|
2118
|
-
|
|
2119
|
-
|
|
2445
|
+
if (err instanceof AutoroutingProbeAcceptedError) {
|
|
2446
|
+
probeAccepted = true;
|
|
2447
|
+
exitCode = 0;
|
|
2448
|
+
error = undefined;
|
|
2449
|
+
} else if (!abortSignal.aborted) {
|
|
2450
|
+
error = formatExecutionError(err);
|
|
2451
|
+
if (options.preflightDurable && preflightFenceCrossed) {
|
|
2452
|
+
const facts = transportFactsFromError(err) ?? transportFailureFacts(err);
|
|
2453
|
+
preflightFailure = facts
|
|
2454
|
+
? { kind: "transport", class: classifyFallbackTrigger(facts).class }
|
|
2455
|
+
: { kind: "transport", class: "other" };
|
|
2456
|
+
} else if (options.preflightProbe || options.preflightDurable) {
|
|
2457
|
+
preflightFailure = classifyAutoroutingPreflightFailure(err, preflightOperation);
|
|
2458
|
+
}
|
|
2459
|
+
if (!llmRequestStarted)
|
|
2460
|
+
setupFailure = createSetupFailureSummary(err instanceof AggregateError ? error : err);
|
|
2120
2461
|
}
|
|
2121
2462
|
} finally {
|
|
2463
|
+
if (options.preflightDurable && !preflightFenceCrossed && openedSessionManager) {
|
|
2464
|
+
try {
|
|
2465
|
+
await openedSessionManager.discardStaged();
|
|
2466
|
+
} catch (cleanupError) {
|
|
2467
|
+
const primary = error ?? "Autorouting preflight candidate failed before acceptance.";
|
|
2468
|
+
error = `${primary}\nCleanup failure: ${formatExecutionError(cleanupError)}`;
|
|
2469
|
+
setupFailure ??= createSetupFailureSummary(error);
|
|
2470
|
+
// Fail closed: a failed pre-fence discard may have left candidate-owned staging
|
|
2471
|
+
// residue behind, so the zero-residue guarantee no longer holds for this attempt.
|
|
2472
|
+
// Downgrade any transient classification to terminal so the ledger stops here
|
|
2473
|
+
// instead of advancing to the next candidate over unknown residue.
|
|
2474
|
+
preflightFailure = { kind: "local", op: "preflight_validation", transient: false };
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2122
2477
|
if (abortSignal.aborted) {
|
|
2123
2478
|
aborted = abortReason === "signal" || runtimeLimitExceeded || abortReason === undefined;
|
|
2124
2479
|
if (aborted) {
|
|
@@ -2127,7 +2482,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2127
2482
|
if (exitCode === 0) exitCode = 1;
|
|
2128
2483
|
}
|
|
2129
2484
|
sessionAbortController.abort();
|
|
2130
|
-
|
|
2485
|
+
if (liveHandleRegistered) liveHandleManager?.removeLiveHandle(liveSubagentId);
|
|
2131
2486
|
if (unsubscribe) {
|
|
2132
2487
|
try {
|
|
2133
2488
|
unsubscribe();
|
|
@@ -2150,9 +2505,14 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2150
2505
|
return {
|
|
2151
2506
|
exitCode,
|
|
2152
2507
|
error,
|
|
2508
|
+
localErrorSummary,
|
|
2153
2509
|
aborted,
|
|
2154
2510
|
abortReason: aborted ? abortReasonText : undefined,
|
|
2155
2511
|
setupFailure,
|
|
2512
|
+
probeAccepted,
|
|
2513
|
+
preflightFailure,
|
|
2514
|
+
preflightFenceCrossed,
|
|
2515
|
+
preflightCommitFailure,
|
|
2156
2516
|
durationMs: Date.now() - startTime,
|
|
2157
2517
|
};
|
|
2158
2518
|
};
|
|
@@ -2183,6 +2543,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2183
2543
|
rawOutput,
|
|
2184
2544
|
exitCode,
|
|
2185
2545
|
stderr,
|
|
2546
|
+
terminalFailure: done.error !== undefined || Boolean(done.aborted),
|
|
2186
2547
|
doneAborted: Boolean(done.aborted),
|
|
2187
2548
|
signalAborted: Boolean(signal?.aborted),
|
|
2188
2549
|
yieldItems,
|
|
@@ -2273,10 +2634,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2273
2634
|
: undefined;
|
|
2274
2635
|
progress.setupFailure = done.setupFailure;
|
|
2275
2636
|
progress.status = paused ? "paused" : wasAborted ? "aborted" : exitCode === 0 ? "completed" : "failed";
|
|
2276
|
-
scheduleProgress(true);
|
|
2637
|
+
if (!options.preflightProbe) scheduleProgress(true);
|
|
2277
2638
|
|
|
2278
2639
|
// Emit lifecycle end event after finalization so yield status is reflected
|
|
2279
|
-
if (options.eventBus) {
|
|
2640
|
+
if (lifecycleStarted && !options.preflightProbe && options.eventBus) {
|
|
2280
2641
|
options.eventBus.emit(TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
|
|
2281
2642
|
id,
|
|
2282
2643
|
agent: agent.name,
|
|
@@ -2288,6 +2649,13 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2288
2649
|
});
|
|
2289
2650
|
}
|
|
2290
2651
|
|
|
2652
|
+
const routingEvidence = finalizeRoutingEvidence(options.routing, {
|
|
2653
|
+
resolvedModelString,
|
|
2654
|
+
lastAssistantModelString,
|
|
2655
|
+
authFallbackUsed,
|
|
2656
|
+
assistantModelMismatch,
|
|
2657
|
+
});
|
|
2658
|
+
|
|
2291
2659
|
return {
|
|
2292
2660
|
index,
|
|
2293
2661
|
id,
|
|
@@ -2304,12 +2672,18 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2304
2672
|
durationMs: Date.now() - startTime,
|
|
2305
2673
|
tokens: progress.tokens,
|
|
2306
2674
|
contextTokens: progress.contextTokens,
|
|
2675
|
+
routing: routingEvidence,
|
|
2307
2676
|
contextWindow: progress.contextWindow,
|
|
2308
2677
|
modelOverride,
|
|
2309
2678
|
modelSubstitutionWarning,
|
|
2310
2679
|
fastMode: progress.fastMode,
|
|
2311
2680
|
error: exitCode !== 0 && stderr ? stderr : undefined,
|
|
2312
2681
|
setupFailure: done.setupFailure,
|
|
2682
|
+
localErrorSummary: done.localErrorSummary,
|
|
2683
|
+
preflightFailure: done.preflightFailure,
|
|
2684
|
+
preflightFenceCrossed: done.preflightFenceCrossed,
|
|
2685
|
+
preflightCommitFailure: done.preflightCommitFailure,
|
|
2686
|
+
preflightProbeAccepted: done.probeAccepted,
|
|
2313
2687
|
aborted: wasAborted,
|
|
2314
2688
|
abortReason: finalAbortReason,
|
|
2315
2689
|
paused,
|
|
@@ -2322,3 +2696,203 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2322
2696
|
reviewFindingsRef,
|
|
2323
2697
|
};
|
|
2324
2698
|
}
|
|
2699
|
+
|
|
2700
|
+
export function boundedSelector(value: string): string {
|
|
2701
|
+
const bounded = value
|
|
2702
|
+
.normalize("NFKC")
|
|
2703
|
+
.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu, "")
|
|
2704
|
+
.slice(0, AUTOROUTING_SELECTOR_MAX_LENGTH);
|
|
2705
|
+
return bounded || "<omitted-selector>";
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
export function buildBoundedRoutingSkips(
|
|
2709
|
+
skips: ExecutorOptions["autoroutingSkips"],
|
|
2710
|
+
): Pick<TaskRoutingEvidence, "skips" | "omittedSkipCount" | "omittedByCode"> {
|
|
2711
|
+
if (!skips || skips.length === 0) return {};
|
|
2712
|
+
const retained = skips.slice(0, 16).map(skip => ({ selector: boundedSelector(skip.selector), code: skip.code }));
|
|
2713
|
+
const omitted = skips.slice(16);
|
|
2714
|
+
const omittedByCode: NonNullable<TaskRoutingEvidence["omittedByCode"]> = {};
|
|
2715
|
+
for (const skip of omitted) omittedByCode[skip.code] = (omittedByCode[skip.code] ?? 0) + 1;
|
|
2716
|
+
return {
|
|
2717
|
+
skips: retained,
|
|
2718
|
+
...(omitted.length > 0 ? { omittedSkipCount: omitted.length, omittedByCode } : {}),
|
|
2719
|
+
};
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
function evidenceWithAttempts(
|
|
2723
|
+
routing: TaskRoutingEvidence | undefined,
|
|
2724
|
+
attempts: readonly AutoroutingAttempt[],
|
|
2725
|
+
terminal?: TaskRoutingEvidence["terminal"],
|
|
2726
|
+
): TaskRoutingEvidence | undefined {
|
|
2727
|
+
if (!routing) return undefined;
|
|
2728
|
+
const evidence: TaskRoutingEvidence = {
|
|
2729
|
+
...routing,
|
|
2730
|
+
requestedSelector: boundedSelector(routing.requestedSelector),
|
|
2731
|
+
attempts: attempts.map(attempt => ({ ...attempt, selector: boundedSelector(attempt.selector) })),
|
|
2732
|
+
...(terminal ? { terminal, notExecuted: true, effectiveModel: undefined } : {}),
|
|
2733
|
+
};
|
|
2734
|
+
assertRoutingEvidenceInvariant(evidence);
|
|
2735
|
+
return evidence;
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
function preflightTerminalResult(
|
|
2739
|
+
options: ExecutorOptions,
|
|
2740
|
+
attempts: readonly AutoroutingAttempt[],
|
|
2741
|
+
terminal: "preflight_exhausted" | "all_candidates_skipped",
|
|
2742
|
+
prior?: SingleResult,
|
|
2743
|
+
): SingleResult {
|
|
2744
|
+
const result: SingleResult = prior
|
|
2745
|
+
? { ...prior }
|
|
2746
|
+
: {
|
|
2747
|
+
index: options.index,
|
|
2748
|
+
id: options.id,
|
|
2749
|
+
agent: options.agent.name,
|
|
2750
|
+
agentSource: options.agent.source,
|
|
2751
|
+
task: options.task,
|
|
2752
|
+
assignment: options.assignment,
|
|
2753
|
+
description: options.description,
|
|
2754
|
+
exitCode: 1,
|
|
2755
|
+
output: "",
|
|
2756
|
+
stderr: "Autorouting preflight exhausted.",
|
|
2757
|
+
truncated: false,
|
|
2758
|
+
durationMs: 0,
|
|
2759
|
+
tokens: 0,
|
|
2760
|
+
};
|
|
2761
|
+
// Preserve a diagnostic from the last attempt so terminalization does not erase why the candidate
|
|
2762
|
+
// failed (including an appended pre-fence cleanup failure). It MUST go through
|
|
2763
|
+
// createSetupFailureSummary, the established egress sanitizer: it redacts authorization/cookie
|
|
2764
|
+
// headers, URL credentials, API-key labels, bare provider tokens and local/Windows absolute paths,
|
|
2765
|
+
// collapses all whitespace (so CR/LF/TAB cannot survive for newline injection), and caps length.
|
|
2766
|
+
// Never interpolate a raw `error` string here — it may carry a full stack with secrets or paths.
|
|
2767
|
+
const rawDiagnostic =
|
|
2768
|
+
typeof prior?.error === "string" && prior.error.trim().length > 0
|
|
2769
|
+
? prior.error
|
|
2770
|
+
: (prior?.setupFailure?.summary ?? "");
|
|
2771
|
+
const sanitizedDiagnostic = rawDiagnostic.trim() ? createSetupFailureSummary(rawDiagnostic).summary : "";
|
|
2772
|
+
const priorDiagnostic =
|
|
2773
|
+
sanitizedDiagnostic && sanitizedDiagnostic !== "Subagent setup failed." ? sanitizedDiagnostic : "";
|
|
2774
|
+
result.exitCode = 1;
|
|
2775
|
+
result.output = "";
|
|
2776
|
+
result.stderr = priorDiagnostic
|
|
2777
|
+
? `Autorouting preflight exhausted. Last candidate diagnostic: ${priorDiagnostic}`
|
|
2778
|
+
: "Autorouting preflight exhausted.";
|
|
2779
|
+
result.error = result.stderr;
|
|
2780
|
+
result.setupFailure = {
|
|
2781
|
+
summary: priorDiagnostic
|
|
2782
|
+
? `Autorouting preflight did not accept a candidate. Last candidate diagnostic: ${priorDiagnostic}`
|
|
2783
|
+
: "Autorouting preflight did not accept a candidate.",
|
|
2784
|
+
};
|
|
2785
|
+
result.routing = evidenceWithAttempts(options.routing, attempts, terminal);
|
|
2786
|
+
return result;
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
/** Run routed initial tasks through the bounded probe/durable candidate ledger. */
|
|
2790
|
+
export async function runSubprocess(options: ExecutorOptions): Promise<SingleResult> {
|
|
2791
|
+
if (
|
|
2792
|
+
!options.autoroutingPreflight ||
|
|
2793
|
+
(options.runMode ?? "initial") !== "initial" ||
|
|
2794
|
+
!options.autoroutingCandidates
|
|
2795
|
+
) {
|
|
2796
|
+
return runSubprocessOnce(options);
|
|
2797
|
+
}
|
|
2798
|
+
const attempts: AutoroutingAttempt[] = [];
|
|
2799
|
+
const consumed = new Set<string>();
|
|
2800
|
+
const skips = buildBoundedRoutingSkips(options.autoroutingSkips);
|
|
2801
|
+
// Candidates were already validated and pinned against the live model snapshot by
|
|
2802
|
+
// normalizeTierSelector before reaching here. boundedSelector is an evidence/telemetry
|
|
2803
|
+
// sanitizer (NFKC normalization, control-char stripping, 256-char truncation) meant for
|
|
2804
|
+
// text that gets rendered or persisted; running it on the live selector could compose
|
|
2805
|
+
// characters differently or truncate a long-but-valid selector, sending execution to a
|
|
2806
|
+
// model that never passed preflight. Only bound the copies that reach evidence/telemetry
|
|
2807
|
+
// (attempts, skips, requestedSelector), never the selector actually used for modelOverride.
|
|
2808
|
+
const candidates = options.autoroutingCandidates.filter(selector => selector.length > 0);
|
|
2809
|
+
const durablePublicationAvailable =
|
|
2810
|
+
options.managedPersistence !== undefined ||
|
|
2811
|
+
(typeof options.sessionFile === "string" && options.sessionFile.length > 0);
|
|
2812
|
+
const routedOptions = options.routing ? { ...options.routing, ...skips } : options.routing;
|
|
2813
|
+
if (candidates.length === 0)
|
|
2814
|
+
return preflightTerminalResult({ ...options, routing: routedOptions }, attempts, "all_candidates_skipped");
|
|
2815
|
+
let prior: SingleResult | undefined;
|
|
2816
|
+
for (const selector of candidates) {
|
|
2817
|
+
if (consumed.has(selector) || consumed.size >= 3) continue;
|
|
2818
|
+
consumed.add(selector);
|
|
2819
|
+
if (options.autoroutingPreflightErrors?.has(selector)) {
|
|
2820
|
+
const preflightError = options.autoroutingPreflightErrors.get(selector);
|
|
2821
|
+
const failure = classifyAutoroutingPreflightFailure(preflightError, "auth_resolve");
|
|
2822
|
+
const { code } = autoroutingAttemptDisposition(failure);
|
|
2823
|
+
attempts.push({ selector, phase: "probe", code });
|
|
2824
|
+
return preflightTerminalResult({ ...options, routing: routedOptions }, attempts, "preflight_exhausted");
|
|
2825
|
+
}
|
|
2826
|
+
const probe = await runSubprocessOnce({
|
|
2827
|
+
...options,
|
|
2828
|
+
autoroutingPreflight: false,
|
|
2829
|
+
preflightProbe: true,
|
|
2830
|
+
preflightDurable: false,
|
|
2831
|
+
modelOverride: [selector],
|
|
2832
|
+
parentActiveModelPattern: undefined,
|
|
2833
|
+
autoroutingCandidates: undefined,
|
|
2834
|
+
autoroutingSkips: undefined,
|
|
2835
|
+
sessionFile: null,
|
|
2836
|
+
artifactsDir: undefined,
|
|
2837
|
+
persistArtifacts: false,
|
|
2838
|
+
managedPersistence: undefined,
|
|
2839
|
+
parentArtifactManager: undefined,
|
|
2840
|
+
routing: undefined,
|
|
2841
|
+
});
|
|
2842
|
+
prior = probe;
|
|
2843
|
+
if (!probe.preflightProbeAccepted) {
|
|
2844
|
+
const failure = probe.preflightFailure ?? { kind: "local", op: "preflight_validation", transient: false };
|
|
2845
|
+
const { code, advance } = autoroutingAttemptDisposition(failure);
|
|
2846
|
+
attempts.push({ selector, phase: "probe", code });
|
|
2847
|
+
if (!advance)
|
|
2848
|
+
return preflightTerminalResult(
|
|
2849
|
+
{ ...options, routing: routedOptions },
|
|
2850
|
+
attempts,
|
|
2851
|
+
"preflight_exhausted",
|
|
2852
|
+
probe,
|
|
2853
|
+
);
|
|
2854
|
+
continue;
|
|
2855
|
+
}
|
|
2856
|
+
attempts.push({ selector, phase: "probe", code: "probe_passed" });
|
|
2857
|
+
const durable = await runSubprocessOnce({
|
|
2858
|
+
...options,
|
|
2859
|
+
autoroutingPreflight: false,
|
|
2860
|
+
preflightProbe: false,
|
|
2861
|
+
// A synchronous Task may have no child transcript or artifact authority. In
|
|
2862
|
+
// that case there is nothing durable to publish at the provider fence, so
|
|
2863
|
+
// execute the accepted candidate through the artifact-only path instead of
|
|
2864
|
+
// asking an in-memory SessionManager to commit a nonexistent staged session.
|
|
2865
|
+
preflightDurable: durablePublicationAvailable,
|
|
2866
|
+
autoroutingAttemptId: `${options.id}-${consumed.size}`,
|
|
2867
|
+
modelOverride: [selector],
|
|
2868
|
+
parentActiveModelPattern: undefined,
|
|
2869
|
+
autoroutingCandidates: undefined,
|
|
2870
|
+
autoroutingSkips: undefined,
|
|
2871
|
+
routing: routedOptions,
|
|
2872
|
+
});
|
|
2873
|
+
prior = durable;
|
|
2874
|
+
if (durable.preflightCommitFailure) {
|
|
2875
|
+
attempts.push({ selector, phase: "durable", code: "post_acceptance_failure" });
|
|
2876
|
+
return { ...durable, routing: evidenceWithAttempts(durable.routing ?? routedOptions, attempts) };
|
|
2877
|
+
}
|
|
2878
|
+
if (durable.preflightFenceCrossed) {
|
|
2879
|
+
if (durable.exitCode === 0) {
|
|
2880
|
+
attempts.push({ selector, phase: "durable", code: "accepted" });
|
|
2881
|
+
return { ...durable, routing: evidenceWithAttempts(durable.routing ?? routedOptions, attempts) };
|
|
2882
|
+
}
|
|
2883
|
+
attempts.push({ selector, phase: "durable", code: "post_acceptance_failure" });
|
|
2884
|
+
return { ...durable, routing: evidenceWithAttempts(durable.routing ?? routedOptions, attempts) };
|
|
2885
|
+
}
|
|
2886
|
+
const failure = durable.preflightFailure ?? { kind: "local", op: "preflight_validation", transient: false };
|
|
2887
|
+
const { code, advance } = autoroutingAttemptDisposition(failure);
|
|
2888
|
+
attempts.push({ selector, phase: "durable", code });
|
|
2889
|
+
if (!advance)
|
|
2890
|
+
return preflightTerminalResult(
|
|
2891
|
+
{ ...options, routing: routedOptions },
|
|
2892
|
+
attempts,
|
|
2893
|
+
"preflight_exhausted",
|
|
2894
|
+
durable,
|
|
2895
|
+
);
|
|
2896
|
+
}
|
|
2897
|
+
return preflightTerminalResult({ ...options, routing: routedOptions }, attempts, "preflight_exhausted", prior);
|
|
2898
|
+
}
|