@agwab/pi-workflow 0.2.0 → 0.3.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/README.md +2 -0
- package/dist/compiler.d.ts +4 -6
- package/dist/compiler.js +70 -39
- package/dist/dynamic-decision.d.ts +0 -1
- package/dist/dynamic-decision.js +0 -7
- package/dist/dynamic-generated-task-runtime.d.ts +2 -0
- package/dist/dynamic-generated-task-runtime.js +21 -8
- package/dist/dynamic-profiles.d.ts +0 -1
- package/dist/dynamic-profiles.js +0 -3
- package/dist/engine-run-graph.d.ts +1 -0
- package/dist/engine-run-graph.js +142 -2
- package/dist/engine.d.ts +10 -6
- package/dist/engine.js +146 -77
- package/dist/extension.d.ts +2 -1
- package/dist/extension.js +38 -15
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/store.d.ts +3 -1
- package/dist/store.js +189 -49
- package/dist/subagent-backend.d.ts +4 -0
- package/dist/subagent-backend.js +281 -31
- package/dist/types.d.ts +9 -1
- package/dist/workflow-runtime.d.ts +2 -0
- package/dist/workflow-runtime.js +40 -1
- package/dist/workflow-view.js +3 -1
- package/dist/workflow-web-source-extension.js +167 -48
- package/dist/workflow-web-source.d.ts +2 -1
- package/dist/workflow-web-source.js +84 -19
- package/docs/usage.md +11 -0
- package/node_modules/@agwab/pi-subagent/README.md +3 -3
- package/node_modules/@agwab/pi-subagent/api.mjs +1 -0
- package/node_modules/@agwab/pi-subagent/docs/usage.md +63 -12
- package/node_modules/@agwab/pi-subagent/package.json +2 -2
- package/node_modules/@agwab/pi-subagent/src/api.ts +54 -1
- package/node_modules/@agwab/pi-subagent/src/artifacts/registry.ts +9 -4
- package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +8 -0
- package/node_modules/@agwab/pi-subagent/src/core/constants.ts +9 -0
- package/node_modules/@agwab/pi-subagent/src/core/validation.ts +21 -0
- package/node_modules/@agwab/pi-subagent/src/index.ts +995 -573
- package/node_modules/@agwab/pi-subagent/src/orchestrate/async.ts +279 -156
- package/node_modules/@agwab/pi-subagent/src/orchestrate/interrupt.ts +165 -89
- package/node_modules/@agwab/pi-subagent/src/orchestrate/reconcile.ts +111 -65
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run-ref.ts +219 -0
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +88 -8
- package/node_modules/@agwab/pi-subagent/src/orchestrate/status.ts +614 -298
- package/node_modules/@agwab/pi-subagent/src/panel.ts +1352 -560
- package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +53 -5
- package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +13 -6
- package/package.json +2 -2
- package/src/compiler.ts +127 -66
- package/src/dynamic-decision.ts +0 -11
- package/src/dynamic-generated-task-runtime.ts +47 -12
- package/src/dynamic-profiles.ts +0 -4
- package/src/engine-run-graph.ts +185 -2
- package/src/engine.ts +192 -107
- package/src/extension.ts +50 -17
- package/src/index.ts +3 -1
- package/src/store.ts +253 -55
- package/src/subagent-backend.ts +369 -32
- package/src/types.ts +13 -1
- package/src/workflow-runtime.ts +53 -2
- package/src/workflow-view.ts +2 -1
- package/src/workflow-web-source-extension.ts +621 -228
- package/src/workflow-web-source.ts +118 -28
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +56 -16
- package/workflows/deep-research/helpers/final-audit-packet.mjs +1 -4
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +1 -1
- package/workflows/deep-research/helpers/render-executive.mjs +8 -21
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +89 -15
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +0 -1
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +4 -1
- package/workflows/impact-review/spec.json +3 -3
- package/workflows/spec-review/helpers/spec-review-pipeline.mjs +1 -8
- package/dist/dynamic-loader.d.ts +0 -25
- package/dist/dynamic-loader.js +0 -13
- package/src/dynamic-loader.ts +0 -49
- package/workflows/impact-review/schemas/docs-release-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/security-performance-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/state-data-impact-control.schema.json +0 -42
|
@@ -3,678 +3,1100 @@ import { Type } from "typebox";
|
|
|
3
3
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { loadAgentByName, type AgentDefinition } from "./agents.ts";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
appendRunEvent,
|
|
7
|
+
createAttemptArtifactStore,
|
|
8
|
+
setRunDependency,
|
|
9
|
+
type ArtifactRef,
|
|
10
|
+
type ResultEnvelope,
|
|
11
11
|
} from "./artifacts/index.ts";
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
AGENT_SCOPES,
|
|
14
|
+
ASYNC_DEPENDENCIES,
|
|
15
|
+
BACKENDS,
|
|
16
|
+
EXECUTION_MODES,
|
|
17
|
+
ON_COMPLETE_ACTIONS,
|
|
18
|
+
THINKING_LEVELS,
|
|
19
|
+
WORKSPACE_MODES,
|
|
20
|
+
WORKTREE_POLICIES,
|
|
21
|
+
type ExecutionMode,
|
|
22
|
+
type ResolveInput,
|
|
23
|
+
type ResolveValidationFailure,
|
|
24
|
+
type ResolvedBackend,
|
|
25
25
|
} from "./core/constants.ts";
|
|
26
26
|
import { resolveBackend } from "./core/resolver.ts";
|
|
27
27
|
import { validateResolveInput } from "./core/validation.ts";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
startAsyncParallelSubagentRuns,
|
|
30
|
+
startAsyncSubagentRun,
|
|
31
|
+
} from "./orchestrate/async.ts";
|
|
29
32
|
import { interruptRun } from "./orchestrate/interrupt.ts";
|
|
30
33
|
import { reconcileSubagentRun } from "./orchestrate/reconcile.ts";
|
|
31
|
-
import {
|
|
34
|
+
import { resolveRunRef } from "./orchestrate/run-ref.ts";
|
|
35
|
+
import {
|
|
36
|
+
DEFAULT_PARALLEL_CONCURRENCY,
|
|
37
|
+
runParallelSubagentTasks,
|
|
38
|
+
runSubagentTask,
|
|
39
|
+
} from "./orchestrate/run.ts";
|
|
32
40
|
import { getRunLogs, getRunStatus, waitForRun } from "./orchestrate/status.ts";
|
|
33
41
|
import { showSubagentPanel } from "./panel.ts";
|
|
34
42
|
import { WorkspacePolicyError } from "./workspace/worktree.ts";
|
|
35
43
|
|
|
36
44
|
const TOOL_NAME = "subagent";
|
|
37
45
|
const SUPPORTED_KEYS = new Set([
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
46
|
+
"backend",
|
|
47
|
+
"visible",
|
|
48
|
+
"sandbox",
|
|
49
|
+
"agent",
|
|
50
|
+
"task",
|
|
51
|
+
"roleContext",
|
|
52
|
+
"agentScope",
|
|
53
|
+
"confirmProjectAgents",
|
|
54
|
+
"mode",
|
|
55
|
+
"tasks",
|
|
56
|
+
"concurrency",
|
|
57
|
+
"failFast",
|
|
58
|
+
"cancelSiblingsOnFailure",
|
|
59
|
+
"asyncDependency",
|
|
60
|
+
"workspace",
|
|
61
|
+
"worktree",
|
|
62
|
+
"worktreePolicy",
|
|
63
|
+
"cwd",
|
|
64
|
+
"async",
|
|
65
|
+
"onComplete",
|
|
66
|
+
"timeoutMs",
|
|
67
|
+
"model",
|
|
68
|
+
"tools",
|
|
69
|
+
"systemPrompt",
|
|
70
|
+
"skills",
|
|
71
|
+
"extensions",
|
|
72
|
+
"runsDir",
|
|
73
|
+
"correlationId",
|
|
74
|
+
"captureToolCalls",
|
|
75
|
+
"thinking",
|
|
76
|
+
"thinkingLevel",
|
|
77
|
+
"reasoningLevel",
|
|
78
|
+
"action",
|
|
79
|
+
"runId",
|
|
80
|
+
"attemptId",
|
|
81
|
+
"taskId",
|
|
82
|
+
"pollIntervalMs",
|
|
83
|
+
"reason",
|
|
84
|
+
"signal",
|
|
85
|
+
"escalateAfterMs",
|
|
86
|
+
"killAfterMs",
|
|
77
87
|
]);
|
|
78
|
-
const AGENT_TASK_KEYS = [
|
|
88
|
+
const AGENT_TASK_KEYS = [
|
|
89
|
+
"agent",
|
|
90
|
+
"task",
|
|
91
|
+
"roleContext",
|
|
92
|
+
"agentScope",
|
|
93
|
+
"confirmProjectAgents",
|
|
94
|
+
];
|
|
79
95
|
const SANDBOX_SCHEMA = Type.Union(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
[
|
|
97
|
+
Type.Boolean(),
|
|
98
|
+
Type.Null(),
|
|
99
|
+
Type.Object({
|
|
100
|
+
allowedDomains: Type.Optional(
|
|
101
|
+
Type.Array(Type.String({ minLength: 1 }), {
|
|
102
|
+
description:
|
|
103
|
+
'Network domains the sandboxed child may reach, e.g. "api.anthropic.com" or "*.npmjs.org". Model-backed sandboxed runs must include their provider endpoint. Omitted means deny-all network.',
|
|
104
|
+
}),
|
|
105
|
+
),
|
|
106
|
+
}),
|
|
107
|
+
],
|
|
108
|
+
{
|
|
109
|
+
description:
|
|
110
|
+
"true = offline OS sandbox; { allowedDomains: [...] } = sandbox with explicit network egress; false/null = no sandbox.",
|
|
111
|
+
},
|
|
92
112
|
);
|
|
93
113
|
const SUBAGENT_TASK_SCHEMA = Type.Object({
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
agent: Type.Optional(Type.String({ minLength: 1 })),
|
|
115
|
+
task: Type.Optional(Type.String({ minLength: 1 })),
|
|
116
|
+
roleContext: Type.Optional(Type.String({ minLength: 1 })),
|
|
117
|
+
agentScope: Type.Optional(
|
|
118
|
+
Type.Union(AGENT_SCOPES.map((value) => Type.Literal(value))),
|
|
119
|
+
),
|
|
120
|
+
confirmProjectAgents: Type.Optional(Type.Boolean()),
|
|
121
|
+
sandbox: Type.Optional(SANDBOX_SCHEMA),
|
|
122
|
+
visible: Type.Optional(Type.Boolean()),
|
|
123
|
+
cwd: Type.Optional(Type.String({ minLength: 1 })),
|
|
124
|
+
timeoutMs: Type.Optional(Type.Number({ exclusiveMinimum: 0 })),
|
|
125
|
+
model: Type.Optional(Type.String({ minLength: 1 })),
|
|
126
|
+
thinking: Type.Optional(
|
|
127
|
+
Type.Union(THINKING_LEVELS.map((value) => Type.Literal(value))),
|
|
128
|
+
),
|
|
129
|
+
tools: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
130
|
+
systemPrompt: Type.Optional(Type.String({ minLength: 1 })),
|
|
131
|
+
skills: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
132
|
+
extensions: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
133
|
+
captureToolCalls: Type.Optional(
|
|
134
|
+
Type.Boolean({
|
|
135
|
+
description:
|
|
136
|
+
"Capture redacted child tool-call telemetry as artifacts. Default false.",
|
|
137
|
+
}),
|
|
138
|
+
),
|
|
110
139
|
});
|
|
111
140
|
|
|
112
141
|
interface ToolTextContent {
|
|
113
|
-
|
|
114
|
-
|
|
142
|
+
type: "text";
|
|
143
|
+
text: string;
|
|
115
144
|
}
|
|
116
145
|
|
|
117
146
|
interface ToolResult {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
content: ToolTextContent[];
|
|
148
|
+
details: unknown;
|
|
149
|
+
isError: boolean;
|
|
121
150
|
}
|
|
122
151
|
|
|
123
152
|
const ANSI_PATTERN = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
124
153
|
|
|
125
154
|
function visibleLength(text: string): number {
|
|
126
|
-
|
|
155
|
+
return text.replace(ANSI_PATTERN, "").length;
|
|
127
156
|
}
|
|
128
157
|
|
|
129
158
|
function clip(text: string, width: number): string {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
159
|
+
if (width <= 0) return "";
|
|
160
|
+
if (visibleLength(text) <= width) return text;
|
|
161
|
+
if (width <= 1) return "…";
|
|
162
|
+
|
|
163
|
+
let output = "";
|
|
164
|
+
let visible = 0;
|
|
165
|
+
for (let index = 0; index < text.length && visible < width - 1; ) {
|
|
166
|
+
if (text.charCodeAt(index) === 0x1b) {
|
|
167
|
+
ANSI_PATTERN.lastIndex = index;
|
|
168
|
+
const match = ANSI_PATTERN.exec(text);
|
|
169
|
+
if (match && match.index === index) {
|
|
170
|
+
output += match[0];
|
|
171
|
+
index = ANSI_PATTERN.lastIndex;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
output += text[index];
|
|
176
|
+
visible += 1;
|
|
177
|
+
index += 1;
|
|
178
|
+
}
|
|
179
|
+
return `${output}…`;
|
|
151
180
|
}
|
|
152
181
|
|
|
153
182
|
class SingleLineComponent {
|
|
154
|
-
|
|
183
|
+
constructor(private readonly text: string) {}
|
|
155
184
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
185
|
+
invalidate(): void {
|
|
186
|
+
// Static one-line component.
|
|
187
|
+
}
|
|
159
188
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
189
|
+
render(width: number): string[] {
|
|
190
|
+
return [clip(this.text, width)];
|
|
191
|
+
}
|
|
163
192
|
}
|
|
164
193
|
|
|
165
194
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
166
|
-
|
|
195
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
167
196
|
}
|
|
168
197
|
|
|
169
|
-
function hasAnyKey(
|
|
170
|
-
|
|
198
|
+
function hasAnyKey(
|
|
199
|
+
input: Record<string, unknown>,
|
|
200
|
+
keys: readonly string[],
|
|
201
|
+
): boolean {
|
|
202
|
+
return keys.some((key) => Object.hasOwn(input, key));
|
|
171
203
|
}
|
|
172
204
|
|
|
173
205
|
function formatKeyList(keys: readonly string[]): string {
|
|
174
|
-
|
|
206
|
+
return keys.map((key) => `"${key}"`).join(", ");
|
|
175
207
|
}
|
|
176
208
|
|
|
177
209
|
function getExecuteParams(first: unknown, second: unknown): unknown {
|
|
178
|
-
|
|
210
|
+
return second === undefined ? first : second;
|
|
179
211
|
}
|
|
180
212
|
|
|
181
213
|
function getCwd(ctx: unknown): string {
|
|
182
|
-
|
|
183
|
-
|
|
214
|
+
if (isRecord(ctx) && typeof ctx.cwd === "string" && ctx.cwd.length > 0)
|
|
215
|
+
return ctx.cwd;
|
|
216
|
+
return process.cwd();
|
|
184
217
|
}
|
|
185
218
|
|
|
186
219
|
function parentSessionIdFromCtx(ctx: unknown): string | undefined {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
220
|
+
if (!isRecord(ctx)) return undefined;
|
|
221
|
+
const sessionManager = ctx.sessionManager;
|
|
222
|
+
if (
|
|
223
|
+
!isRecord(sessionManager) ||
|
|
224
|
+
typeof sessionManager.getSessionId !== "function"
|
|
225
|
+
)
|
|
226
|
+
return undefined;
|
|
227
|
+
try {
|
|
228
|
+
const id = (sessionManager.getSessionId as () => unknown)();
|
|
229
|
+
return typeof id === "string" && id.length > 0 ? id : undefined;
|
|
230
|
+
} catch {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
196
233
|
}
|
|
197
234
|
|
|
198
|
-
function textResult(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
235
|
+
function textResult(
|
|
236
|
+
payload: unknown,
|
|
237
|
+
isError: boolean,
|
|
238
|
+
details?: unknown,
|
|
239
|
+
): ToolResult {
|
|
240
|
+
return {
|
|
241
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
242
|
+
details,
|
|
243
|
+
isError,
|
|
244
|
+
};
|
|
204
245
|
}
|
|
205
246
|
|
|
206
247
|
function artifactSummary(artifacts: readonly ArtifactRef[]) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
248
|
+
return artifacts.map((artifact) => ({
|
|
249
|
+
type: artifact.type,
|
|
250
|
+
path: artifact.path,
|
|
251
|
+
...(artifact.bytes === undefined ? {} : { bytes: artifact.bytes }),
|
|
252
|
+
}));
|
|
212
253
|
}
|
|
213
254
|
|
|
214
255
|
function compactResult(result: ResultEnvelope, error?: string) {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
256
|
+
return {
|
|
257
|
+
tool: TOOL_NAME,
|
|
258
|
+
backend: result.backend,
|
|
259
|
+
status: result.status,
|
|
260
|
+
failureKind: result.failureKind,
|
|
261
|
+
...(error === undefined ? {} : { error }),
|
|
262
|
+
runId: result.runId,
|
|
263
|
+
attemptId: result.attemptId,
|
|
264
|
+
...(result.taskId === undefined ? {} : { taskId: result.taskId }),
|
|
265
|
+
...(result.correlationId === undefined
|
|
266
|
+
? {}
|
|
267
|
+
: { correlationId: result.correlationId }),
|
|
268
|
+
durationMs: result.durationMs,
|
|
269
|
+
exitCode: result.exitCode,
|
|
270
|
+
signal: result.signal,
|
|
271
|
+
sandbox: result.sandbox,
|
|
272
|
+
workspace: result.workspace,
|
|
273
|
+
...(result.tmux === undefined ? {} : { tmux: result.tmux }),
|
|
274
|
+
...(result.completion === undefined
|
|
275
|
+
? {}
|
|
276
|
+
: { completion: result.completion }),
|
|
277
|
+
metadata: result.metadata,
|
|
278
|
+
artifacts: artifactSummary(result.artifacts),
|
|
279
|
+
};
|
|
235
280
|
}
|
|
236
281
|
|
|
237
282
|
function displayText(value: unknown, maxLength: number): string | undefined {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
283
|
+
if (typeof value !== "string") return undefined;
|
|
284
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
285
|
+
if (!normalized) return undefined;
|
|
286
|
+
return normalized.length <= maxLength
|
|
287
|
+
? normalized
|
|
288
|
+
: `${normalized.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
242
289
|
}
|
|
243
290
|
|
|
244
291
|
function subagentCallSummary(input: unknown): string {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
292
|
+
const args = isRecord(input) ? input : {};
|
|
293
|
+
const action = displayText(args.action, 16) ?? "run";
|
|
294
|
+
const mode =
|
|
295
|
+
displayText(args.mode, 16) ??
|
|
296
|
+
(Array.isArray(args.tasks) ? "parallel" : "single");
|
|
297
|
+
const pieces = [`subagent ${action}`];
|
|
298
|
+
|
|
299
|
+
if (action === "run") {
|
|
300
|
+
pieces.push(mode);
|
|
301
|
+
if (Array.isArray(args.tasks))
|
|
302
|
+
pieces.push(
|
|
303
|
+
`${args.tasks.length} run${args.tasks.length === 1 ? "" : "s"}`,
|
|
304
|
+
);
|
|
305
|
+
const agent = displayText(args.agent, 24);
|
|
306
|
+
if (agent) pieces.push(agent);
|
|
307
|
+
const task = displayText(args.task, 48);
|
|
308
|
+
if (task) pieces.push(task);
|
|
309
|
+
const asyncMode =
|
|
310
|
+
args.async === true ? "async" : displayText(args.onComplete, 16);
|
|
311
|
+
if (args.failFast === true || args.cancelSiblingsOnFailure === true)
|
|
312
|
+
pieces.push("fail-fast");
|
|
313
|
+
if (asyncMode) pieces.push(asyncMode);
|
|
314
|
+
} else {
|
|
315
|
+
const runId = displayText(args.runId, 28);
|
|
316
|
+
if (runId) pieces.push(runId);
|
|
317
|
+
const attemptId =
|
|
318
|
+
displayText(args.attemptId, 16) ?? displayText(args.taskId, 16);
|
|
319
|
+
if (attemptId) pieces.push(attemptId);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return pieces.filter(Boolean).join(" · ");
|
|
267
323
|
}
|
|
268
324
|
|
|
269
325
|
function validationFailure(failure: ResolveValidationFailure): ToolResult {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
326
|
+
return textResult(
|
|
327
|
+
{
|
|
328
|
+
tool: TOOL_NAME,
|
|
329
|
+
backend: failure.backend,
|
|
330
|
+
status: failure.status,
|
|
331
|
+
failureKind: failure.failureKind,
|
|
332
|
+
error: failure.error,
|
|
333
|
+
},
|
|
334
|
+
true,
|
|
335
|
+
{ resolved: failure },
|
|
336
|
+
);
|
|
281
337
|
}
|
|
282
338
|
|
|
283
339
|
class InputValidationError extends Error {
|
|
284
|
-
|
|
340
|
+
readonly failureKind = "validation" as const;
|
|
285
341
|
}
|
|
286
342
|
|
|
287
343
|
function optionalString(value: unknown, fieldName: string): string | undefined {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
344
|
+
if (value === undefined) return undefined;
|
|
345
|
+
if (typeof value !== "string" || value.length === 0)
|
|
346
|
+
throw new InputValidationError(
|
|
347
|
+
`${fieldName} must be a non-empty string when provided.`,
|
|
348
|
+
);
|
|
349
|
+
return value;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function optionalPositiveNumber(
|
|
353
|
+
value: unknown,
|
|
354
|
+
fieldName: string,
|
|
355
|
+
): number | undefined {
|
|
356
|
+
if (value === undefined) return undefined;
|
|
357
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0)
|
|
358
|
+
throw new InputValidationError(
|
|
359
|
+
`${fieldName} must be a positive finite number when provided.`,
|
|
360
|
+
);
|
|
361
|
+
return value;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function lifecycleAction(
|
|
365
|
+
raw: Record<string, unknown>,
|
|
366
|
+
cwd: string,
|
|
367
|
+
): Promise<ToolResult | null> {
|
|
368
|
+
const action = raw.action ?? "run";
|
|
369
|
+
if (action === "run") return null;
|
|
370
|
+
if (
|
|
371
|
+
action !== "status" &&
|
|
372
|
+
action !== "logs" &&
|
|
373
|
+
action !== "wait" &&
|
|
374
|
+
action !== "interrupt" &&
|
|
375
|
+
action !== "mark-background" &&
|
|
376
|
+
action !== "reconcile"
|
|
377
|
+
) {
|
|
378
|
+
throw new InputValidationError(
|
|
379
|
+
'action must be one of "run", "status", "logs", "wait", "interrupt", "mark-background", or "reconcile" when provided.',
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const runId = optionalString(raw.runId, "runId");
|
|
384
|
+
if (runId === undefined)
|
|
385
|
+
throw new InputValidationError(
|
|
386
|
+
`${String(action)} action requires a non-empty runId.`,
|
|
387
|
+
);
|
|
388
|
+
const ref = await resolveRunRef(
|
|
389
|
+
{
|
|
390
|
+
cwd: optionalString(raw.cwd, "cwd"),
|
|
391
|
+
runId,
|
|
392
|
+
attemptId:
|
|
393
|
+
optionalString(raw.attemptId, "attemptId") ??
|
|
394
|
+
optionalString(raw.taskId, "taskId"),
|
|
395
|
+
runsDir: optionalString(raw.runsDir, "runsDir"),
|
|
396
|
+
},
|
|
397
|
+
cwd,
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
if (action === "status") {
|
|
401
|
+
const snapshot = await getRunStatus(ref);
|
|
402
|
+
return textResult(
|
|
403
|
+
{
|
|
404
|
+
tool: TOOL_NAME,
|
|
405
|
+
action,
|
|
406
|
+
status: snapshot === null ? "failed" : snapshot.status,
|
|
407
|
+
snapshot,
|
|
408
|
+
},
|
|
409
|
+
snapshot === null,
|
|
410
|
+
{ snapshot },
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (action === "logs") {
|
|
415
|
+
const snapshot = await getRunLogs(ref);
|
|
416
|
+
return textResult(
|
|
417
|
+
{
|
|
418
|
+
tool: TOOL_NAME,
|
|
419
|
+
action,
|
|
420
|
+
status: snapshot === null ? "failed" : snapshot.status,
|
|
421
|
+
snapshot,
|
|
422
|
+
},
|
|
423
|
+
snapshot === null,
|
|
424
|
+
{ snapshot },
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (action === "mark-background") {
|
|
429
|
+
const record = await setRunDependency(ref, "background");
|
|
430
|
+
await appendRunEvent(ref, {
|
|
431
|
+
type: "run.mark_background",
|
|
432
|
+
status: record.status,
|
|
433
|
+
message: "run marked background",
|
|
434
|
+
});
|
|
435
|
+
const snapshot = await getRunStatus(ref);
|
|
436
|
+
return textResult(
|
|
437
|
+
{
|
|
438
|
+
tool: TOOL_NAME,
|
|
439
|
+
action,
|
|
440
|
+
status: snapshot?.status ?? record.status,
|
|
441
|
+
snapshot,
|
|
442
|
+
},
|
|
443
|
+
false,
|
|
444
|
+
{ snapshot, record },
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (action === "interrupt") {
|
|
449
|
+
const signal = optionalString(raw.signal, "signal") as
|
|
450
|
+
| NodeJS.Signals
|
|
451
|
+
| undefined;
|
|
452
|
+
if (
|
|
453
|
+
signal !== undefined &&
|
|
454
|
+
signal !== "SIGINT" &&
|
|
455
|
+
signal !== "SIGTERM" &&
|
|
456
|
+
signal !== "SIGKILL"
|
|
457
|
+
) {
|
|
458
|
+
throw new InputValidationError(
|
|
459
|
+
'signal must be one of "SIGINT", "SIGTERM", or "SIGKILL" when provided.',
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
const interrupted = await interruptRun({
|
|
463
|
+
cwd: ref.cwd,
|
|
464
|
+
runId,
|
|
465
|
+
runsDir: ref.runsDir,
|
|
466
|
+
attemptId: ref.attemptId,
|
|
467
|
+
reason: optionalString(raw.reason, "reason"),
|
|
468
|
+
signal,
|
|
469
|
+
escalateAfterMs: optionalPositiveNumber(
|
|
470
|
+
raw.escalateAfterMs,
|
|
471
|
+
"escalateAfterMs",
|
|
472
|
+
),
|
|
473
|
+
killAfterMs: optionalPositiveNumber(raw.killAfterMs, "killAfterMs"),
|
|
474
|
+
});
|
|
475
|
+
const snapshot = await getRunStatus(ref);
|
|
476
|
+
const isError =
|
|
477
|
+
interrupted.status === "not-found" ||
|
|
478
|
+
interrupted.status === "unsupported";
|
|
479
|
+
return textResult(
|
|
480
|
+
{
|
|
481
|
+
tool: TOOL_NAME,
|
|
482
|
+
action,
|
|
483
|
+
status: interrupted.status,
|
|
484
|
+
interrupted,
|
|
485
|
+
snapshot,
|
|
486
|
+
},
|
|
487
|
+
isError,
|
|
488
|
+
{ interrupted, snapshot },
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (action === "reconcile") {
|
|
493
|
+
const reconciled = await reconcileSubagentRun(ref);
|
|
494
|
+
const snapshot = await getRunStatus(ref);
|
|
495
|
+
return textResult(
|
|
496
|
+
{
|
|
497
|
+
tool: TOOL_NAME,
|
|
498
|
+
action,
|
|
499
|
+
status: reconciled.status,
|
|
500
|
+
reconciled,
|
|
501
|
+
snapshot,
|
|
502
|
+
},
|
|
503
|
+
reconciled.status === "not-found",
|
|
504
|
+
{ reconciled, snapshot },
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const waited = await waitForRun({
|
|
509
|
+
...ref,
|
|
510
|
+
timeoutMs: optionalPositiveNumber(raw.timeoutMs, "timeoutMs"),
|
|
511
|
+
pollIntervalMs: optionalPositiveNumber(
|
|
512
|
+
raw.pollIntervalMs,
|
|
513
|
+
"pollIntervalMs",
|
|
514
|
+
),
|
|
515
|
+
});
|
|
516
|
+
const isError =
|
|
517
|
+
waited.status !== "completed" || waited.snapshot?.status !== "completed";
|
|
518
|
+
return textResult(
|
|
519
|
+
{
|
|
520
|
+
tool: TOOL_NAME,
|
|
521
|
+
action,
|
|
522
|
+
status: waited.status,
|
|
523
|
+
snapshot: waited.snapshot,
|
|
524
|
+
},
|
|
525
|
+
isError,
|
|
526
|
+
{ waited },
|
|
527
|
+
);
|
|
361
528
|
}
|
|
362
529
|
|
|
363
530
|
function executionMode(input: ResolveInput): ExecutionMode {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function unsupportedPathError(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
531
|
+
if (input.mode !== undefined) return input.mode;
|
|
532
|
+
if (input.tasks !== undefined) return "parallel";
|
|
533
|
+
return "single";
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function unsupportedPathError(
|
|
537
|
+
raw: Record<string, unknown>,
|
|
538
|
+
input: ResolveInput,
|
|
539
|
+
backend: ResolvedBackend,
|
|
540
|
+
): string | undefined {
|
|
541
|
+
const mode = executionMode(input);
|
|
542
|
+
const unknownKeys = Object.keys(raw).filter(
|
|
543
|
+
(key) => !SUPPORTED_KEYS.has(key),
|
|
544
|
+
);
|
|
545
|
+
if (unknownKeys.length > 0) {
|
|
546
|
+
return `unsupported subagent option(s): ${formatKeyList(unknownKeys)}.`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (mode === "parallel") {
|
|
550
|
+
return input.tasks === undefined
|
|
551
|
+
? "parallel mode requires a non-empty tasks array."
|
|
552
|
+
: undefined;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (backend !== "inline" && backend !== "headless" && backend !== "tmux") {
|
|
556
|
+
return `backend "${backend}" is not implemented in this MVP; only inline, headless, and tmux execution are supported.`;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (hasAnyKey(raw, AGENT_TASK_KEYS) && input.task === undefined) {
|
|
560
|
+
return `${backend} agent/task execution requires a non-empty "task".`;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (!hasAnyKey(raw, AGENT_TASK_KEYS)) {
|
|
564
|
+
return `${backend} execution requires agent/task input.`;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return undefined;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async function writeUnsupportedResult(
|
|
571
|
+
cwd: string,
|
|
572
|
+
backend: ResolvedBackend,
|
|
573
|
+
input: ResolveInput,
|
|
574
|
+
): Promise<ResultEnvelope> {
|
|
575
|
+
const startedAt = new Date();
|
|
576
|
+
const store = await createAttemptArtifactStore({
|
|
577
|
+
cwd,
|
|
578
|
+
runsDir: input.runsDir,
|
|
579
|
+
});
|
|
580
|
+
const sandboxed = input.sandbox !== undefined && input.sandbox !== null;
|
|
581
|
+
return await store.writeResult({
|
|
582
|
+
backend,
|
|
583
|
+
status: "failed",
|
|
584
|
+
failureKind: "validation",
|
|
585
|
+
cwd,
|
|
586
|
+
startedAt,
|
|
587
|
+
completedAt: new Date(),
|
|
588
|
+
workspace: { mode: "shared", cwd },
|
|
589
|
+
sandbox: { enabled: sandboxed },
|
|
590
|
+
exitCode: null,
|
|
591
|
+
signal: null,
|
|
592
|
+
artifacts: [],
|
|
593
|
+
correlationId: input.correlationId,
|
|
594
|
+
metadata: { contextLengthExceeded: false },
|
|
595
|
+
});
|
|
414
596
|
}
|
|
415
597
|
|
|
416
598
|
interface ToolUpdateCallback {
|
|
417
|
-
|
|
599
|
+
(update: { content: ToolTextContent[]; details: unknown }): void;
|
|
418
600
|
}
|
|
419
601
|
|
|
420
602
|
interface NotificationContext {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
603
|
+
ui?: {
|
|
604
|
+
notify?: (message: string, level?: "info" | "warning" | "error") => void;
|
|
605
|
+
};
|
|
424
606
|
}
|
|
425
607
|
|
|
426
608
|
interface ProjectAgentApprovalContext extends NotificationContext {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
609
|
+
hasUI?: boolean;
|
|
610
|
+
ui?: NotificationContext["ui"] & {
|
|
611
|
+
confirm?: (title: string, message?: string) => Promise<boolean> | boolean;
|
|
612
|
+
};
|
|
431
613
|
}
|
|
432
614
|
|
|
433
615
|
interface AgentRequest {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
616
|
+
agent: string;
|
|
617
|
+
cwd?: string;
|
|
618
|
+
agentScope?: ResolveInput["agentScope"];
|
|
619
|
+
confirmProjectAgents?: boolean;
|
|
438
620
|
}
|
|
439
621
|
|
|
440
622
|
function agentRequests(input: ResolveInput): AgentRequest[] {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
623
|
+
if (input.tasks !== undefined) {
|
|
624
|
+
return input.tasks
|
|
625
|
+
.filter(
|
|
626
|
+
(task): task is typeof task & { agent: string } =>
|
|
627
|
+
typeof task.agent === "string" && task.agent.length > 0,
|
|
628
|
+
)
|
|
629
|
+
.map((task) => ({
|
|
630
|
+
agent: task.agent,
|
|
631
|
+
cwd: task.cwd,
|
|
632
|
+
agentScope: task.agentScope ?? input.agentScope,
|
|
633
|
+
confirmProjectAgents:
|
|
634
|
+
task.confirmProjectAgents ?? input.confirmProjectAgents ?? false,
|
|
635
|
+
}));
|
|
636
|
+
}
|
|
637
|
+
return typeof input.agent === "string" && input.agent.length > 0
|
|
638
|
+
? [
|
|
639
|
+
{
|
|
640
|
+
agent: input.agent,
|
|
641
|
+
cwd: input.cwd,
|
|
642
|
+
agentScope: input.agentScope,
|
|
643
|
+
confirmProjectAgents: input.confirmProjectAgents ?? false,
|
|
644
|
+
},
|
|
645
|
+
]
|
|
646
|
+
: [];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async function maybeConfirmProjectAgents(
|
|
650
|
+
input: ResolveInput,
|
|
651
|
+
cwd: string,
|
|
652
|
+
ctx?: ProjectAgentApprovalContext,
|
|
653
|
+
): Promise<void> {
|
|
654
|
+
const projectAgents: AgentDefinition[] = [];
|
|
655
|
+
for (const request of agentRequests(input)) {
|
|
656
|
+
if (
|
|
657
|
+
request.confirmProjectAgents === false ||
|
|
658
|
+
request.agentScope === "global"
|
|
659
|
+
)
|
|
660
|
+
continue;
|
|
661
|
+
const requestCwd = resolve(cwd, request.cwd ?? ".");
|
|
662
|
+
const agent = await loadAgentByName(
|
|
663
|
+
request.agent,
|
|
664
|
+
requestCwd,
|
|
665
|
+
request.agentScope,
|
|
666
|
+
);
|
|
667
|
+
if (
|
|
668
|
+
agent?.source === "project" &&
|
|
669
|
+
!projectAgents.some(
|
|
670
|
+
(candidate) => candidate.sourcePath === agent.sourcePath,
|
|
671
|
+
)
|
|
672
|
+
) {
|
|
673
|
+
projectAgents.push(agent);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if (projectAgents.length === 0) return;
|
|
677
|
+
|
|
678
|
+
const names = projectAgents.map((agent) => agent.displayName).join(", ");
|
|
679
|
+
const sources = projectAgents.map((agent) => agent.sourcePath).join("\n");
|
|
680
|
+
if (ctx?.hasUI && ctx.ui?.confirm) {
|
|
681
|
+
const approved = await ctx.ui.confirm(
|
|
682
|
+
"Run project-local subagent definitions?",
|
|
683
|
+
`Agents: ${names}\nSources:\n${sources}\n\nProject agents are repository-controlled. Continue only for trusted repositories.`,
|
|
684
|
+
);
|
|
685
|
+
if (!approved)
|
|
686
|
+
throw new Error(
|
|
687
|
+
"Canceled: project-local subagent definitions were not approved.",
|
|
688
|
+
);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
throw new Error(
|
|
693
|
+
"Project-local subagent definitions require interactive approval or confirmProjectAgents:false.",
|
|
694
|
+
);
|
|
480
695
|
}
|
|
481
696
|
|
|
482
697
|
function completionPayload(result: ResultEnvelope, mode: ExecutionMode) {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
function notifyCompletion(input: ResolveInput, result: ResultEnvelope, mode: ExecutionMode, onUpdate?: ToolUpdateCallback, ctx?: NotificationContext): number {
|
|
497
|
-
if (input.onComplete !== "notify") return 0;
|
|
498
|
-
const payload = completionPayload(result, mode);
|
|
499
|
-
let updatesSent = 0;
|
|
500
|
-
try {
|
|
501
|
-
onUpdate?.({ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }], details: payload });
|
|
502
|
-
if (onUpdate) updatesSent += 1;
|
|
503
|
-
} catch {
|
|
504
|
-
// Completion notifications must not change the task result.
|
|
505
|
-
}
|
|
506
|
-
try {
|
|
507
|
-
ctx?.ui?.notify?.(`subagent ${result.runId}/${result.attemptId} ${result.status}`, result.status === "completed" ? "info" : "warning");
|
|
508
|
-
if (ctx?.ui?.notify) updatesSent += 1;
|
|
509
|
-
} catch {
|
|
510
|
-
// Completion notifications must not change the task result.
|
|
511
|
-
}
|
|
512
|
-
return updatesSent;
|
|
698
|
+
return {
|
|
699
|
+
tool: TOOL_NAME,
|
|
700
|
+
event: "complete",
|
|
701
|
+
mode,
|
|
702
|
+
runId: result.runId,
|
|
703
|
+
attemptId: result.attemptId,
|
|
704
|
+
backend: result.backend,
|
|
705
|
+
status: result.status,
|
|
706
|
+
failureKind: result.failureKind,
|
|
707
|
+
artifacts: artifactSummary(result.artifacts),
|
|
708
|
+
};
|
|
513
709
|
}
|
|
514
710
|
|
|
711
|
+
function notifyCompletion(
|
|
712
|
+
input: ResolveInput,
|
|
713
|
+
result: ResultEnvelope,
|
|
714
|
+
mode: ExecutionMode,
|
|
715
|
+
onUpdate?: ToolUpdateCallback,
|
|
716
|
+
ctx?: NotificationContext,
|
|
717
|
+
): number {
|
|
718
|
+
if (input.onComplete !== "notify") return 0;
|
|
719
|
+
const payload = completionPayload(result, mode);
|
|
720
|
+
let updatesSent = 0;
|
|
721
|
+
try {
|
|
722
|
+
onUpdate?.({
|
|
723
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
724
|
+
details: payload,
|
|
725
|
+
});
|
|
726
|
+
if (onUpdate) updatesSent += 1;
|
|
727
|
+
} catch {
|
|
728
|
+
// Completion notifications must not change the task result.
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
ctx?.ui?.notify?.(
|
|
732
|
+
`subagent ${result.runId}/${result.attemptId} ${result.status}`,
|
|
733
|
+
result.status === "completed" ? "info" : "warning",
|
|
734
|
+
);
|
|
735
|
+
if (ctx?.ui?.notify) updatesSent += 1;
|
|
736
|
+
} catch {
|
|
737
|
+
// Completion notifications must not change the task result.
|
|
738
|
+
}
|
|
739
|
+
return updatesSent;
|
|
740
|
+
}
|
|
515
741
|
|
|
516
742
|
export default function registerSubagentEngine(pi: ExtensionAPI) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
743
|
+
if (typeof pi.registerCommand === "function") {
|
|
744
|
+
pi.registerCommand("subagent", {
|
|
745
|
+
description:
|
|
746
|
+
"Subagent utilities. Use `/subagent panel` to open the live status panel.",
|
|
747
|
+
getArgumentCompletions(prefix) {
|
|
748
|
+
const items = [
|
|
749
|
+
{
|
|
750
|
+
value: "panel",
|
|
751
|
+
label: "panel",
|
|
752
|
+
description: "Open the live Subagents status panel",
|
|
753
|
+
},
|
|
754
|
+
];
|
|
755
|
+
const filtered = items.filter((item) =>
|
|
756
|
+
item.value.startsWith(prefix.trim()),
|
|
757
|
+
);
|
|
758
|
+
return filtered.length > 0 ? filtered : null;
|
|
759
|
+
},
|
|
760
|
+
async handler(args, ctx) {
|
|
761
|
+
const commandArgs = args.trim();
|
|
762
|
+
const normalizedArgs = commandArgs
|
|
763
|
+
.replace(/^\/?subagent\b\s*/, "")
|
|
764
|
+
.trim();
|
|
765
|
+
if (normalizedArgs !== "panel") {
|
|
766
|
+
ctx.ui.notify?.("Usage: /subagent panel", "warning");
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
await showSubagentPanel(ctx);
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
pi.registerTool({
|
|
775
|
+
name: TOOL_NAME,
|
|
776
|
+
label: "Subagent",
|
|
777
|
+
description:
|
|
778
|
+
"Subagent engine. Executes headless/tmux/inline workers; supports workspace:auto/worktree isolation, bounded parallel fanout, async lifecycle lookup, mark-background, reconcile, and conservative interrupt. Workspaces default to shared; set worktree:true for parallel tasks that mutate files.",
|
|
779
|
+
parameters: Type.Object({
|
|
780
|
+
backend: Type.Optional(
|
|
781
|
+
Type.Union(BACKENDS.map((value) => Type.Literal(value))),
|
|
782
|
+
),
|
|
783
|
+
visible: Type.Optional(Type.Boolean()),
|
|
784
|
+
sandbox: Type.Optional(SANDBOX_SCHEMA),
|
|
785
|
+
agent: Type.Optional(Type.String({ minLength: 1 })),
|
|
786
|
+
task: Type.Optional(Type.String({ minLength: 1 })),
|
|
787
|
+
roleContext: Type.Optional(Type.String({ minLength: 1 })),
|
|
788
|
+
agentScope: Type.Optional(
|
|
789
|
+
Type.Union(AGENT_SCOPES.map((value) => Type.Literal(value))),
|
|
790
|
+
),
|
|
791
|
+
confirmProjectAgents: Type.Optional(Type.Boolean()),
|
|
792
|
+
mode: Type.Optional(
|
|
793
|
+
Type.Union(EXECUTION_MODES.map((value) => Type.Literal(value))),
|
|
794
|
+
),
|
|
795
|
+
tasks: Type.Optional(Type.Array(SUBAGENT_TASK_SCHEMA, { minItems: 1 })),
|
|
796
|
+
concurrency: Type.Optional(
|
|
797
|
+
Type.Number({
|
|
798
|
+
minimum: 1,
|
|
799
|
+
description: `Maximum parallel runs to launch at once. Default ${DEFAULT_PARALLEL_CONCURRENCY}.`,
|
|
800
|
+
}),
|
|
801
|
+
),
|
|
802
|
+
failFast: Type.Optional(
|
|
803
|
+
Type.Boolean({
|
|
804
|
+
description:
|
|
805
|
+
"For synchronous parallel runs, stop scheduling additional siblings after the first failed result.",
|
|
806
|
+
}),
|
|
807
|
+
),
|
|
808
|
+
cancelSiblingsOnFailure: Type.Optional(
|
|
809
|
+
Type.Boolean({
|
|
810
|
+
description:
|
|
811
|
+
"For synchronous parallel runs, abort already-running siblings after the first failed result. Implies fail-fast scheduling.",
|
|
812
|
+
}),
|
|
813
|
+
),
|
|
814
|
+
asyncDependency: Type.Optional(
|
|
815
|
+
Type.Union(
|
|
816
|
+
ASYNC_DEPENDENCIES.map((value) => Type.Literal(value)),
|
|
817
|
+
{
|
|
818
|
+
description:
|
|
819
|
+
"Whether an async run is needed before final, background, or unclassified.",
|
|
820
|
+
},
|
|
821
|
+
),
|
|
822
|
+
),
|
|
823
|
+
workspace: Type.Optional(
|
|
824
|
+
Type.Union([
|
|
825
|
+
Type.Union(WORKSPACE_MODES.map((value) => Type.Literal(value))),
|
|
826
|
+
Type.Object({
|
|
827
|
+
mode: Type.Optional(
|
|
828
|
+
Type.Union(WORKSPACE_MODES.map((value) => Type.Literal(value))),
|
|
829
|
+
),
|
|
830
|
+
path: Type.Optional(Type.String({ minLength: 1 })),
|
|
831
|
+
}),
|
|
832
|
+
]),
|
|
833
|
+
),
|
|
834
|
+
worktree: Type.Optional(
|
|
835
|
+
Type.Union([Type.Boolean(), Type.String({ minLength: 1 })]),
|
|
836
|
+
),
|
|
837
|
+
worktreePolicy: Type.Optional(
|
|
838
|
+
Type.Union(WORKTREE_POLICIES.map((value) => Type.Literal(value))),
|
|
839
|
+
),
|
|
840
|
+
cwd: Type.Optional(Type.String({ minLength: 1 })),
|
|
841
|
+
async: Type.Optional(Type.Boolean()),
|
|
842
|
+
onComplete: Type.Optional(
|
|
843
|
+
Type.Union(ON_COMPLETE_ACTIONS.map((value) => Type.Literal(value))),
|
|
844
|
+
),
|
|
845
|
+
timeoutMs: Type.Optional(Type.Number({ exclusiveMinimum: 0 })),
|
|
846
|
+
model: Type.Optional(
|
|
847
|
+
Type.String({
|
|
848
|
+
minLength: 1,
|
|
849
|
+
description:
|
|
850
|
+
"Optional Pi model pattern or provider/model id for model-backed subagents.",
|
|
851
|
+
}),
|
|
852
|
+
),
|
|
853
|
+
tools: Type.Optional(
|
|
854
|
+
Type.Array(Type.String({ minLength: 1 }), {
|
|
855
|
+
description:
|
|
856
|
+
"Optional tool allowlist. With a named agent this may only narrow the agent-declared tools. Use [] to disable tools.",
|
|
857
|
+
}),
|
|
858
|
+
),
|
|
859
|
+
systemPrompt: Type.Optional(
|
|
860
|
+
Type.String({
|
|
861
|
+
minLength: 1,
|
|
862
|
+
description:
|
|
863
|
+
"Optional compiled system prompt. When provided, it replaces the named agent prompt body but not agent frontmatter policy.",
|
|
864
|
+
}),
|
|
865
|
+
),
|
|
866
|
+
skills: Type.Optional(
|
|
867
|
+
Type.Array(Type.String({ minLength: 1 }), {
|
|
868
|
+
description:
|
|
869
|
+
"Additional Pi skill paths to load. Omit to use ambient discovery; pass [] to disable child skills.",
|
|
870
|
+
}),
|
|
871
|
+
),
|
|
872
|
+
extensions: Type.Optional(
|
|
873
|
+
Type.Array(Type.String({ minLength: 1 }), {
|
|
874
|
+
description:
|
|
875
|
+
"Additional Pi extension paths to load. Omit to use ambient discovery; pass [] to disable child extensions.",
|
|
876
|
+
}),
|
|
877
|
+
),
|
|
878
|
+
runsDir: Type.Optional(
|
|
879
|
+
Type.String({
|
|
880
|
+
minLength: 1,
|
|
881
|
+
description: "Safe relative run/artifact root under cwd.",
|
|
882
|
+
}),
|
|
883
|
+
),
|
|
884
|
+
correlationId: Type.Optional(
|
|
885
|
+
Type.String({
|
|
886
|
+
minLength: 1,
|
|
887
|
+
description: "External correlation label; no aggregation semantics.",
|
|
888
|
+
}),
|
|
889
|
+
),
|
|
890
|
+
captureToolCalls: Type.Optional(
|
|
891
|
+
Type.Boolean({
|
|
892
|
+
description:
|
|
893
|
+
"Capture redacted child tool-call telemetry (tool names, durations, statuses; no args/results) as run artifacts. Default false.",
|
|
894
|
+
}),
|
|
895
|
+
),
|
|
896
|
+
thinking: Type.Optional(
|
|
897
|
+
Type.Union(
|
|
898
|
+
THINKING_LEVELS.map((value) => Type.Literal(value)),
|
|
899
|
+
{ description: "Optional Pi thinking/reasoning level." },
|
|
900
|
+
),
|
|
901
|
+
),
|
|
902
|
+
thinkingLevel: Type.Optional(
|
|
903
|
+
Type.Union(
|
|
904
|
+
THINKING_LEVELS.map((value) => Type.Literal(value)),
|
|
905
|
+
{ description: "Alias for thinking." },
|
|
906
|
+
),
|
|
907
|
+
),
|
|
908
|
+
reasoningLevel: Type.Optional(
|
|
909
|
+
Type.Union(
|
|
910
|
+
THINKING_LEVELS.map((value) => Type.Literal(value)),
|
|
911
|
+
{ description: "Alias for thinking." },
|
|
912
|
+
),
|
|
913
|
+
),
|
|
914
|
+
action: Type.Optional(
|
|
915
|
+
Type.Union(
|
|
916
|
+
[
|
|
917
|
+
Type.Literal("run"),
|
|
918
|
+
Type.Literal("status"),
|
|
919
|
+
Type.Literal("logs"),
|
|
920
|
+
Type.Literal("wait"),
|
|
921
|
+
Type.Literal("interrupt"),
|
|
922
|
+
Type.Literal("mark-background"),
|
|
923
|
+
Type.Literal("reconcile"),
|
|
924
|
+
],
|
|
925
|
+
{
|
|
926
|
+
default: "run",
|
|
927
|
+
description:
|
|
928
|
+
'What to do. Default "run" starts a new subagent. status/logs/wait/interrupt/mark-background/reconcile operate on an existing runId.',
|
|
929
|
+
},
|
|
930
|
+
),
|
|
931
|
+
),
|
|
932
|
+
runId: Type.Optional(Type.String({ minLength: 1 })),
|
|
933
|
+
attemptId: Type.Optional(Type.String({ minLength: 1 })),
|
|
934
|
+
taskId: Type.Optional(
|
|
935
|
+
Type.String({
|
|
936
|
+
minLength: 1,
|
|
937
|
+
description: "Deprecated alias for attemptId when reading old runs.",
|
|
938
|
+
}),
|
|
939
|
+
),
|
|
940
|
+
pollIntervalMs: Type.Optional(Type.Number({ exclusiveMinimum: 0 })),
|
|
941
|
+
reason: Type.Optional(Type.String({ minLength: 1 })),
|
|
942
|
+
signal: Type.Optional(
|
|
943
|
+
Type.Union([
|
|
944
|
+
Type.Literal("SIGINT"),
|
|
945
|
+
Type.Literal("SIGTERM"),
|
|
946
|
+
Type.Literal("SIGKILL"),
|
|
947
|
+
]),
|
|
948
|
+
),
|
|
949
|
+
escalateAfterMs: Type.Optional(Type.Number({ exclusiveMinimum: 0 })),
|
|
950
|
+
killAfterMs: Type.Optional(Type.Number({ exclusiveMinimum: 0 })),
|
|
951
|
+
}),
|
|
952
|
+
renderCall(args, theme) {
|
|
953
|
+
const title = theme.fg("toolTitle", theme.bold("subagent"));
|
|
954
|
+
const summary = subagentCallSummary(args);
|
|
955
|
+
const rest = summary.startsWith("subagent ")
|
|
956
|
+
? summary.slice("subagent ".length)
|
|
957
|
+
: summary;
|
|
958
|
+
return new SingleLineComponent(`${title} ${theme.fg("muted", rest)}`);
|
|
959
|
+
},
|
|
960
|
+
async execute(toolCallIdOrArgs, maybeParams, signal, onUpdate, ctx) {
|
|
961
|
+
const params = getExecuteParams(toolCallIdOrArgs, maybeParams);
|
|
962
|
+
const cwd = getCwd(ctx);
|
|
963
|
+
|
|
964
|
+
try {
|
|
965
|
+
const raw = isRecord(params) ? params : {};
|
|
966
|
+
const lifecycle = await lifecycleAction(raw, cwd);
|
|
967
|
+
if (lifecycle !== null) return lifecycle;
|
|
968
|
+
|
|
969
|
+
const validation = validateResolveInput(params);
|
|
970
|
+
if (!validation.ok) return validationFailure(validation.failure);
|
|
971
|
+
|
|
972
|
+
const parentSessionId = parentSessionIdFromCtx(ctx);
|
|
973
|
+
if (parentSessionId !== undefined)
|
|
974
|
+
validation.input.parentSessionId = parentSessionId;
|
|
975
|
+
|
|
976
|
+
const resolved = resolveBackend(validation.input);
|
|
977
|
+
if (resolved.status === "failed") return validationFailure(resolved);
|
|
978
|
+
|
|
979
|
+
const unsupportedError = unsupportedPathError(
|
|
980
|
+
raw,
|
|
981
|
+
validation.input,
|
|
982
|
+
resolved.backend,
|
|
983
|
+
);
|
|
984
|
+
if (unsupportedError) {
|
|
985
|
+
const result = await writeUnsupportedResult(
|
|
986
|
+
cwd,
|
|
987
|
+
resolved.backend,
|
|
988
|
+
validation.input,
|
|
989
|
+
);
|
|
990
|
+
return textResult(compactResult(result, unsupportedError), true, {
|
|
991
|
+
result,
|
|
992
|
+
resolved,
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const runCwd = validation.input.cwd ?? cwd;
|
|
997
|
+
await maybeConfirmProjectAgents(
|
|
998
|
+
validation.input,
|
|
999
|
+
runCwd,
|
|
1000
|
+
ctx as ProjectAgentApprovalContext,
|
|
1001
|
+
);
|
|
1002
|
+
const mode = executionMode(validation.input);
|
|
1003
|
+
const asyncRequested =
|
|
1004
|
+
validation.input.async === true ||
|
|
1005
|
+
validation.input.onComplete === "detach" ||
|
|
1006
|
+
validation.input.onComplete === "notify";
|
|
1007
|
+
if (mode === "parallel") {
|
|
1008
|
+
const parallel = asyncRequested
|
|
1009
|
+
? await startAsyncParallelSubagentRuns(
|
|
1010
|
+
validation.input,
|
|
1011
|
+
runCwd,
|
|
1012
|
+
signal,
|
|
1013
|
+
(completed, completedMode) =>
|
|
1014
|
+
notifyCompletion(
|
|
1015
|
+
validation.input,
|
|
1016
|
+
completed,
|
|
1017
|
+
completedMode,
|
|
1018
|
+
onUpdate,
|
|
1019
|
+
ctx as NotificationContext,
|
|
1020
|
+
),
|
|
1021
|
+
)
|
|
1022
|
+
: await runParallelSubagentTasks(validation.input, runCwd, signal);
|
|
1023
|
+
const runs = parallel.results.map((result) => compactResult(result));
|
|
1024
|
+
const failed =
|
|
1025
|
+
!asyncRequested &&
|
|
1026
|
+
(parallel.failFastTriggered ||
|
|
1027
|
+
parallel.results.some((result) => result.status !== "completed"));
|
|
1028
|
+
return textResult(
|
|
1029
|
+
{
|
|
1030
|
+
tool: TOOL_NAME,
|
|
1031
|
+
mode: "parallel",
|
|
1032
|
+
status: failed
|
|
1033
|
+
? "failed"
|
|
1034
|
+
: asyncRequested
|
|
1035
|
+
? "running"
|
|
1036
|
+
: "completed",
|
|
1037
|
+
runIds: parallel.runIds,
|
|
1038
|
+
concurrencyLimit: parallel.concurrency,
|
|
1039
|
+
totalTasks: parallel.totalTasks,
|
|
1040
|
+
startedCount: parallel.startedCount,
|
|
1041
|
+
skippedCount: parallel.skippedCount,
|
|
1042
|
+
failFastTriggered: parallel.failFastTriggered,
|
|
1043
|
+
runs,
|
|
1044
|
+
},
|
|
1045
|
+
failed,
|
|
1046
|
+
{ results: parallel.results, resolved },
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (asyncRequested) {
|
|
1051
|
+
const result = await startAsyncSubagentRun({
|
|
1052
|
+
input: validation.input,
|
|
1053
|
+
cwd: runCwd,
|
|
1054
|
+
backend: resolved.backend,
|
|
1055
|
+
signal,
|
|
1056
|
+
onComplete: (completed, completedMode) =>
|
|
1057
|
+
notifyCompletion(
|
|
1058
|
+
validation.input,
|
|
1059
|
+
completed,
|
|
1060
|
+
completedMode,
|
|
1061
|
+
onUpdate,
|
|
1062
|
+
ctx as NotificationContext,
|
|
1063
|
+
),
|
|
1064
|
+
});
|
|
1065
|
+
return textResult(compactResult(result), false, { result, resolved });
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const result = await runSubagentTask({
|
|
1069
|
+
input: validation.input,
|
|
1070
|
+
cwd: runCwd,
|
|
1071
|
+
signal,
|
|
1072
|
+
});
|
|
1073
|
+
return textResult(
|
|
1074
|
+
compactResult(result),
|
|
1075
|
+
result.status !== "completed",
|
|
1076
|
+
{ result, resolved },
|
|
1077
|
+
);
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1080
|
+
const failureKind =
|
|
1081
|
+
error instanceof WorkspacePolicyError ||
|
|
1082
|
+
error instanceof InputValidationError
|
|
1083
|
+
? error.failureKind
|
|
1084
|
+
: typeof error === "object" &&
|
|
1085
|
+
error !== null &&
|
|
1086
|
+
(error as { failureKind?: unknown }).failureKind ===
|
|
1087
|
+
"validation"
|
|
1088
|
+
? "validation"
|
|
1089
|
+
: "internal";
|
|
1090
|
+
return textResult(
|
|
1091
|
+
{
|
|
1092
|
+
tool: TOOL_NAME,
|
|
1093
|
+
status: "failed",
|
|
1094
|
+
failureKind,
|
|
1095
|
+
error: message,
|
|
1096
|
+
},
|
|
1097
|
+
true,
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
1101
|
+
});
|
|
680
1102
|
}
|