@oh-my-pi/pi-coding-agent 17.0.9 → 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 +54 -0
- package/README.md +1 -1
- package/dist/cli.js +4659 -4261
- 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/model-registry.d.ts +3 -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 +316 -47
- 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/plugins/legacy-pi-compat.d.ts +13 -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 +16 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -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/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/settings-defs.d.ts +7 -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/interactive-mode.d.ts +5 -0
- 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 +118 -755
- 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-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/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- 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 +5 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- 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/types.d.ts +94 -0
- package/package.json +12 -12
- package/scripts/generate-share-viewer.ts +4 -6
- 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/usage-cli.ts +10 -1
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/model-registry.ts +10 -2
- 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 +209 -51
- 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/plugins/legacy-pi-compat.ts +19 -1
- 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/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 +29 -9
- package/src/lsp/index.ts +19 -4
- package/src/main.ts +8 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- 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 +1 -1
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/settings-defs.ts +23 -1
- package/src/modes/components/settings-selector.ts +169 -1
- 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.ts +11 -2
- package/src/modes/controllers/command-controller.ts +56 -15
- 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 +10 -1
- 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 +157 -38
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
- 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/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/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 +210 -136
- 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 +1892 -12226
- 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.ts +6 -3
- 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 +0 -46
- 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 +22 -3
- package/src/system-prompt.ts +41 -17
- package/src/task/executor.ts +285 -70
- package/src/task/index.ts +98 -105
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +1 -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/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +9 -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/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/xai.ts +7 -1
- package/src/web/search/types.ts +3 -0
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") {
|
|
@@ -1623,6 +1698,25 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1623
1698
|
abortReason === "signal" || runtimeLimitExceeded || budgetLimitExceeded || budgetStopRequested,
|
|
1624
1699
|
budgetStopRequested: () => budgetStopRequested,
|
|
1625
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
|
+
},
|
|
1626
1720
|
// A soft stop that never escalated still identifies as a budget abort so
|
|
1627
1721
|
// the lifecycle can park the agent as resumable instead of killing it.
|
|
1628
1722
|
abortKind: () => abortReason ?? (budgetStopRequested ? "budget" : undefined),
|
|
@@ -1721,62 +1815,135 @@ async function driveSessionToYield(
|
|
|
1721
1815
|
await awaitAbortable(session.prompt(task, { attribution: "agent" }));
|
|
1722
1816
|
await awaitAbortable(session.waitForIdle());
|
|
1723
1817
|
} catch (err) {
|
|
1724
|
-
// A budget stop
|
|
1725
|
-
//
|
|
1726
|
-
//
|
|
1727
|
-
//
|
|
1728
|
-
|
|
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;
|
|
1729
1826
|
}
|
|
1730
1827
|
|
|
1731
1828
|
const reminderToolChoice = buildNamedToolChoice("yield", session.model);
|
|
1732
1829
|
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
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
|
+
}
|
|
1743
1878
|
}
|
|
1744
|
-
|
|
1745
|
-
// rate-limit cap hit, auth failure). Re-prompting would just
|
|
1746
|
-
// hit the same wall, multiplying the failure noise without
|
|
1747
|
-
// any chance of producing a yield.
|
|
1748
|
-
const lastBeforeReminder = session.getLastAssistantMessage();
|
|
1749
|
-
if (lastBeforeReminder?.stopReason === "error") break;
|
|
1750
|
-
try {
|
|
1751
|
-
retryCount++;
|
|
1752
|
-
const reminder = prompt.render(submitReminderTemplate, {
|
|
1753
|
-
retryCount,
|
|
1754
|
-
maxRetries: MAX_YIELD_RETRIES,
|
|
1755
|
-
budgetStop,
|
|
1756
|
-
});
|
|
1879
|
+
};
|
|
1757
1880
|
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
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,
|
|
1777
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;
|
|
1778
1942
|
}
|
|
1779
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.
|
|
1780
1947
|
}
|
|
1781
1948
|
|
|
1782
1949
|
if (monitor.yieldCalled()) {
|
|
@@ -1815,6 +1982,18 @@ async function driveSessionToYield(
|
|
|
1815
1982
|
abortReasonText ??= monitor.resolveAbortReasonText();
|
|
1816
1983
|
exitCode = 1;
|
|
1817
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
|
+
}
|
|
1818
1997
|
} catch (err) {
|
|
1819
1998
|
if (abortSignal.aborted && monitor.yieldCalled() && !monitor.runtimeLimitExceeded()) {
|
|
1820
1999
|
exitCode = 0;
|
|
@@ -2019,9 +2198,11 @@ export async function finalizeSubagentLifecycle(args: {
|
|
|
2019
2198
|
keepAlive: boolean;
|
|
2020
2199
|
isolated: boolean;
|
|
2021
2200
|
agentIdleTtlMs: number;
|
|
2022
|
-
reviveSession:
|
|
2201
|
+
reviveSession: AgentReviver | null;
|
|
2023
2202
|
}): Promise<void> {
|
|
2024
2203
|
const registry = AgentRegistry.global();
|
|
2204
|
+
const ref = registry.get(args.id);
|
|
2205
|
+
const ownsRef = Boolean(ref && ref.session === args.session);
|
|
2025
2206
|
const disposeSession = async (): Promise<void> => {
|
|
2026
2207
|
try {
|
|
2027
2208
|
await untilAborted(AbortSignal.timeout(5000), () => args.session.dispose());
|
|
@@ -2036,7 +2217,7 @@ export async function finalizeSubagentLifecycle(args: {
|
|
|
2036
2217
|
const resumableAbort =
|
|
2037
2218
|
args.abortKind === "budget" && args.keepAlive && !args.isolated && args.reviveSession !== null;
|
|
2038
2219
|
if (args.aborted && !resumableAbort) {
|
|
2039
|
-
registry.setStatus(args.id, "aborted");
|
|
2220
|
+
if (ref && ownsRef) registry.setStatus(args.id, "aborted", ref);
|
|
2040
2221
|
await disposeSession();
|
|
2041
2222
|
return;
|
|
2042
2223
|
}
|
|
@@ -2044,7 +2225,7 @@ export async function finalizeSubagentLifecycle(args: {
|
|
|
2044
2225
|
if (!args.keepAlive) {
|
|
2045
2226
|
// One-shot helper: dispose and unregister. No IRC, no revival.
|
|
2046
2227
|
await disposeSession();
|
|
2047
|
-
registry.unregister(args.id);
|
|
2228
|
+
if (ref && ownsRef) registry.unregister(args.id, ref);
|
|
2048
2229
|
return;
|
|
2049
2230
|
}
|
|
2050
2231
|
|
|
@@ -2054,19 +2235,26 @@ export async function finalizeSubagentLifecycle(args: {
|
|
|
2054
2235
|
// transcript stays reachable (history://), but ensureLive will throw.
|
|
2055
2236
|
// Status must flip to "parked" before dispose so the sdk dispose
|
|
2056
2237
|
// wrapper skips unregister.
|
|
2057
|
-
registry.setStatus(args.id, "parked");
|
|
2238
|
+
if (ref && ownsRef) registry.setStatus(args.id, "parked", ref);
|
|
2058
2239
|
await disposeSession();
|
|
2059
|
-
registry.detachSession(args.id);
|
|
2240
|
+
if (ref && ownsRef) registry.detachSession(args.id, ref);
|
|
2060
2241
|
return;
|
|
2061
2242
|
}
|
|
2062
2243
|
|
|
2063
2244
|
// Keep-alive: finished and failed subagents both stay interrogable.
|
|
2064
2245
|
// The lifecycle manager owns idle-TTL parking + revival from here on.
|
|
2065
|
-
registry.setStatus(args.id, "idle")
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
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
|
+
);
|
|
2070
2258
|
}
|
|
2071
2259
|
|
|
2072
2260
|
/** Options for {@link runSubagentFollowUpTurn}. */
|
|
@@ -2237,7 +2425,11 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2237
2425
|
const settings = options.settings ?? Settings.isolated();
|
|
2238
2426
|
const subagentSettings = createSubagentSettings(
|
|
2239
2427
|
settings,
|
|
2240
|
-
|
|
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
|
+
},
|
|
2241
2433
|
options.parentServiceTier,
|
|
2242
2434
|
);
|
|
2243
2435
|
const maxRecursionDepth = settings.get("task.maxRecursionDepth") ?? 2;
|
|
@@ -2321,7 +2513,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2321
2513
|
});
|
|
2322
2514
|
const progress = monitor.progress;
|
|
2323
2515
|
let unsubscribe: (() => void) | null = null;
|
|
2324
|
-
let reviveSession:
|
|
2516
|
+
let reviveSession: AgentReviver | null = null;
|
|
2325
2517
|
// Adopted (kept-alive) subagents flip registry status from session events on
|
|
2326
2518
|
// later turns: revive/wake → running, turn drained → idle. The subscription
|
|
2327
2519
|
// intentionally survives this run; a disposed session emits nothing, so it
|
|
@@ -2329,9 +2521,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2329
2521
|
const installRegistryStatusSync = (target: AgentSession): void => {
|
|
2330
2522
|
target.subscribe(event => {
|
|
2331
2523
|
if (event.type === "agent_start") {
|
|
2332
|
-
AgentRegistry.global().setStatus(id, "running");
|
|
2524
|
+
AgentRegistry.global().setStatus(id, "running", target);
|
|
2333
2525
|
} else if (event.type === "agent_end") {
|
|
2334
|
-
AgentRegistry.global().setStatus(id, "idle");
|
|
2526
|
+
AgentRegistry.global().setStatus(id, "idle", target);
|
|
2335
2527
|
}
|
|
2336
2528
|
});
|
|
2337
2529
|
};
|
|
@@ -2554,8 +2746,12 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2554
2746
|
// the same JSONL file re-invokes createAgentSession with the exact options
|
|
2555
2747
|
// of the original run (same agent id, tools, model, system prompt,
|
|
2556
2748
|
// artifacts dir) — only the SessionManager differs.
|
|
2557
|
-
const buildSubagentSessionOptions = (
|
|
2749
|
+
const buildSubagentSessionOptions = (
|
|
2750
|
+
sessionManagerForRun: SessionManager,
|
|
2751
|
+
expectedAgentRef: CreateAgentSessionOptions["expectedAgentRef"],
|
|
2752
|
+
): CreateAgentSessionOptions => ({
|
|
2558
2753
|
cwd: worktree ?? cwd,
|
|
2754
|
+
additionalDirectories: worktree !== undefined ? undefined : options.additionalDirectories,
|
|
2559
2755
|
authStorage,
|
|
2560
2756
|
modelRegistry,
|
|
2561
2757
|
settings: subagentSettings,
|
|
@@ -2607,6 +2803,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2607
2803
|
parentAgentId: options.parentAgentId,
|
|
2608
2804
|
agentId: id,
|
|
2609
2805
|
agentDisplayName: agent.name,
|
|
2806
|
+
expectedAgentRef,
|
|
2610
2807
|
enableLsp: lspEnabled,
|
|
2611
2808
|
enableIrc: options.enableIrc,
|
|
2612
2809
|
skipPythonPreflight,
|
|
@@ -2621,7 +2818,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2621
2818
|
},
|
|
2622
2819
|
});
|
|
2623
2820
|
|
|
2624
|
-
const sessionPromise = createAgentSession(buildSubagentSessionOptions(sessionManager));
|
|
2821
|
+
const sessionPromise = createAgentSession(buildSubagentSessionOptions(sessionManager, null));
|
|
2625
2822
|
let session: AgentSession;
|
|
2626
2823
|
try {
|
|
2627
2824
|
({ session } = await awaitAbortable(sessionPromise));
|
|
@@ -2641,14 +2838,16 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2641
2838
|
// the single-writer lock cleanly and restores the full message history
|
|
2642
2839
|
// (createAgentSession → agent.replaceMessages). Isolated runs are not
|
|
2643
2840
|
// resumable (worktree is merged + cleaned) and never get a reviver.
|
|
2644
|
-
reviveSession = async
|
|
2841
|
+
reviveSession = async expectedAgentRef => {
|
|
2645
2842
|
const reopened = await SessionManager.open(sessionFile, undefined, undefined, {
|
|
2646
2843
|
suppressBreadcrumb: true,
|
|
2647
2844
|
});
|
|
2648
2845
|
if (options.parentArtifactManager) {
|
|
2649
2846
|
reopened.adoptArtifactManager(options.parentArtifactManager);
|
|
2650
2847
|
}
|
|
2651
|
-
const { session: revived } = await createAgentSession(
|
|
2848
|
+
const { session: revived } = await createAgentSession(
|
|
2849
|
+
buildSubagentSessionOptions(reopened, expectedAgentRef),
|
|
2850
|
+
);
|
|
2652
2851
|
installRegistryStatusSync(revived);
|
|
2653
2852
|
return revived;
|
|
2654
2853
|
};
|
|
@@ -2739,6 +2938,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2739
2938
|
setModel: model => runExtensionSetModel(session, model),
|
|
2740
2939
|
getThinkingLevel: () => session.thinkingLevel,
|
|
2741
2940
|
setThinkingLevel: level => session.setThinkingLevel(level),
|
|
2941
|
+
getServiceTiers: () => session.serviceTierByFamily,
|
|
2942
|
+
setServiceTier: (family, tier) => session.setServiceTierFamily(family, tier),
|
|
2742
2943
|
getSessionName: () => session.sessionManager.getSessionName(),
|
|
2743
2944
|
setSessionName: async name => {
|
|
2744
2945
|
await session.sessionManager.setSessionName(name, "user");
|
|
@@ -2830,6 +3031,20 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2830
3031
|
reviveSession,
|
|
2831
3032
|
});
|
|
2832
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
|
+
}
|
|
2833
3048
|
}
|
|
2834
3049
|
|
|
2835
3050
|
// Launch-latency breakdown (subagent invocation → first chat dispatch).
|