@bastani/atomic 0.9.15 → 0.9.16-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/builtin/intercom/CHANGELOG.md +10 -0
- package/dist/builtin/intercom/README.md +8 -8
- package/dist/builtin/intercom/contact-supervisor-tool.ts +68 -32
- package/dist/builtin/intercom/index-heavy.ts +1 -0
- package/dist/builtin/intercom/index.ts +21 -15
- package/dist/builtin/intercom/intercom-tool.ts +66 -10
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/parent-ask-handoff.ts +72 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +13 -0
- package/dist/builtin/subagents/README.md +23 -12
- package/dist/builtin/subagents/agents/worker.md +2 -2
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +10 -17
- package/dist/builtin/subagents/src/extension/index.ts +5 -30
- package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
- package/dist/builtin/subagents/src/extension/schemas.ts +2 -14
- package/dist/builtin/subagents/src/extension/tool-description.ts +1 -2
- package/dist/builtin/subagents/src/extension/tool-rendering.ts +53 -0
- package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -1
- package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -4
- package/dist/builtin/subagents/src/runs/foreground/execution-parent-ask-handoff.ts +78 -0
- package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +7 -5
- package/dist/builtin/subagents/src/runs/foreground/notify.ts +5 -6
- package/dist/builtin/subagents/src/runs/foreground/parent-ask-output.ts +48 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst-display.ts +46 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst.ts +403 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +48 -9
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +3 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-live-update.ts +11 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +164 -93
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +36 -29
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parent-ask-projection.ts +11 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +42 -17
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +3 -121
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +7 -2
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +30 -220
- package/dist/builtin/subagents/src/runs/inprocess/control-status.ts +0 -34
- package/dist/builtin/subagents/src/runs/inprocess/index.ts +0 -12
- package/dist/builtin/subagents/src/runs/inprocess/prompt-behavior.ts +1 -1
- package/dist/builtin/subagents/src/runs/inprocess/runner.ts +2 -141
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +8 -0
- package/dist/builtin/subagents/src/shared/status-format.ts +3 -3
- package/dist/builtin/subagents/src/shared/types-config.ts +41 -0
- package/dist/builtin/subagents/src/shared/types-foreground-state.ts +7 -15
- package/dist/builtin/subagents/src/shared/types-results.ts +3 -1
- package/dist/builtin/subagents/src/shared/types-runtime.ts +1 -11
- package/dist/builtin/subagents/src/tui/render-progress.ts +2 -2
- package/dist/builtin/subagents/src/tui/render-result-compact.ts +2 -2
- package/dist/builtin/subagents/src/tui/render-result.ts +38 -14
- package/dist/builtin/subagents/src/tui/render-status-progress.ts +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +10 -0
- package/dist/builtin/workflows/README.md +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts +548 -39
- package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +529 -65
- package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +30 -8
- package/dist/builtin/workflows/src/durable/dbos-local-postgres.ts +36 -3
- package/dist/builtin/workflows/src/durable/local-command.ts +68 -10
- package/dist/builtin/workflows/src/extension/dispatcher.ts +7 -1
- package/dist/builtin/workflows/src/extension/extension-factory.ts +2 -2
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +1094 -298
- package/dist/builtin/workflows/src/extension/public-types.ts +2 -2
- package/dist/builtin/workflows/src/extension/render-result.ts +16 -2
- package/dist/builtin/workflows/src/extension/runtime.ts +8 -1
- package/dist/builtin/workflows/src/extension/workflow-request-abort.ts +15 -0
- package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +4 -3
- package/dist/builtin/workflows/src/extension/workflow-tool-registration.ts +107 -13
- package/dist/builtin/workflows/src/extension/workflow-tool.ts +22 -12
- package/dist/builtin/workflows/src/tui/widget.ts +25 -6
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js +19 -3
- package/dist/core/event-bus.js.map +1 -1
- package/docs/intercom.md +28 -26
- package/docs/subagents.md +15 -11
- package/docs/workflows.md +27 -10
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
- package/dist/builtin/subagents/src/runs/inprocess/attempt-handles.ts +0 -104
- /package/dist/builtin/subagents/src/runs/foreground/{subagent-executor-resume.ts → subagent-executor-cwd.ts} +0 -0
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type SubagentState,
|
|
18
18
|
type SubagentToolResult,
|
|
19
19
|
} from "../../shared/types.js";
|
|
20
|
-
import { compactForegroundDetails,
|
|
20
|
+
import { compactForegroundDetails, getSingleResultOutput } from "../../shared/utils.js";
|
|
21
21
|
import {
|
|
22
22
|
formatControlIntercomMessage,
|
|
23
23
|
formatControlNoticeMessage,
|
|
@@ -77,128 +77,10 @@ export function foregroundStatusResult(control: ForegroundControl): SubagentTool
|
|
|
77
77
|
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "management", results: [] } };
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export function retainedForegroundStatusResult(state: SubagentState, runId: string): SubagentToolResult | undefined {
|
|
81
|
-
const run = state.foregroundRuns?.get(runId);
|
|
82
|
-
if (!run) return undefined;
|
|
83
|
-
const statuses = run.children.map((child) => child.status);
|
|
84
|
-
const stateLabel = statuses.includes("detached")
|
|
85
|
-
? "detached"
|
|
86
|
-
: statuses.includes("failed")
|
|
87
|
-
? "failed"
|
|
88
|
-
: statuses.includes("paused")
|
|
89
|
-
? "paused"
|
|
90
|
-
: statuses.every((status) => status === "completed")
|
|
91
|
-
? "completed"
|
|
92
|
-
: (statuses[0] ?? "unknown");
|
|
93
|
-
const lines = [`Run: ${run.runId}`, `State: ${stateLabel}`, `Mode: ${run.mode}`];
|
|
94
|
-
for (const child of run.children) {
|
|
95
|
-
lines.push(`Child ${child.index + 1}: ${child.agent} (${child.status})`);
|
|
96
|
-
const output = child.result ? getSingleResultOutput(child.result) : "";
|
|
97
|
-
if (output) lines.push(output);
|
|
98
|
-
else if (child.result?.error) lines.push(child.result.error);
|
|
99
|
-
}
|
|
100
|
-
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "management", results: [] } };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const earlyDetachedResults = new WeakMap<SubagentState, Map<string, Map<number, SingleResult>>>();
|
|
104
|
-
const MAX_EARLY_DETACHED_RUNS = 50;
|
|
105
|
-
const MAX_EARLY_DETACHED_CHILDREN_PER_RUN = 50;
|
|
106
|
-
|
|
107
|
-
function takeEarlyDetachedResult(state: SubagentState, runId: string, index: number): SingleResult | undefined {
|
|
108
|
-
const byRun = earlyDetachedResults.get(state);
|
|
109
|
-
const byIndex = byRun?.get(runId);
|
|
110
|
-
const result = byIndex?.get(index);
|
|
111
|
-
byIndex?.delete(index);
|
|
112
|
-
if (byIndex?.size === 0) byRun?.delete(runId);
|
|
113
|
-
return result;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function rememberForegroundRun(
|
|
117
|
-
state: SubagentState,
|
|
118
|
-
input: {
|
|
119
|
-
runId: string;
|
|
120
|
-
mode: "single" | "parallel";
|
|
121
|
-
cwd: string;
|
|
122
|
-
results: SingleResult[];
|
|
123
|
-
},
|
|
124
|
-
): void {
|
|
125
|
-
state.foregroundRuns ??= new Map();
|
|
126
|
-
state.foregroundRuns.set(input.runId, {
|
|
127
|
-
runId: input.runId,
|
|
128
|
-
mode: input.mode,
|
|
129
|
-
cwd: input.cwd,
|
|
130
|
-
updatedAt: Date.now(),
|
|
131
|
-
children: input.results.map((originalResult, index) => {
|
|
132
|
-
const result = takeEarlyDetachedResult(state, input.runId, index) ?? originalResult;
|
|
133
|
-
return {
|
|
134
|
-
agent: result.agent,
|
|
135
|
-
index,
|
|
136
|
-
status: resolveSubagentResultStatus({
|
|
137
|
-
status: result.status,
|
|
138
|
-
interrupted: result.interrupted,
|
|
139
|
-
detached: result.detached,
|
|
140
|
-
}),
|
|
141
|
-
...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
|
|
142
|
-
result,
|
|
143
|
-
};
|
|
144
|
-
}),
|
|
145
|
-
});
|
|
146
|
-
while (state.foregroundRuns.size > 50) {
|
|
147
|
-
const oldest = [...state.foregroundRuns.values()].sort((left, right) => left.updatedAt - right.updatedAt)[0];
|
|
148
|
-
if (!oldest) break;
|
|
149
|
-
state.foregroundRuns.delete(oldest.runId);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function replaceForegroundRunChild(
|
|
154
|
-
state: SubagentState,
|
|
155
|
-
runId: string,
|
|
156
|
-
index: number,
|
|
157
|
-
result: SingleResult,
|
|
158
|
-
options: { onlyWhenDetached?: boolean } = {},
|
|
159
|
-
): void {
|
|
160
|
-
const retainedResult = compactForegroundResult(result);
|
|
161
|
-
const run = state.foregroundRuns?.get(runId);
|
|
162
|
-
if (!run) {
|
|
163
|
-
let byRun = earlyDetachedResults.get(state);
|
|
164
|
-
if (!byRun) {
|
|
165
|
-
byRun = new Map();
|
|
166
|
-
earlyDetachedResults.set(state, byRun);
|
|
167
|
-
}
|
|
168
|
-
let byIndex = byRun.get(runId);
|
|
169
|
-
if (!byIndex) {
|
|
170
|
-
byIndex = new Map();
|
|
171
|
-
byRun.set(runId, byIndex);
|
|
172
|
-
}
|
|
173
|
-
byIndex.set(index, retainedResult);
|
|
174
|
-
while (byIndex.size > MAX_EARLY_DETACHED_CHILDREN_PER_RUN) {
|
|
175
|
-
const oldestIndex = byIndex.keys().next().value;
|
|
176
|
-
if (oldestIndex === undefined) break;
|
|
177
|
-
byIndex.delete(oldestIndex);
|
|
178
|
-
}
|
|
179
|
-
while (byRun.size > MAX_EARLY_DETACHED_RUNS) {
|
|
180
|
-
const oldestRun = byRun.keys().next().value;
|
|
181
|
-
if (oldestRun === undefined) break;
|
|
182
|
-
byRun.delete(oldestRun);
|
|
183
|
-
}
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const child = run.children.find((entry) => entry.index === index);
|
|
187
|
-
if (!child || (options.onlyWhenDetached !== false && child.status !== "detached")) return;
|
|
188
|
-
child.status = resolveSubagentResultStatus({
|
|
189
|
-
status: retainedResult.status,
|
|
190
|
-
interrupted: retainedResult.interrupted,
|
|
191
|
-
detached: retainedResult.detached,
|
|
192
|
-
});
|
|
193
|
-
child.result = retainedResult;
|
|
194
|
-
if (retainedResult.sessionFile) child.sessionFile = retainedResult.sessionFile;
|
|
195
|
-
run.updatedAt = Date.now();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
80
|
export function emitControlNotification(input: {
|
|
199
81
|
pi: ExtensionAPI;
|
|
200
82
|
controlConfig: ExecutionContextData["controlConfig"];
|
|
201
|
-
intercomBridge: IntercomBridgeState
|
|
83
|
+
intercomBridge: Pick<IntercomBridgeState, "active" | "orchestratorTarget">;
|
|
202
84
|
event: ControlEvent;
|
|
203
85
|
}): void {
|
|
204
86
|
if (!shouldNotifyControlEvent(input.controlConfig, input.event)) return;
|
|
@@ -284,7 +166,7 @@ export function notifyDetachedForegroundChildExit(input: {
|
|
|
284
166
|
agent: result.agent,
|
|
285
167
|
status: result.status,
|
|
286
168
|
summary: resultSummaryForIntercom(result),
|
|
287
|
-
...(result.interrupted ? { state: "
|
|
169
|
+
...(result.interrupted ? { state: "interrupted" } : {}),
|
|
288
170
|
timestamp: Date.now(),
|
|
289
171
|
...(result.progressSummary?.durationMs !== undefined ? { durationMs: result.progressSummary.durationMs } : {}),
|
|
290
172
|
...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
|
|
@@ -14,6 +14,8 @@ import type {
|
|
|
14
14
|
import type { ChildModePolicy } from "../inprocess/child-policy.js";
|
|
15
15
|
import type { runSync } from "./execution.js";
|
|
16
16
|
|
|
17
|
+
export const BURST_TASK_DISCOVERY_CWD = Symbol("burstTaskDiscoveryCwd");
|
|
18
|
+
|
|
17
19
|
export interface TaskParam {
|
|
18
20
|
agent: string;
|
|
19
21
|
task: string;
|
|
@@ -28,14 +30,16 @@ export interface TaskParam {
|
|
|
28
30
|
group?: string | true;
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
export type BurstTaskParam = TaskParam & {
|
|
34
|
+
[BURST_TASK_DISCOVERY_CWD]?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
31
37
|
export interface SubagentParamsLike {
|
|
32
38
|
action?: (typeof SUBAGENT_ACTIONS)[number];
|
|
33
39
|
id?: string;
|
|
34
40
|
runId?: string;
|
|
35
|
-
index?: number;
|
|
36
41
|
agent?: string;
|
|
37
42
|
task?: string;
|
|
38
|
-
message?: string;
|
|
39
43
|
config?: unknown;
|
|
40
44
|
tasks?: TaskParam[];
|
|
41
45
|
concurrency?: number;
|
|
@@ -94,6 +98,7 @@ export interface ExecutionContextData {
|
|
|
94
98
|
signal: AbortSignal;
|
|
95
99
|
onUpdate?: (r: SubagentToolResult) => void;
|
|
96
100
|
agents: AgentConfig[];
|
|
101
|
+
parallelAgentConfigs?: AgentConfig[];
|
|
97
102
|
runId: string;
|
|
98
103
|
shareEnabled: boolean;
|
|
99
104
|
sessionRoot: string;
|
|
@@ -1,55 +1,18 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
1
|
import type { ExtensionContext } from "@bastani/atomic";
|
|
4
2
|
import { handleManagementAction } from "../../agents/agent-management.js";
|
|
5
|
-
import { resolveExecutionAgentScope } from "../../agents/agent-scope.js";
|
|
6
3
|
import { clearPendingForegroundControlNotices } from "../../extension/control-notices.js";
|
|
7
4
|
import { buildDoctorReport } from "../../extension/doctor.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
resolveSubagentIntercomTarget,
|
|
13
|
-
} from "../../intercom/intercom-bridge.js";
|
|
14
|
-
import { requestSupervisorAuthorization } from "../../intercom/supervisor-authorization.js";
|
|
15
|
-
import { getArtifactsDir } from "../../shared/artifacts.js";
|
|
16
|
-
import { collectKnownModelProviders, toModelInfo } from "../../shared/model-info.js";
|
|
17
|
-
import { createCandidateModelResolver } from "../../shared/model-resolution.js";
|
|
18
|
-
import {
|
|
19
|
-
injectSingleProgressInstruction,
|
|
20
|
-
resolveSingleProgress,
|
|
21
|
-
writeInitialProgressFile,
|
|
22
|
-
} from "../../shared/settings.js";
|
|
23
|
-
import {
|
|
24
|
-
DEFAULT_ARTIFACT_CONFIG,
|
|
25
|
-
getCurrentSubagentDepth,
|
|
26
|
-
isWorkflowStageOrchestrationContext,
|
|
27
|
-
type SingleResult,
|
|
28
|
-
SUBAGENT_ACTIONS,
|
|
29
|
-
type SubagentToolResult,
|
|
30
|
-
workflowSessionMetadataFromContext,
|
|
31
|
-
} from "../../shared/types.js";
|
|
32
|
-
import {
|
|
33
|
-
inspectInProcessChildStatus,
|
|
34
|
-
interruptInProcessChild,
|
|
35
|
-
resumeInProcessChild,
|
|
36
|
-
} from "../inprocess/control-status.js";
|
|
37
|
-
import { inheritedIntercomGroup } from "../shared/intercom-group.js";
|
|
38
|
-
import { currentModelFullId } from "../shared/model-fallback.js";
|
|
39
|
-
import { resolveControlConfig } from "../shared/subagent-control.js";
|
|
5
|
+
import { resolveIntercomSessionTarget } from "../../intercom/intercom-bridge.js";
|
|
6
|
+
import { SUBAGENT_ACTIONS, type SubagentToolResult } from "../../shared/types.js";
|
|
7
|
+
import { inspectInProcessChildStatus, interruptInProcessChild } from "../inprocess/control-status.js";
|
|
8
|
+
import { createExecutionBurstDispatcher } from "./subagent-executor-burst.js";
|
|
40
9
|
import { prepareExecutionContext, refuseSubagentChildDelegation } from "./subagent-executor-context.js";
|
|
10
|
+
import { resolveRequestedCwd } from "./subagent-executor-cwd.js";
|
|
41
11
|
import { toExecutionErrorResult, withForkContext } from "./subagent-executor-input.js";
|
|
42
12
|
import { runParallelPath } from "./subagent-executor-parallel.js";
|
|
43
|
-
import { resolveRequestedCwd } from "./subagent-executor-resume.js";
|
|
44
13
|
import { resolveSubagentExecutorRuntimeDeps } from "./subagent-executor-runtime.js";
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
foregroundStatusResult,
|
|
48
|
-
getForegroundControl,
|
|
49
|
-
notifyDetachedForegroundChildExit,
|
|
50
|
-
replaceForegroundRunChild,
|
|
51
|
-
retainedForegroundStatusResult,
|
|
52
|
-
} from "./subagent-executor-status.js";
|
|
14
|
+
import { runSinglePath } from "./subagent-executor-single.js";
|
|
15
|
+
import { foregroundStatusResult, getForegroundControl } from "./subagent-executor-status.js";
|
|
53
16
|
import {
|
|
54
17
|
type ExecutorDeps,
|
|
55
18
|
isManagementActionsRestricted,
|
|
@@ -57,129 +20,8 @@ import {
|
|
|
57
20
|
type SubagentParamsLike,
|
|
58
21
|
} from "./subagent-executor-types.js";
|
|
59
22
|
|
|
60
|
-
async function resumeRetainedForegroundChild(
|
|
61
|
-
params: SubagentParamsLike,
|
|
62
|
-
message: string,
|
|
63
|
-
ctx: ExtensionContext,
|
|
64
|
-
deps: ResolvedExecutorDeps,
|
|
65
|
-
onUpdate: ((r: SubagentToolResult) => void) | undefined,
|
|
66
|
-
): Promise<SubagentToolResult | undefined> {
|
|
67
|
-
const requested = params.id ?? params.runId;
|
|
68
|
-
if (!requested) return undefined;
|
|
69
|
-
const run = deps.state.foregroundRuns?.get(requested);
|
|
70
|
-
if (!run) return undefined;
|
|
71
|
-
const child = run.children[params.index ?? 0];
|
|
72
|
-
if (!child) return undefined;
|
|
73
|
-
const agents = deps.discoverAgents(run.cwd, resolveExecutionAgentScope(params.agentScope)).agents;
|
|
74
|
-
const agentConfig = agents.find((agent) => agent.name === child.agent);
|
|
75
|
-
if (!agentConfig) {
|
|
76
|
-
return {
|
|
77
|
-
content: [{ type: "text", text: `Unknown agent for resume: ${child.agent}` }],
|
|
78
|
-
isError: true,
|
|
79
|
-
details: { mode: "single", results: [] },
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
83
|
-
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
84
|
-
const intercomBridge = resolveIntercomBridge({
|
|
85
|
-
config: deps.config.intercomBridge,
|
|
86
|
-
context: params.context,
|
|
87
|
-
orchestratorTarget: sessionName,
|
|
88
|
-
cwd: run.cwd,
|
|
89
|
-
});
|
|
90
|
-
const supervisedTarget = intercomBridge.active
|
|
91
|
-
? resolveSubagentIntercomTarget(run.runId, child.agent, child.index)
|
|
92
|
-
: undefined;
|
|
93
|
-
const supervisorAuthorization = await requestSupervisorAuthorization(deps.pi.events, supervisedTarget);
|
|
94
|
-
const artifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: params.artifacts !== false };
|
|
95
|
-
const artifactsDir = getArtifactsDir(parentSessionFile);
|
|
96
|
-
const progressDir = resolveSingleProgress(agentConfig, params.progress, message)
|
|
97
|
-
? path.join(artifactsDir, "progress", run.runId)
|
|
98
|
-
: undefined;
|
|
99
|
-
if (progressDir) {
|
|
100
|
-
writeInitialProgressFile(progressDir);
|
|
101
|
-
message = injectSingleProgressInstruction(message, progressDir);
|
|
102
|
-
}
|
|
103
|
-
const cleanupProgress = (): void => {
|
|
104
|
-
if (!progressDir || artifactConfig.enabled) return;
|
|
105
|
-
try {
|
|
106
|
-
fs.rmSync(progressDir, { recursive: true, force: true });
|
|
107
|
-
} catch {
|
|
108
|
-
// Scratch cleanup must never replace the child run's result or original error.
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
const forwardSingleUpdate = createForwardSingleUpdate(
|
|
112
|
-
onUpdate,
|
|
113
|
-
getForegroundControl(deps.state, run.runId),
|
|
114
|
-
child.agent,
|
|
115
|
-
child.index,
|
|
116
|
-
);
|
|
117
|
-
let result: SingleResult;
|
|
118
|
-
try {
|
|
119
|
-
result = await deps.runtime.runSync(run.cwd, agents, child.agent, message, {
|
|
120
|
-
cwd: run.cwd,
|
|
121
|
-
signal: ctx.signal,
|
|
122
|
-
interruptSignal: ctx.signal,
|
|
123
|
-
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
124
|
-
intercomEvents: deps.pi.events,
|
|
125
|
-
runId: run.runId,
|
|
126
|
-
index: child.index,
|
|
127
|
-
sessionDir: child.sessionFile ? path.dirname(child.sessionFile) : undefined,
|
|
128
|
-
sessionFile: child.sessionFile,
|
|
129
|
-
share: params.share === true,
|
|
130
|
-
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
131
|
-
artifactConfig,
|
|
132
|
-
maxOutput: params.maxOutput,
|
|
133
|
-
parentDepth: getCurrentSubagentDepth(ctx),
|
|
134
|
-
workflowStageSubagentGuard: isWorkflowStageOrchestrationContext(ctx),
|
|
135
|
-
workflowSessionMetadata: workflowSessionMetadataFromContext(ctx),
|
|
136
|
-
controlConfig: resolveControlConfig(deps.config.control, params.control),
|
|
137
|
-
intercomSessionName: intercomBridge.active
|
|
138
|
-
? resolveSubagentIntercomTarget(run.runId, child.agent, child.index)
|
|
139
|
-
: undefined,
|
|
140
|
-
orchestratorIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
141
|
-
intercomGroup: inheritedIntercomGroup(ctx),
|
|
142
|
-
supervisorAuthorization,
|
|
143
|
-
modelOverride: params.model,
|
|
144
|
-
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
145
|
-
knownModelProviders: collectKnownModelProviders(ctx.modelRegistry),
|
|
146
|
-
resolveCandidateModel: createCandidateModelResolver(ctx.modelRegistry, ctx.model?.provider),
|
|
147
|
-
preferredModelProvider: ctx.model?.provider,
|
|
148
|
-
currentModel: currentModelFullId(ctx.model),
|
|
149
|
-
currentThinkingLevel: ctx.thinkingLevel,
|
|
150
|
-
onUpdate: forwardSingleUpdate,
|
|
151
|
-
onDetachedExit: (detachedResult) => {
|
|
152
|
-
cleanupProgress();
|
|
153
|
-
if (detachedResult) {
|
|
154
|
-
replaceForegroundRunChild(deps.state, run.runId, child.index, detachedResult);
|
|
155
|
-
notifyDetachedForegroundChildExit({
|
|
156
|
-
pi: deps.pi,
|
|
157
|
-
runId: run.runId,
|
|
158
|
-
mode: "single",
|
|
159
|
-
index: child.index,
|
|
160
|
-
result: detachedResult,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
} catch (error) {
|
|
166
|
-
cleanupProgress();
|
|
167
|
-
throw error;
|
|
168
|
-
}
|
|
169
|
-
if (!result.detached) cleanupProgress();
|
|
170
|
-
replaceForegroundRunChild(deps.state, run.runId, child.index, result, { onlyWhenDetached: false });
|
|
171
|
-
return {
|
|
172
|
-
content: [{ type: "text", text: result.finalOutput ?? result.envelope ?? result.error ?? "" }],
|
|
173
|
-
details: { mode: "single", runId: run.runId, results: [result] },
|
|
174
|
-
...(result.status === "error" ? { isError: true } : {}),
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
23
|
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete"]);
|
|
178
|
-
/**
|
|
179
|
-
* Management actions that only observe. Every other action either mutates agent
|
|
180
|
-
* definitions or starts/continues agent execution, so a child without fanout
|
|
181
|
-
* authorization is refused all of them.
|
|
182
|
-
*/
|
|
24
|
+
/** Observing management actions do not start or mutate child execution. */
|
|
183
25
|
const READ_ONLY_MANAGEMENT_ACTIONS = new Set(["list", "get", "status", "doctor"]);
|
|
184
26
|
const FANOUT_REFUSAL_MESSAGE = "Subagent fanout is not authorized for this child.";
|
|
185
27
|
|
|
@@ -191,9 +33,8 @@ async function handleManagementRequest(input: {
|
|
|
191
33
|
requestCwd: string;
|
|
192
34
|
ctx: ExtensionContext;
|
|
193
35
|
deps: ResolvedExecutorDeps;
|
|
194
|
-
onUpdate?: (r: SubagentToolResult) => void;
|
|
195
36
|
}): Promise<SubagentToolResult> {
|
|
196
|
-
const { params, paramsWithResolvedCwd, requestCwd, ctx, deps
|
|
37
|
+
const { params, paramsWithResolvedCwd, requestCwd, ctx, deps } = input;
|
|
197
38
|
const action = params.action;
|
|
198
39
|
if (!action) {
|
|
199
40
|
return {
|
|
@@ -216,9 +57,8 @@ async function handleManagementRequest(input: {
|
|
|
216
57
|
details: { mode: "management" as const, results: [] },
|
|
217
58
|
};
|
|
218
59
|
}
|
|
219
|
-
// `
|
|
220
|
-
//
|
|
221
|
-
// reach their handlers for a child without fanout authorization.
|
|
60
|
+
// `interrupt` is privileged control over a running child, so only the
|
|
61
|
+
// observing actions reach handlers for a child without fanout authorization.
|
|
222
62
|
if (deps.childPolicy && !deps.childPolicy.fanoutAuthorized && !READ_ONLY_MANAGEMENT_ACTIONS.has(action)) {
|
|
223
63
|
return {
|
|
224
64
|
content: [{ type: "text", text: FANOUT_REFUSAL_MESSAGE }],
|
|
@@ -226,8 +66,7 @@ async function handleManagementRequest(input: {
|
|
|
226
66
|
details: { mode: "management" as const, results: [] },
|
|
227
67
|
};
|
|
228
68
|
}
|
|
229
|
-
// Delegation is one level deep: a
|
|
230
|
-
// never start or continue another agent, whatever else it is authorized for.
|
|
69
|
+
// Delegation is one level deep: a child may never control another child.
|
|
231
70
|
if (!READ_ONLY_MANAGEMENT_ACTIONS.has(action)) {
|
|
232
71
|
const childRefusal = refuseSubagentChildDelegation(ctx, "management");
|
|
233
72
|
if (childRefusal) return childRefusal;
|
|
@@ -273,10 +112,6 @@ async function handleManagementRequest(input: {
|
|
|
273
112
|
if (inProcess) return inProcess;
|
|
274
113
|
const foreground = getForegroundControl(deps.state, targetRunId);
|
|
275
114
|
if (foreground) return foregroundStatusResult(foreground);
|
|
276
|
-
if (targetRunId) {
|
|
277
|
-
const retained = retainedForegroundStatusResult(deps.state, targetRunId);
|
|
278
|
-
if (retained) return retained;
|
|
279
|
-
}
|
|
280
115
|
return {
|
|
281
116
|
content: [
|
|
282
117
|
{
|
|
@@ -288,26 +123,6 @@ async function handleManagementRequest(input: {
|
|
|
288
123
|
details: { mode: "management", results: [] },
|
|
289
124
|
};
|
|
290
125
|
}
|
|
291
|
-
if (action === "resume") {
|
|
292
|
-
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
293
|
-
const message = paramsWithResolvedCwd.message ?? paramsWithResolvedCwd.task;
|
|
294
|
-
if (!targetRunId || !message) {
|
|
295
|
-
return {
|
|
296
|
-
content: [{ type: "text", text: "action='resume' requires id/runId and message." }],
|
|
297
|
-
isError: true,
|
|
298
|
-
details: { mode: "management", results: [] },
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
const inProcess = await resumeInProcessChild(targetRunId, message, { model: ctx.model });
|
|
302
|
-
if (inProcess) return inProcess;
|
|
303
|
-
const retained = await resumeRetainedForegroundChild(paramsWithResolvedCwd, message, ctx, deps, onUpdate);
|
|
304
|
-
if (retained) return retained;
|
|
305
|
-
return {
|
|
306
|
-
content: [{ type: "text", text: `No in-process child found for '${targetRunId}'.` }],
|
|
307
|
-
isError: true,
|
|
308
|
-
details: { mode: "management", results: [] },
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
126
|
if (action === "interrupt") {
|
|
312
127
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
313
128
|
if (targetRunId) {
|
|
@@ -366,18 +181,16 @@ export function createSubagentExecutor(rawDeps: ExecutorDeps): {
|
|
|
366
181
|
ctx: ExtensionContext,
|
|
367
182
|
): Promise<SubagentToolResult> => {
|
|
368
183
|
deps.state.baseCwd = ctx.cwd;
|
|
369
|
-
deps.state.foregroundRuns ??= new Map();
|
|
370
184
|
deps.state.foregroundControls ??= new Map();
|
|
371
185
|
deps.state.lastForegroundControlId ??= null;
|
|
372
186
|
const requestCwd = resolveRequestedCwd(ctx.cwd, params.cwd);
|
|
373
187
|
const paramsWithResolvedCwd = params.cwd === undefined ? params : { ...params, cwd: requestCwd };
|
|
374
188
|
if (params.action) {
|
|
375
|
-
return handleManagementRequest({ params, paramsWithResolvedCwd, requestCwd, ctx, deps
|
|
189
|
+
return handleManagementRequest({ params, paramsWithResolvedCwd, requestCwd, ctx, deps });
|
|
376
190
|
}
|
|
377
|
-
// Fanout authorization gates delegation and
|
|
378
|
-
//
|
|
379
|
-
// `
|
|
380
|
-
// and mutating management are refused inside handleManagementRequest.
|
|
191
|
+
// Fanout authorization gates delegation and privileged control. Only `list`,
|
|
192
|
+
// `get`, `status`, and `doctor` stay available to an unauthorized child;
|
|
193
|
+
// `interrupt` and mutating management are refused by handleManagementRequest.
|
|
381
194
|
if (deps.childPolicy && !deps.childPolicy.fanoutAuthorized) {
|
|
382
195
|
return {
|
|
383
196
|
content: [{ type: "text", text: FANOUT_REFUSAL_MESSAGE }],
|
|
@@ -423,22 +236,19 @@ export function createSubagentExecutor(rawDeps: ExecutorDeps): {
|
|
|
423
236
|
);
|
|
424
237
|
};
|
|
425
238
|
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (deps.state.subagentInProgress === true) return duplicateSubagentCallResult(params);
|
|
435
|
-
deps.state.subagentInProgress = true;
|
|
436
|
-
try {
|
|
437
|
-
return await execute(id, params, signal, onUpdate, ctx);
|
|
438
|
-
} finally {
|
|
439
|
-
deps.state.subagentInProgress = false;
|
|
440
|
-
}
|
|
441
|
-
};
|
|
239
|
+
const executeWithBurstCollection = createExecutionBurstDispatcher({
|
|
240
|
+
execute,
|
|
241
|
+
isActive: () => deps.state.subagentInProgress === true,
|
|
242
|
+
setActive: (active) => {
|
|
243
|
+
deps.state.subagentInProgress = active;
|
|
244
|
+
},
|
|
245
|
+
duplicateResult: duplicateSubagentCallResult,
|
|
246
|
+
});
|
|
442
247
|
|
|
443
|
-
return {
|
|
248
|
+
return {
|
|
249
|
+
execute: (id, params, signal, onUpdate, ctx) =>
|
|
250
|
+
params.action
|
|
251
|
+
? execute(id, params, signal, onUpdate, ctx)
|
|
252
|
+
: executeWithBurstCollection(id, params, signal, onUpdate, ctx),
|
|
253
|
+
};
|
|
444
254
|
}
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import type { SubagentToolResult } from "../../shared/types.js";
|
|
2
2
|
import { findSubagentControl, listSubagentControls } from "./control-registry.js";
|
|
3
|
-
import type { ModelCandidate } from "./runner.js";
|
|
4
3
|
|
|
5
4
|
function canonicalChildren(control: NonNullable<ReturnType<typeof findSubagentControl>>) {
|
|
6
5
|
return [...control.listChildren()].sort((left, right) => left.path.localeCompare(right.path));
|
|
7
6
|
}
|
|
8
7
|
|
|
9
|
-
function resumeTarget(control: NonNullable<ReturnType<typeof findSubagentControl>>, id: string) {
|
|
10
|
-
if (id !== control.parent.path) return control.findChild(id);
|
|
11
|
-
const children = canonicalChildren(control);
|
|
12
|
-
return children.find((child) => child.status === "interrupted") ?? children[0];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
8
|
function childLines(control: ReturnType<typeof findSubagentControl>, id?: string): string[] {
|
|
16
9
|
if (!control) return [];
|
|
17
10
|
if (id) {
|
|
@@ -77,30 +70,3 @@ export async function interruptInProcessChild(id: string): Promise<SubagentToolR
|
|
|
77
70
|
details: { mode: "management", results: [] },
|
|
78
71
|
};
|
|
79
72
|
}
|
|
80
|
-
|
|
81
|
-
export async function resumeInProcessChild(
|
|
82
|
-
id: string,
|
|
83
|
-
message: string,
|
|
84
|
-
candidate: ModelCandidate,
|
|
85
|
-
): Promise<SubagentToolResult | undefined> {
|
|
86
|
-
const control = findSubagentControl(id);
|
|
87
|
-
if (!control) return undefined;
|
|
88
|
-
const target = resumeTarget(control, id);
|
|
89
|
-
if (!target) {
|
|
90
|
-
return {
|
|
91
|
-
content: [{ type: "text", text: `No in-process child found for '${id}'.` }],
|
|
92
|
-
isError: true,
|
|
93
|
-
details: { mode: "management", results: [] },
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
const outcome = await control.resumeChild(target.path, message, candidate);
|
|
97
|
-
const selection =
|
|
98
|
-
id === control.parent.path && control.listChildren().length > 1
|
|
99
|
-
? `Bare run id resolved to ${target.path}; interrupted children are preferred, then canonical path order.\n\n`
|
|
100
|
-
: "";
|
|
101
|
-
return {
|
|
102
|
-
content: [{ type: "text", text: `${selection}${outcome.envelope}` }],
|
|
103
|
-
isError: outcome.status === "error" || outcome.status === "interrupted" ? true : undefined,
|
|
104
|
-
details: { mode: "management", results: [] },
|
|
105
|
-
};
|
|
106
|
-
}
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
InProcessAttemptControlResult,
|
|
3
|
-
InProcessAttemptHandle,
|
|
4
|
-
InProcessAttemptResumeOutcome,
|
|
5
|
-
} from "./attempt-handles.js";
|
|
6
|
-
export {
|
|
7
|
-
clearInProcessAttemptHandles,
|
|
8
|
-
interruptInProcessAttempt,
|
|
9
|
-
registerInProcessAttempt,
|
|
10
|
-
resumeInProcessAttempt,
|
|
11
|
-
} from "./attempt-handles.js";
|
|
12
1
|
export type {
|
|
13
2
|
AdmissionRefusal,
|
|
14
3
|
AdmittedResult,
|
|
@@ -32,7 +21,6 @@ export {
|
|
|
32
21
|
continue_detached,
|
|
33
22
|
createSubagentControl,
|
|
34
23
|
deliver_child_result,
|
|
35
|
-
reload_cold_child,
|
|
36
24
|
run_child_attempt,
|
|
37
25
|
SubagentControlRuntime,
|
|
38
26
|
terminate_child_attempt,
|
|
@@ -11,7 +11,7 @@ export const CHILD_SUBAGENT_BOUNDARY_INSTRUCTIONS = [
|
|
|
11
11
|
export const CHILD_FANOUT_BOUNDARY_INSTRUCTIONS = [
|
|
12
12
|
"You are a child subagent with explicit fanout responsibility for this assigned task.",
|
|
13
13
|
"The parent session owns final orchestration, completion decisions, and follow-up implementation launches.",
|
|
14
|
-
"Delegation is one level deep: the `subagent` tool refuses every launch
|
|
14
|
+
"Delegation is one level deep: the `subagent` tool refuses every launch and interrupt from inside a subagent.",
|
|
15
15
|
"Do the assigned fanout work yourself and report what you found; do not plan around delegating it.",
|
|
16
16
|
"If you need to edit files, call the actual edit/write tools. Do not print tool-call syntax, patches, or pseudo-tool calls as text.",
|
|
17
17
|
].join("\n");
|