@oh-my-pi/pi-coding-agent 17.0.8 → 17.1.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 +87 -1
- package/README.md +1 -1
- package/dist/cli.js +5495 -5021
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +350 -50
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
- package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
- package/dist/types/live/controller.d.ts +40 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +48 -0
- package/dist/types/live/visualizer.d.ts +33 -0
- package/dist/types/lsp/client.d.ts +18 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/manager.d.ts +6 -2
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/render.d.ts +2 -2
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/mcp/tool-bridge.d.ts +7 -3
- package/dist/types/mcp/types.d.ts +6 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/oauth-selector.d.ts +8 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/settings-defs.d.ts +8 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +5 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
- package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +314 -0
- package/dist/types/session/agent-session.d.ts +120 -750
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +49 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +22 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-loader.d.ts +6 -4
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +143 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/task/types.d.ts +14 -0
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/hub/jobs.d.ts +1 -1
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/report-tool-issue.d.ts +24 -9
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +14 -0
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +95 -1
- package/package.json +13 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/scripts/legacy-pi-virtual-module.ts +1 -1
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +2 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/grievances-cli.ts +2 -2
- package/src/cli/usage-cli.ts +11 -2
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/__tests__/model-registry.test.ts +147 -0
- package/src/config/model-registry.ts +50 -2
- package/src/config/model-resolver.ts +0 -1
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +250 -54
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +89 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/registry-helpers.ts +40 -0
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/audio-worklet.txt +59 -0
- package/src/live/browser-runtime.txt +221 -0
- package/src/live/controller.ts +464 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +490 -0
- package/src/live/visualizer.ts +234 -0
- package/src/lsp/client.ts +52 -9
- package/src/lsp/clients/biome-client.ts +3 -4
- package/src/lsp/index.ts +44 -11
- package/src/main.ts +9 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/manager.ts +39 -8
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/render.ts +94 -35
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/mcp/tool-bridge.ts +107 -11
- package/src/mcp/types.ts +7 -0
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +27 -10
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/oauth-selector.ts +24 -7
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/settings-defs.ts +28 -3
- package/src/modes/components/settings-selector.ts +178 -6
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.test.ts +63 -2
- package/src/modes/components/tool-execution.ts +11 -2
- package/src/modes/controllers/command-controller.ts +70 -22
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/live-command-controller.ts +178 -0
- package/src/modes/controllers/mcp-command-controller.ts +80 -41
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +18 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +160 -38
- package/src/modes/rpc/rpc-client.ts +94 -3
- package/src/modes/rpc/rpc-frame.ts +164 -4
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +79 -7
- package/src/modes/rpc/rpc-types.ts +34 -2
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/model.ts +5 -7
- package/src/modes/setup-wizard/scenes/providers.ts +3 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
- package/src/modes/setup-wizard/scenes/theme.ts +13 -3
- package/src/modes/setup-wizard/scenes/types.ts +9 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/goals/guided-goal-system.md +24 -3
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/task.md +12 -2
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +255 -139
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +334 -0
- package/src/session/agent-session.ts +1910 -12213
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +212 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +263 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.test.ts +224 -1
- package/src/session/session-context.ts +47 -5
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +10 -51
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +943 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +92 -11
- package/src/slash-commands/helpers/usage-report.ts +25 -4
- package/src/system-prompt.ts +41 -17
- package/src/task/executor.ts +288 -70
- package/src/task/index.ts +148 -111
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +4 -1
- package/src/task/types.ts +16 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +26 -0
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/jobs.ts +67 -8
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +7 -1
- package/src/tools/report-tool-issue.ts +79 -28
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +67 -3
- package/src/tts/streaming-player.ts +62 -4
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/firecrawl.ts +46 -13
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +8 -1
package/src/task/executor.ts
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import type { AgentEvent, AgentIdentity, AgentTelemetryConfig } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import type { AgentEvent, AgentIdentity, AgentMessage, AgentTelemetryConfig } from "@oh-my-pi/pi-agent-core";
|
|
9
9
|
import { recordHandoff, resolveTelemetry } from "@oh-my-pi/pi-agent-core";
|
|
10
10
|
import type { Api, Model, ServiceTierByFamily, Usage } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import { logger, popLoopPhase, prompt, pushLoopPhase, untilAborted } from "@oh-my-pi/pi-utils";
|
|
12
|
+
import { AsyncJobManager } from "../async";
|
|
12
13
|
import type { Rule } from "../capability/rule";
|
|
13
14
|
import { ModelRegistry } from "../config/model-registry";
|
|
14
15
|
import {
|
|
@@ -32,13 +33,15 @@ import type { HindsightSessionState } from "../hindsight/state";
|
|
|
32
33
|
import type { LocalProtocolOptions } from "../internal-urls";
|
|
33
34
|
import type { MCPManager } from "../mcp/manager";
|
|
34
35
|
import type { MnemopiSessionState } from "../mnemopi/state";
|
|
36
|
+
import subagentAsyncPendingTemplate from "../prompts/system/subagent-async-pending.md" with { type: "text" };
|
|
35
37
|
import subagentSystemPromptTemplate from "../prompts/system/subagent-system-prompt.md" with { type: "text" };
|
|
36
38
|
import submitReminderTemplate from "../prompts/system/subagent-yield-reminder.md" with { type: "text" };
|
|
37
|
-
import { AgentLifecycleManager } from "../registry/agent-lifecycle";
|
|
39
|
+
import { AgentLifecycleManager, type AgentReviver } from "../registry/agent-lifecycle";
|
|
38
40
|
import { AgentRegistry } from "../registry/agent-registry";
|
|
39
41
|
import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage } from "../sdk";
|
|
40
42
|
import type { AgentSession, AgentSessionEvent, Prewalk } from "../session/agent-session";
|
|
41
43
|
import type { ArtifactManager } from "../session/artifacts";
|
|
44
|
+
import { ASYNC_RESULT_MESSAGE_TYPE } from "../session/async-job-delivery";
|
|
42
45
|
import type { AuthStorage } from "../session/auth-storage";
|
|
43
46
|
import { SKILL_PROMPT_MESSAGE_TYPE, USER_INTERRUPT_LABEL } from "../session/messages";
|
|
44
47
|
import { SessionManager } from "../session/session-manager";
|
|
@@ -287,6 +290,8 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
287
290
|
/** Options for subagent execution */
|
|
288
291
|
export interface ExecutorOptions {
|
|
289
292
|
cwd: string;
|
|
293
|
+
/** Additional workspace directories to seed on the subagent session (multi-root). */
|
|
294
|
+
additionalDirectories?: string[];
|
|
290
295
|
worktree?: string;
|
|
291
296
|
agent: AgentDefinition;
|
|
292
297
|
task: string;
|
|
@@ -823,8 +828,11 @@ export function createSubagentSettings(
|
|
|
823
828
|
snapshot["tier.google"] = subagentTiers.google ?? "none";
|
|
824
829
|
return Settings.isolated({
|
|
825
830
|
...snapshot,
|
|
826
|
-
|
|
827
|
-
|
|
831
|
+
// Async jobs and bash auto-backgrounding are inherited from the parent:
|
|
832
|
+
// background jobs are owner-routed to the subagent's own session, and
|
|
833
|
+
// the run driver's quiescence barrier + teardown reap guarantee no
|
|
834
|
+
// owner job outlives the run, so worktree capture/cleanup stays
|
|
835
|
+
// race-free (previously both were force-disabled here).
|
|
828
836
|
|
|
829
837
|
// Subagents run headless — there is no UI to confirm prompts against, so
|
|
830
838
|
// the parent task approval is the authorization boundary. Use yolo mode
|
|
@@ -882,6 +890,20 @@ interface SubagentRunMonitor {
|
|
|
882
890
|
budgetStopRequested(): boolean;
|
|
883
891
|
/** Resolves when the budget-stop session abort has settled (immediately when no stop fired). */
|
|
884
892
|
waitForBudgetStop(): Promise<void>;
|
|
893
|
+
/**
|
|
894
|
+
* True when a recorded yield was invalidated by a later async-result
|
|
895
|
+
* injection and no fresh yield has landed since: the yield payload
|
|
896
|
+
* predates background job outcomes the model was shown.
|
|
897
|
+
*/
|
|
898
|
+
yieldInvalidatedByAsync(): boolean;
|
|
899
|
+
/**
|
|
900
|
+
* True once a terminal yield with pending owner async work stopped the
|
|
901
|
+
* free-running turn (recoverable, like a budget stop) instead of
|
|
902
|
+
* terminating the run. Cleared when {@link waitForYieldTurnStop} settles.
|
|
903
|
+
*/
|
|
904
|
+
yieldTurnStopRequested(): boolean;
|
|
905
|
+
/** Resolves when the yield turn-stop session abort has settled (immediately when none fired). */
|
|
906
|
+
waitForYieldTurnStop(): Promise<void>;
|
|
885
907
|
/** The abort kind for this run, when an abort was requested. */
|
|
886
908
|
abortKind(): AbortReason | undefined;
|
|
887
909
|
terminalError(): string | undefined;
|
|
@@ -910,6 +932,15 @@ interface SubagentRunMonitor {
|
|
|
910
932
|
finish(): void;
|
|
911
933
|
}
|
|
912
934
|
|
|
935
|
+
/**
|
|
936
|
+
* True when `message` is the session-injected async-result follow-up
|
|
937
|
+
* ({@link ASYNC_RESULT_MESSAGE_TYPE}): the transcript-ordered signal that a
|
|
938
|
+
* background job outcome landed after whatever the model said before it.
|
|
939
|
+
*/
|
|
940
|
+
function isAsyncResultInjection(message: AgentMessage | undefined): boolean {
|
|
941
|
+
return message?.role === "custom" && message.customType === ASYNC_RESULT_MESSAGE_TYPE;
|
|
942
|
+
}
|
|
943
|
+
|
|
913
944
|
function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
914
945
|
const {
|
|
915
946
|
index,
|
|
@@ -961,6 +992,9 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
961
992
|
let activeSession: AgentSession | null = null;
|
|
962
993
|
let yieldCalled = false;
|
|
963
994
|
let yieldCallPending = false;
|
|
995
|
+
let yieldInvalidatedByAsync = false;
|
|
996
|
+
let yieldTurnStopRequested = false;
|
|
997
|
+
let yieldTurnStopPromise: Promise<void> | null = null;
|
|
964
998
|
|
|
965
999
|
// Accumulate usage incrementally from message_end events (no memory for streaming events)
|
|
966
1000
|
const accumulatedUsage: Usage = {
|
|
@@ -1039,6 +1073,29 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1039
1073
|
terminalError ??= message;
|
|
1040
1074
|
requestAbort("terminate");
|
|
1041
1075
|
};
|
|
1076
|
+
// Yield turn-stop: a terminal yield recorded while owner async work is
|
|
1077
|
+
// still pending is a scheduling pause, not run completion. Stop the
|
|
1078
|
+
// free-running turn exactly like a budget stop (session abort, monitor
|
|
1079
|
+
// signal untouched) so driveSessionToYield's quiescence barrier can settle
|
|
1080
|
+
// the jobs, fold their results in, and demand a fresh yield. Terminating
|
|
1081
|
+
// here instead would abort the run signal and make the barrier
|
|
1082
|
+
// unreachable, completing the run with a payload that predates the job
|
|
1083
|
+
// outcomes.
|
|
1084
|
+
const requestYieldTurnStop = () => {
|
|
1085
|
+
if (yieldTurnStopRequested || abortSent || resolved) return;
|
|
1086
|
+
yieldTurnStopRequested = true;
|
|
1087
|
+
const session = activeSession;
|
|
1088
|
+
yieldTurnStopPromise = session
|
|
1089
|
+
? session.abort().catch(error => {
|
|
1090
|
+
logger.debug("Subagent yield turn-stop abort failed", {
|
|
1091
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1092
|
+
});
|
|
1093
|
+
})
|
|
1094
|
+
: Promise.resolve();
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
/** Owner async work that can still re-wake the run (quiescence barrier predicate). */
|
|
1098
|
+
const sessionHasPendingAsyncWork = (): boolean => activeSession?.hasPendingAsyncWork?.() ?? false;
|
|
1042
1099
|
|
|
1043
1100
|
// Handle abort signal
|
|
1044
1101
|
if (signal) {
|
|
@@ -1243,6 +1300,7 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1243
1300
|
if (toolName === "yield") {
|
|
1244
1301
|
yieldCalled = true;
|
|
1245
1302
|
yieldCallPending = false;
|
|
1303
|
+
yieldInvalidatedByAsync = false;
|
|
1246
1304
|
}
|
|
1247
1305
|
};
|
|
1248
1306
|
|
|
@@ -1256,6 +1314,16 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1256
1314
|
if (event.message?.role === "assistant") {
|
|
1257
1315
|
resetRecentOutput();
|
|
1258
1316
|
}
|
|
1317
|
+
// An async-result follow-up injected after a recorded yield
|
|
1318
|
+
// supersedes that yield: its payload predates the job outcome the
|
|
1319
|
+
// model is now being shown. Un-latch so the quiescence barrier's
|
|
1320
|
+
// reminder ladder demands a fresh yield. Guarded on the run signal:
|
|
1321
|
+
// once the run is completing, late injections must not destabilize
|
|
1322
|
+
// the settled classification.
|
|
1323
|
+
if (yieldCalled && !abortSignal.aborted && isAsyncResultInjection(event.message)) {
|
|
1324
|
+
yieldCalled = false;
|
|
1325
|
+
yieldInvalidatedByAsync = true;
|
|
1326
|
+
}
|
|
1259
1327
|
break;
|
|
1260
1328
|
|
|
1261
1329
|
case "tool_execution_start": {
|
|
@@ -1339,7 +1407,14 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1339
1407
|
isError: event.isError,
|
|
1340
1408
|
})
|
|
1341
1409
|
) {
|
|
1342
|
-
|
|
1410
|
+
if (event.toolName === "yield" && sessionHasPendingAsyncWork()) {
|
|
1411
|
+
// Terminal yield with owner jobs still pending: park the
|
|
1412
|
+
// run behind the quiescence barrier instead of completing
|
|
1413
|
+
// it (see requestYieldTurnStop).
|
|
1414
|
+
requestYieldTurnStop();
|
|
1415
|
+
} else {
|
|
1416
|
+
requestAbort("terminate");
|
|
1417
|
+
}
|
|
1343
1418
|
}
|
|
1344
1419
|
}
|
|
1345
1420
|
if (event.toolName === "yield") {
|
|
@@ -1578,11 +1653,13 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1578
1653
|
}
|
|
1579
1654
|
if (event.type === "retry_fallback_applied") {
|
|
1580
1655
|
progress.resolvedModel = event.to;
|
|
1656
|
+
progress.resolvedModelIsFallback = true;
|
|
1581
1657
|
scheduleProgress(true);
|
|
1582
1658
|
return;
|
|
1583
1659
|
}
|
|
1584
1660
|
if (event.type === "retry_fallback_succeeded") {
|
|
1585
1661
|
progress.resolvedModel = event.model;
|
|
1662
|
+
progress.resolvedModelIsFallback = true;
|
|
1586
1663
|
scheduleProgress(true);
|
|
1587
1664
|
return;
|
|
1588
1665
|
}
|
|
@@ -1621,6 +1698,25 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1621
1698
|
abortReason === "signal" || runtimeLimitExceeded || budgetLimitExceeded || budgetStopRequested,
|
|
1622
1699
|
budgetStopRequested: () => budgetStopRequested,
|
|
1623
1700
|
waitForBudgetStop: () => budgetStopAbortPromise ?? Promise.resolve(),
|
|
1701
|
+
yieldInvalidatedByAsync: () => yieldInvalidatedByAsync,
|
|
1702
|
+
yieldTurnStopRequested: () => yieldTurnStopRequested,
|
|
1703
|
+
waitForYieldTurnStop: async () => {
|
|
1704
|
+
const pending = yieldTurnStopPromise;
|
|
1705
|
+
if (!pending) {
|
|
1706
|
+
yieldTurnStopRequested = false;
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1709
|
+
try {
|
|
1710
|
+
await pending;
|
|
1711
|
+
} finally {
|
|
1712
|
+
// Clear only after the abort settled so the idempotence gate in
|
|
1713
|
+
// requestYieldTurnStop stays closed while it is in flight.
|
|
1714
|
+
if (yieldTurnStopPromise === pending) {
|
|
1715
|
+
yieldTurnStopPromise = null;
|
|
1716
|
+
yieldTurnStopRequested = false;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
},
|
|
1624
1720
|
// A soft stop that never escalated still identifies as a budget abort so
|
|
1625
1721
|
// the lifecycle can park the agent as resumable instead of killing it.
|
|
1626
1722
|
abortKind: () => abortReason ?? (budgetStopRequested ? "budget" : undefined),
|
|
@@ -1719,62 +1815,135 @@ async function driveSessionToYield(
|
|
|
1719
1815
|
await awaitAbortable(session.prompt(task, { attribution: "agent" }));
|
|
1720
1816
|
await awaitAbortable(session.waitForIdle());
|
|
1721
1817
|
} catch (err) {
|
|
1722
|
-
// A budget stop
|
|
1723
|
-
//
|
|
1724
|
-
//
|
|
1725
|
-
//
|
|
1726
|
-
|
|
1818
|
+
// A budget stop or a yield turn-stop (terminal yield parked behind
|
|
1819
|
+
// the async quiescence barrier) cancels the free-running turn by
|
|
1820
|
+
// aborting the session, which can surface here as a rejected
|
|
1821
|
+
// prompt. Swallow it and drive the barrier/forced final yield
|
|
1822
|
+
// below; real caller/timeout aborts (monitor signal) and genuine
|
|
1823
|
+
// failures keep the old path.
|
|
1824
|
+
const recoverableStop = monitor.budgetStopRequested() || monitor.yieldTurnStopRequested();
|
|
1825
|
+
if (!recoverableStop || abortSignal.aborted) throw err;
|
|
1727
1826
|
}
|
|
1728
1827
|
|
|
1729
1828
|
const reminderToolChoice = buildNamedToolChoice("yield", session.model);
|
|
1730
1829
|
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1830
|
+
const runYieldLadder = async (): Promise<void> => {
|
|
1831
|
+
let retryCount = 0;
|
|
1832
|
+
while (!monitor.yieldCalled() && retryCount < MAX_YIELD_RETRIES && !abortSignal.aborted) {
|
|
1833
|
+
// A budget stop collapses the reminder ladder to a single forced
|
|
1834
|
+
// final yield: wait for the stop's session abort to settle, then
|
|
1835
|
+
// prompt once with the wrap-up reminder + named tool choice.
|
|
1836
|
+
const budgetStop = monitor.budgetStopRequested();
|
|
1837
|
+
if (budgetStop) {
|
|
1838
|
+
retryCount = MAX_YIELD_RETRIES - 1;
|
|
1839
|
+
await monitor.waitForBudgetStop();
|
|
1840
|
+
if (monitor.yieldCalled() || abortSignal.aborted) break;
|
|
1841
|
+
}
|
|
1842
|
+
// Skip reminders when the model returned a terminal error (e.g.
|
|
1843
|
+
// rate-limit cap hit, auth failure). Re-prompting would just
|
|
1844
|
+
// hit the same wall, multiplying the failure noise without
|
|
1845
|
+
// any chance of producing a yield.
|
|
1846
|
+
const lastBeforeReminder = session.getLastAssistantMessage();
|
|
1847
|
+
if (lastBeforeReminder?.stopReason === "error") break;
|
|
1848
|
+
try {
|
|
1849
|
+
retryCount++;
|
|
1850
|
+
const reminder = prompt.render(submitReminderTemplate, {
|
|
1851
|
+
retryCount,
|
|
1852
|
+
maxRetries: MAX_YIELD_RETRIES,
|
|
1853
|
+
budgetStop,
|
|
1854
|
+
});
|
|
1855
|
+
|
|
1856
|
+
const isFinalRetry = retryCount >= MAX_YIELD_RETRIES;
|
|
1857
|
+
await awaitAbortable(
|
|
1858
|
+
session.prompt(reminder, {
|
|
1859
|
+
attribution: "agent",
|
|
1860
|
+
synthetic: true,
|
|
1861
|
+
...(isFinalRetry && reminderToolChoice ? { toolChoice: reminderToolChoice } : {}),
|
|
1862
|
+
}),
|
|
1863
|
+
);
|
|
1864
|
+
await awaitAbortable(session.waitForIdle());
|
|
1865
|
+
} catch (err) {
|
|
1866
|
+
if (abortSignal.aborted || err instanceof ToolAbortError) {
|
|
1867
|
+
// Benign control-flow exit — user cancel (^C) or compaction aborting
|
|
1868
|
+
// pending operations both surface here as ToolAbortError. The outer
|
|
1869
|
+
// catch and finally already mark the run aborted; logging at ERROR
|
|
1870
|
+
// would spam operator dashboards with non-failures.
|
|
1871
|
+
logger.debug("Subagent prompt aborted");
|
|
1872
|
+
} else {
|
|
1873
|
+
logger.error("Subagent prompt failed", {
|
|
1874
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1741
1878
|
}
|
|
1742
|
-
|
|
1743
|
-
// rate-limit cap hit, auth failure). Re-prompting would just
|
|
1744
|
-
// hit the same wall, multiplying the failure noise without
|
|
1745
|
-
// any chance of producing a yield.
|
|
1746
|
-
const lastBeforeReminder = session.getLastAssistantMessage();
|
|
1747
|
-
if (lastBeforeReminder?.stopReason === "error") break;
|
|
1748
|
-
try {
|
|
1749
|
-
retryCount++;
|
|
1750
|
-
const reminder = prompt.render(submitReminderTemplate, {
|
|
1751
|
-
retryCount,
|
|
1752
|
-
maxRetries: MAX_YIELD_RETRIES,
|
|
1753
|
-
budgetStop,
|
|
1754
|
-
});
|
|
1879
|
+
};
|
|
1755
1880
|
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1881
|
+
// Yield ladder + quiescence barrier (structured concurrency), one
|
|
1882
|
+
// loop: each iteration first demands a yield — initially, and again
|
|
1883
|
+
// whenever an async-result delivery un-latched the previous one
|
|
1884
|
+
// (including during the notice turn) — then either completes on
|
|
1885
|
+
// quiescence or settles one generation of owner async work.
|
|
1886
|
+
//
|
|
1887
|
+
// A final yield with owner background jobs still running or
|
|
1888
|
+
// undelivered is a scheduling pause, not run completion — the monitor
|
|
1889
|
+
// parks such a yield with a recoverable turn-stop instead of
|
|
1890
|
+
// terminating the run. Jobs are settled and their results folded into
|
|
1891
|
+
// the run as async-result follow-up turns; each delivered result
|
|
1892
|
+
// supersedes the yield it postdates, so the reminder ladder re-runs
|
|
1893
|
+
// to demand a fresh yield that accounts for it. Only a yield with no
|
|
1894
|
+
// pending owner work left is terminal — the isolation runner captures
|
|
1895
|
+
// and destroys the worktree right after this run resolves, so no
|
|
1896
|
+
// owner job that could still re-wake the session may outlive it.
|
|
1897
|
+
// Suppressed (acknowledged / hub-watched) jobs never re-wake the run
|
|
1898
|
+
// and are reaped at teardown.
|
|
1899
|
+
//
|
|
1900
|
+
// Before blocking on running jobs, tell the model ONCE what it is
|
|
1901
|
+
// waiting on so it can `hub` wait/cancel instead of sitting silent
|
|
1902
|
+
// until the jobs (or the runtime limit) expire. Runs that never yield
|
|
1903
|
+
// (ladder exhausted / terminal model error) skip the barrier — more
|
|
1904
|
+
// injected turns just multiply the failure noise; the teardown reap
|
|
1905
|
+
// still cancels and awaits their jobs before worktree capture.
|
|
1906
|
+
let asyncPendingNoticeSent = false;
|
|
1907
|
+
while (!abortSignal.aborted) {
|
|
1908
|
+
if (!monitor.yieldCalled()) {
|
|
1909
|
+
await runYieldLadder();
|
|
1910
|
+
// Ladder exhausted / terminal model error: classified below
|
|
1911
|
+
// (missing yield, or stale yield when one was invalidated).
|
|
1912
|
+
if (!monitor.yieldCalled()) break;
|
|
1913
|
+
}
|
|
1914
|
+
// Let the parked yield's turn-stop session abort settle before
|
|
1915
|
+
// prompting again (mirrors waitForBudgetStop).
|
|
1916
|
+
await awaitAbortable(monitor.waitForYieldTurnStop());
|
|
1917
|
+
if (!session.hasPendingAsyncWork()) break;
|
|
1918
|
+
if (!asyncPendingNoticeSent) {
|
|
1919
|
+
asyncPendingNoticeSent = true;
|
|
1920
|
+
const running = session.getAsyncJobSnapshot()?.running ?? [];
|
|
1921
|
+
if (running.length > 0) {
|
|
1922
|
+
const jobs = running.map(job => `${job.id}${job.label ? ` (${job.label})` : ""}`).join(", ");
|
|
1923
|
+
const notice = prompt.render(subagentAsyncPendingTemplate, {
|
|
1924
|
+
count: running.length,
|
|
1925
|
+
multiple: running.length > 1,
|
|
1926
|
+
jobs,
|
|
1775
1927
|
});
|
|
1928
|
+
try {
|
|
1929
|
+
await awaitAbortable(session.prompt(notice, { attribution: "agent", synthetic: true }));
|
|
1930
|
+
await awaitAbortable(session.waitForIdle());
|
|
1931
|
+
} catch (err) {
|
|
1932
|
+
if (abortSignal.aborted || err instanceof ToolAbortError) throw err;
|
|
1933
|
+
// A failed notice turn must not kill the run — fall through
|
|
1934
|
+
// to the passive settle below.
|
|
1935
|
+
logger.warn("Subagent async-pending notice failed", {
|
|
1936
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
// Re-evaluate: the notice turn may have cancelled, watched, or
|
|
1940
|
+
// absorbed the jobs — or already re-yielded.
|
|
1941
|
+
continue;
|
|
1776
1942
|
}
|
|
1777
1943
|
}
|
|
1944
|
+
await awaitAbortable(session.settleAsyncWork());
|
|
1945
|
+
// Results delivered during the settle invalidated the recorded
|
|
1946
|
+
// yield: the next iteration's ladder demands a fresh one.
|
|
1778
1947
|
}
|
|
1779
1948
|
|
|
1780
1949
|
if (monitor.yieldCalled()) {
|
|
@@ -1813,6 +1982,18 @@ async function driveSessionToYield(
|
|
|
1813
1982
|
abortReasonText ??= monitor.resolveAbortReasonText();
|
|
1814
1983
|
exitCode = 1;
|
|
1815
1984
|
}
|
|
1985
|
+
|
|
1986
|
+
// A recorded yield that async-result deliveries superseded and the
|
|
1987
|
+
// model never refreshed is stale: fail the run instead of letting the
|
|
1988
|
+
// parent act on a payload that predates the background job outcomes
|
|
1989
|
+
// the model was shown. The stale payload still ships through
|
|
1990
|
+
// finalizeSubprocessOutput's failed-after-yield path (exit 1 + stderr,
|
|
1991
|
+
// output preserved as salvage).
|
|
1992
|
+
if (monitor.yieldInvalidatedByAsync() && !abortSignal.aborted) {
|
|
1993
|
+
exitCode = 1;
|
|
1994
|
+
error ??=
|
|
1995
|
+
"Background job results arrived after the subagent's last yield; it did not submit a refreshed yield covering them.";
|
|
1996
|
+
}
|
|
1816
1997
|
} catch (err) {
|
|
1817
1998
|
if (abortSignal.aborted && monitor.yieldCalled() && !monitor.runtimeLimitExceeded()) {
|
|
1818
1999
|
exitCode = 0;
|
|
@@ -1989,6 +2170,7 @@ async function finalizeRunResult(args: FinalizeRunArgs): Promise<SingleResult> {
|
|
|
1989
2170
|
contextWindow: progress.contextWindow,
|
|
1990
2171
|
modelOverride,
|
|
1991
2172
|
resolvedModel: progress.resolvedModel,
|
|
2173
|
+
resolvedModelIsFallback: progress.resolvedModelIsFallback,
|
|
1992
2174
|
error: exitCode !== 0 && stderr ? stderr : undefined,
|
|
1993
2175
|
aborted: wasAborted,
|
|
1994
2176
|
abortReason: finalAbortReason,
|
|
@@ -2016,9 +2198,11 @@ export async function finalizeSubagentLifecycle(args: {
|
|
|
2016
2198
|
keepAlive: boolean;
|
|
2017
2199
|
isolated: boolean;
|
|
2018
2200
|
agentIdleTtlMs: number;
|
|
2019
|
-
reviveSession:
|
|
2201
|
+
reviveSession: AgentReviver | null;
|
|
2020
2202
|
}): Promise<void> {
|
|
2021
2203
|
const registry = AgentRegistry.global();
|
|
2204
|
+
const ref = registry.get(args.id);
|
|
2205
|
+
const ownsRef = Boolean(ref && ref.session === args.session);
|
|
2022
2206
|
const disposeSession = async (): Promise<void> => {
|
|
2023
2207
|
try {
|
|
2024
2208
|
await untilAborted(AbortSignal.timeout(5000), () => args.session.dispose());
|
|
@@ -2033,7 +2217,7 @@ export async function finalizeSubagentLifecycle(args: {
|
|
|
2033
2217
|
const resumableAbort =
|
|
2034
2218
|
args.abortKind === "budget" && args.keepAlive && !args.isolated && args.reviveSession !== null;
|
|
2035
2219
|
if (args.aborted && !resumableAbort) {
|
|
2036
|
-
registry.setStatus(args.id, "aborted");
|
|
2220
|
+
if (ref && ownsRef) registry.setStatus(args.id, "aborted", ref);
|
|
2037
2221
|
await disposeSession();
|
|
2038
2222
|
return;
|
|
2039
2223
|
}
|
|
@@ -2041,7 +2225,7 @@ export async function finalizeSubagentLifecycle(args: {
|
|
|
2041
2225
|
if (!args.keepAlive) {
|
|
2042
2226
|
// One-shot helper: dispose and unregister. No IRC, no revival.
|
|
2043
2227
|
await disposeSession();
|
|
2044
|
-
registry.unregister(args.id);
|
|
2228
|
+
if (ref && ownsRef) registry.unregister(args.id, ref);
|
|
2045
2229
|
return;
|
|
2046
2230
|
}
|
|
2047
2231
|
|
|
@@ -2051,19 +2235,26 @@ export async function finalizeSubagentLifecycle(args: {
|
|
|
2051
2235
|
// transcript stays reachable (history://), but ensureLive will throw.
|
|
2052
2236
|
// Status must flip to "parked" before dispose so the sdk dispose
|
|
2053
2237
|
// wrapper skips unregister.
|
|
2054
|
-
registry.setStatus(args.id, "parked");
|
|
2238
|
+
if (ref && ownsRef) registry.setStatus(args.id, "parked", ref);
|
|
2055
2239
|
await disposeSession();
|
|
2056
|
-
registry.detachSession(args.id);
|
|
2240
|
+
if (ref && ownsRef) registry.detachSession(args.id, ref);
|
|
2057
2241
|
return;
|
|
2058
2242
|
}
|
|
2059
2243
|
|
|
2060
2244
|
// Keep-alive: finished and failed subagents both stay interrogable.
|
|
2061
2245
|
// The lifecycle manager owns idle-TTL parking + revival from here on.
|
|
2062
|
-
registry.setStatus(args.id, "idle")
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2246
|
+
if (!ref || !ownsRef || !registry.setStatus(args.id, "idle", ref)) {
|
|
2247
|
+
await disposeSession();
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
AgentLifecycleManager.global().adopt(
|
|
2251
|
+
args.id,
|
|
2252
|
+
{
|
|
2253
|
+
idleTtlMs: args.agentIdleTtlMs,
|
|
2254
|
+
revive: args.reviveSession ?? undefined,
|
|
2255
|
+
},
|
|
2256
|
+
ref,
|
|
2257
|
+
);
|
|
2067
2258
|
}
|
|
2068
2259
|
|
|
2069
2260
|
/** Options for {@link runSubagentFollowUpTurn}. */
|
|
@@ -2234,7 +2425,11 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2234
2425
|
const settings = options.settings ?? Settings.isolated();
|
|
2235
2426
|
const subagentSettings = createSubagentSettings(
|
|
2236
2427
|
settings,
|
|
2237
|
-
|
|
2428
|
+
{
|
|
2429
|
+
...(agent.readSummarize === false ? { "read.summarize.enabled": false } : undefined),
|
|
2430
|
+
// Isolated runs must not expose roots outside the worktree.
|
|
2431
|
+
...(worktree !== undefined ? { "workspace.additionalDirectories": [] } : undefined),
|
|
2432
|
+
},
|
|
2238
2433
|
options.parentServiceTier,
|
|
2239
2434
|
);
|
|
2240
2435
|
const maxRecursionDepth = settings.get("task.maxRecursionDepth") ?? 2;
|
|
@@ -2318,7 +2513,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2318
2513
|
});
|
|
2319
2514
|
const progress = monitor.progress;
|
|
2320
2515
|
let unsubscribe: (() => void) | null = null;
|
|
2321
|
-
let reviveSession:
|
|
2516
|
+
let reviveSession: AgentReviver | null = null;
|
|
2322
2517
|
// Adopted (kept-alive) subagents flip registry status from session events on
|
|
2323
2518
|
// later turns: revive/wake → running, turn drained → idle. The subscription
|
|
2324
2519
|
// intentionally survives this run; a disposed session emits nothing, so it
|
|
@@ -2326,9 +2521,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2326
2521
|
const installRegistryStatusSync = (target: AgentSession): void => {
|
|
2327
2522
|
target.subscribe(event => {
|
|
2328
2523
|
if (event.type === "agent_start") {
|
|
2329
|
-
AgentRegistry.global().setStatus(id, "running");
|
|
2524
|
+
AgentRegistry.global().setStatus(id, "running", target);
|
|
2330
2525
|
} else if (event.type === "agent_end") {
|
|
2331
|
-
AgentRegistry.global().setStatus(id, "idle");
|
|
2526
|
+
AgentRegistry.global().setStatus(id, "idle", target);
|
|
2332
2527
|
}
|
|
2333
2528
|
});
|
|
2334
2529
|
};
|
|
@@ -2551,8 +2746,12 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2551
2746
|
// the same JSONL file re-invokes createAgentSession with the exact options
|
|
2552
2747
|
// of the original run (same agent id, tools, model, system prompt,
|
|
2553
2748
|
// artifacts dir) — only the SessionManager differs.
|
|
2554
|
-
const buildSubagentSessionOptions = (
|
|
2749
|
+
const buildSubagentSessionOptions = (
|
|
2750
|
+
sessionManagerForRun: SessionManager,
|
|
2751
|
+
expectedAgentRef: CreateAgentSessionOptions["expectedAgentRef"],
|
|
2752
|
+
): CreateAgentSessionOptions => ({
|
|
2555
2753
|
cwd: worktree ?? cwd,
|
|
2754
|
+
additionalDirectories: worktree !== undefined ? undefined : options.additionalDirectories,
|
|
2556
2755
|
authStorage,
|
|
2557
2756
|
modelRegistry,
|
|
2558
2757
|
settings: subagentSettings,
|
|
@@ -2604,6 +2803,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2604
2803
|
parentAgentId: options.parentAgentId,
|
|
2605
2804
|
agentId: id,
|
|
2606
2805
|
agentDisplayName: agent.name,
|
|
2806
|
+
expectedAgentRef,
|
|
2607
2807
|
enableLsp: lspEnabled,
|
|
2608
2808
|
enableIrc: options.enableIrc,
|
|
2609
2809
|
skipPythonPreflight,
|
|
@@ -2618,7 +2818,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2618
2818
|
},
|
|
2619
2819
|
});
|
|
2620
2820
|
|
|
2621
|
-
const sessionPromise = createAgentSession(buildSubagentSessionOptions(sessionManager));
|
|
2821
|
+
const sessionPromise = createAgentSession(buildSubagentSessionOptions(sessionManager, null));
|
|
2622
2822
|
let session: AgentSession;
|
|
2623
2823
|
try {
|
|
2624
2824
|
({ session } = await awaitAbortable(sessionPromise));
|
|
@@ -2638,14 +2838,16 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2638
2838
|
// the single-writer lock cleanly and restores the full message history
|
|
2639
2839
|
// (createAgentSession → agent.replaceMessages). Isolated runs are not
|
|
2640
2840
|
// resumable (worktree is merged + cleaned) and never get a reviver.
|
|
2641
|
-
reviveSession = async
|
|
2841
|
+
reviveSession = async expectedAgentRef => {
|
|
2642
2842
|
const reopened = await SessionManager.open(sessionFile, undefined, undefined, {
|
|
2643
2843
|
suppressBreadcrumb: true,
|
|
2644
2844
|
});
|
|
2645
2845
|
if (options.parentArtifactManager) {
|
|
2646
2846
|
reopened.adoptArtifactManager(options.parentArtifactManager);
|
|
2647
2847
|
}
|
|
2648
|
-
const { session: revived } = await createAgentSession(
|
|
2848
|
+
const { session: revived } = await createAgentSession(
|
|
2849
|
+
buildSubagentSessionOptions(reopened, expectedAgentRef),
|
|
2850
|
+
);
|
|
2649
2851
|
installRegistryStatusSync(revived);
|
|
2650
2852
|
return revived;
|
|
2651
2853
|
};
|
|
@@ -2736,6 +2938,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2736
2938
|
setModel: model => runExtensionSetModel(session, model),
|
|
2737
2939
|
getThinkingLevel: () => session.thinkingLevel,
|
|
2738
2940
|
setThinkingLevel: level => session.setThinkingLevel(level),
|
|
2941
|
+
getServiceTiers: () => session.serviceTierByFamily,
|
|
2942
|
+
setServiceTier: (family, tier) => session.setServiceTierFamily(family, tier),
|
|
2739
2943
|
getSessionName: () => session.sessionManager.getSessionName(),
|
|
2740
2944
|
setSessionName: async name => {
|
|
2741
2945
|
await session.sessionManager.setSessionName(name, "user");
|
|
@@ -2827,6 +3031,20 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2827
3031
|
reviveSession,
|
|
2828
3032
|
});
|
|
2829
3033
|
}
|
|
3034
|
+
// Structured-concurrency reap: cancel and await ALL surviving owner
|
|
3035
|
+
// jobs (abort paths; suppressed/watched jobs the model left behind)
|
|
3036
|
+
// so isolation capture/cleanup never races a live process writing
|
|
3037
|
+
// into the worktree. This never proceeds while an owner process is
|
|
3038
|
+
// live: cancellation SIGKILL-escalates, so settlement is expected
|
|
3039
|
+
// within one interval — an unkillable process blocks here visibly
|
|
3040
|
+
// (with periodic warnings) instead of silently racing teardown.
|
|
3041
|
+
const jobManager = AsyncJobManager.instance();
|
|
3042
|
+
if (jobManager) {
|
|
3043
|
+
jobManager.cancelAll({ ownerId: id });
|
|
3044
|
+
while (!(await jobManager.waitForOwnerJobs(id, { timeoutMs: 10_000 }))) {
|
|
3045
|
+
logger.warn("Subagent async jobs still settling; delaying teardown until process exit", { id });
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
2830
3048
|
}
|
|
2831
3049
|
|
|
2832
3050
|
// Launch-latency breakdown (subagent invocation → first chat dispatch).
|