pi-subagents 0.34.0 → 0.35.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 +78 -9
- package/README.md +213 -32
- package/index.ts +1 -0
- package/install.mjs +1 -1
- package/package.json +23 -8
- package/prompts/review-loop.md +3 -1
- package/skills/pi-subagents/SKILL.md +87 -25
- package/src/agents/agent-management.ts +82 -15
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +91 -49
- package/src/agents/frontmatter.ts +67 -13
- package/src/agents/skills.ts +25 -12
- package/src/api/background-work.ts +197 -0
- package/src/api/delegation.ts +158 -0
- package/src/extension/chain-validation.ts +165 -0
- package/src/extension/doctor.ts +15 -0
- package/src/extension/fanout-child.ts +3 -1
- package/src/extension/index.ts +65 -124
- package/src/extension/rpc.ts +10 -2
- package/src/extension/schemas.ts +18 -14
- package/src/extension/steering-notices.ts +35 -0
- package/src/extension/tool-description.ts +20 -9
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/native-supervisor-channel.ts +9 -1
- package/src/intercom/result-intercom.ts +4 -0
- package/src/runs/background/async-execution.ts +293 -44
- package/src/runs/background/async-job-tracker.ts +56 -9
- package/src/runs/background/async-resume.ts +159 -52
- package/src/runs/background/async-status.ts +25 -18
- package/src/runs/background/auto-drain.ts +67 -0
- package/src/runs/background/chain-root-attachment.ts +16 -8
- package/src/runs/background/control-channel.ts +260 -13
- package/src/runs/background/fleet-view.ts +23 -2
- package/src/runs/background/notify.ts +79 -10
- package/src/runs/background/result-watcher.ts +12 -9
- package/src/runs/background/run-id-resolver.ts +14 -2
- package/src/runs/background/run-status.ts +23 -15
- package/src/runs/background/scheduled-runs.ts +3 -0
- package/src/runs/background/stale-run-reconciler.ts +32 -10
- package/src/runs/background/steering.ts +237 -0
- package/src/runs/background/subagent-runner.ts +898 -236
- package/src/runs/background/subagent-wait.ts +484 -0
- package/src/runs/background/top-level-async.ts +2 -1
- package/src/runs/background/wait-config.ts +36 -0
- package/src/runs/background/wait-tool.ts +26 -0
- package/src/runs/foreground/async-steering-action.ts +230 -0
- package/src/runs/foreground/chain-clarify.ts +22 -6
- package/src/runs/foreground/chain-execution.ts +50 -32
- package/src/runs/foreground/execution.ts +308 -94
- package/src/runs/foreground/subagent-executor.ts +592 -268
- package/src/runs/shared/acceptance.ts +355 -97
- package/src/runs/shared/child-protocol.ts +121 -0
- package/src/runs/shared/completion-guard.ts +8 -127
- package/src/runs/shared/dynamic-fanout.ts +6 -4
- package/src/runs/shared/model-fallback.ts +36 -0
- package/src/runs/shared/nested-events.ts +9 -4
- package/src/runs/shared/nested-render.ts +4 -1
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +34 -7
- package/src/runs/shared/pi-spawn.ts +18 -12
- package/src/runs/shared/session-lease.ts +279 -0
- package/src/runs/shared/single-output.ts +61 -6
- package/src/runs/shared/spawn-budget.ts +128 -0
- package/src/runs/shared/subagent-control.ts +10 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +127 -26
- package/src/runs/shared/task-intent.ts +176 -0
- package/src/runs/shared/tool-availability.ts +65 -0
- package/src/runs/shared/turn-budget.ts +49 -4
- package/src/shared/atomic-json.ts +4 -1
- package/src/shared/fork-context.ts +28 -3
- package/src/shared/model-info.ts +7 -4
- package/src/shared/status-format.ts +7 -1
- package/src/shared/types.ts +203 -25
- package/src/shared/utils.ts +35 -7
- package/src/slash/delegation-adapters.ts +457 -0
- package/src/slash/delegation-request.ts +103 -0
- package/src/slash/prompt-template-bridge.ts +167 -344
- package/src/slash/slash-commands.ts +239 -6
- package/src/slash/subagents-admin.ts +428 -0
- package/src/slash/subagents-editor.ts +86 -0
- package/src/tui/fleet.ts +405 -0
- package/src/tui/render.ts +90 -16
- package/src/watchdog/change-signature.ts +127 -0
- package/src/watchdog/child-status.ts +205 -0
- package/src/watchdog/emission-guard.ts +123 -0
- package/src/watchdog/lsp-diagnostics.ts +532 -0
- package/src/watchdog/model-selection.ts +167 -0
- package/src/watchdog/register-child.ts +117 -0
- package/src/watchdog/register-main.ts +433 -0
- package/src/watchdog/render.ts +54 -0
- package/src/watchdog/review.ts +293 -0
- package/src/watchdog/runtime.ts +712 -0
- package/src/watchdog/settings.ts +528 -0
- package/src/watchdog/tool-actions.ts +155 -0
- package/src/watchdog/turn-delta.ts +161 -0
- package/src/watchdog/types.ts +188 -0
- package/src/watchdog/warning-format.ts +73 -0
- package/src/runs/background/wait.ts +0 -394
|
@@ -1,290 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
interface PromptTemplateDelegationResponse extends PromptTemplateDelegationRequest {
|
|
33
|
-
messages: unknown[];
|
|
34
|
-
parallelResults?: PromptTemplateDelegationParallelResult[];
|
|
35
|
-
contentText?: string;
|
|
36
|
-
isError: boolean;
|
|
37
|
-
errorText?: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface PromptTemplateDelegationTaskProgress {
|
|
41
|
-
index?: number;
|
|
42
|
-
agent: string;
|
|
43
|
-
status?: string;
|
|
44
|
-
currentTool?: string;
|
|
45
|
-
currentToolArgs?: string;
|
|
46
|
-
recentOutput?: string;
|
|
47
|
-
recentOutputLines?: string[];
|
|
48
|
-
recentTools?: Array<{ tool: string; args: string }>;
|
|
49
|
-
model?: string;
|
|
50
|
-
toolCount?: number;
|
|
51
|
-
durationMs?: number;
|
|
52
|
-
tokens?: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface PromptTemplateDelegationUpdate {
|
|
56
|
-
requestId: string;
|
|
57
|
-
currentTool?: string;
|
|
58
|
-
currentToolArgs?: string;
|
|
59
|
-
recentOutput?: string;
|
|
60
|
-
recentOutputLines?: string[];
|
|
61
|
-
recentTools?: Array<{ tool: string; args: string }>;
|
|
62
|
-
model?: string;
|
|
63
|
-
toolCount?: number;
|
|
64
|
-
durationMs?: number;
|
|
65
|
-
tokens?: number;
|
|
66
|
-
taskProgress?: PromptTemplateDelegationTaskProgress[];
|
|
67
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
SUBAGENT_DELEGATION_CANCEL_EVENT,
|
|
3
|
+
SUBAGENT_DELEGATION_PROTOCOL_VERSION,
|
|
4
|
+
SUBAGENT_DELEGATION_REQUEST_EVENT,
|
|
5
|
+
SUBAGENT_DELEGATION_RESPONSE_EVENT,
|
|
6
|
+
SUBAGENT_DELEGATION_STARTED_EVENT,
|
|
7
|
+
SUBAGENT_DELEGATION_UPDATE_EVENT,
|
|
8
|
+
type SubagentDelegationRequest,
|
|
9
|
+
type SubagentDelegationResponse,
|
|
10
|
+
} from "../api/delegation.ts";
|
|
11
|
+
import { parseSubagentDelegationRequest } from "./delegation-request.ts";
|
|
12
|
+
import {
|
|
13
|
+
parsePromptTemplateRequest,
|
|
14
|
+
toDelegationUpdate,
|
|
15
|
+
toLegacyExecutionParams,
|
|
16
|
+
toPromptTemplateResponse,
|
|
17
|
+
toSubagentDelegationExecutionParams,
|
|
18
|
+
toSubagentDelegationResponse,
|
|
19
|
+
toSubagentDelegationUpdate,
|
|
20
|
+
type DelegatedSubagentExecutionParams,
|
|
21
|
+
type PromptTemplateBridgeResult,
|
|
22
|
+
type PromptTemplateDelegationRequest,
|
|
23
|
+
type PromptTemplateDelegationResponse,
|
|
24
|
+
} from "./delegation-adapters.ts";
|
|
25
|
+
|
|
26
|
+
export const PROMPT_TEMPLATE_SUBAGENT_REQUEST_EVENT = SUBAGENT_DELEGATION_REQUEST_EVENT;
|
|
27
|
+
export const PROMPT_TEMPLATE_SUBAGENT_STARTED_EVENT = SUBAGENT_DELEGATION_STARTED_EVENT;
|
|
28
|
+
export const PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT = SUBAGENT_DELEGATION_RESPONSE_EVENT;
|
|
29
|
+
export const PROMPT_TEMPLATE_SUBAGENT_UPDATE_EVENT = SUBAGENT_DELEGATION_UPDATE_EVENT;
|
|
30
|
+
export const PROMPT_TEMPLATE_SUBAGENT_CANCEL_EVENT = SUBAGENT_DELEGATION_CANCEL_EVENT;
|
|
68
31
|
|
|
69
32
|
export interface PromptTemplateBridgeEvents {
|
|
70
33
|
on(event: string, handler: (data: unknown) => void): (() => void) | void;
|
|
71
34
|
emit(event: string, data: unknown): void;
|
|
72
35
|
}
|
|
73
36
|
|
|
74
|
-
interface PromptTemplateBridgeResult {
|
|
75
|
-
isError?: boolean;
|
|
76
|
-
content?: unknown;
|
|
77
|
-
details?: {
|
|
78
|
-
results?: Array<{
|
|
79
|
-
agent?: string;
|
|
80
|
-
messages?: unknown[];
|
|
81
|
-
finalOutput?: string;
|
|
82
|
-
toolCalls?: Array<{ text?: string; expandedText?: string }>;
|
|
83
|
-
exitCode?: number;
|
|
84
|
-
error?: string;
|
|
85
|
-
model?: string;
|
|
86
|
-
}>;
|
|
87
|
-
progress?: Array<{
|
|
88
|
-
index?: number;
|
|
89
|
-
agent?: string;
|
|
90
|
-
status?: string;
|
|
91
|
-
currentTool?: string;
|
|
92
|
-
currentToolArgs?: string;
|
|
93
|
-
recentOutput?: string[];
|
|
94
|
-
recentTools?: Array<{ tool?: string; args?: string }>;
|
|
95
|
-
toolCount?: number;
|
|
96
|
-
durationMs?: number;
|
|
97
|
-
tokens?: number;
|
|
98
|
-
}>;
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
37
|
interface PromptTemplateBridgeOptions<Ctx extends { cwd?: string }> {
|
|
103
38
|
events: PromptTemplateBridgeEvents;
|
|
104
39
|
getContext: () => Ctx | null;
|
|
105
40
|
execute: (
|
|
106
41
|
requestId: string,
|
|
107
|
-
|
|
42
|
+
params: DelegatedSubagentExecutionParams,
|
|
108
43
|
signal: AbortSignal,
|
|
109
44
|
ctx: Ctx,
|
|
110
45
|
onUpdate: (result: PromptTemplateBridgeResult) => void,
|
|
111
46
|
) => Promise<PromptTemplateBridgeResult>;
|
|
112
47
|
}
|
|
113
48
|
|
|
114
|
-
function parseDelegationTasks(tasks: unknown): PromptTemplateDelegationTask[] {
|
|
115
|
-
if (!Array.isArray(tasks)) return [];
|
|
116
|
-
const parsed: PromptTemplateDelegationTask[] = [];
|
|
117
|
-
for (const item of tasks) {
|
|
118
|
-
if (!item || typeof item !== "object") return [];
|
|
119
|
-
const value = item as Partial<PromptTemplateDelegationTask>;
|
|
120
|
-
if (typeof value.agent !== "string" || !value.agent.trim()) return [];
|
|
121
|
-
if (typeof value.task !== "string" || !value.task.trim()) return [];
|
|
122
|
-
const model = typeof value.model === "string" && value.model.trim().length > 0 ? value.model : undefined;
|
|
123
|
-
const cwd = typeof value.cwd === "string" && value.cwd.trim().length > 0 ? value.cwd : undefined;
|
|
124
|
-
parsed.push({
|
|
125
|
-
agent: value.agent,
|
|
126
|
-
task: value.task,
|
|
127
|
-
...(model ? { model } : {}),
|
|
128
|
-
...(cwd ? { cwd } : {}),
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
return parsed;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function parsePromptTemplateRequest(data: unknown): PromptTemplateDelegationRequest | undefined {
|
|
135
|
-
if (!data || typeof data !== "object") return undefined;
|
|
136
|
-
const value = data as Partial<PromptTemplateDelegationRequest> & { tasks?: unknown };
|
|
137
|
-
if (typeof value.requestId !== "string" || !value.requestId) return undefined;
|
|
138
|
-
if (typeof value.model !== "string" || !value.model) return undefined;
|
|
139
|
-
if (typeof value.cwd !== "string" || !value.cwd) return undefined;
|
|
140
|
-
if (value.context !== "fresh" && value.context !== "fork") return undefined;
|
|
141
|
-
const tasks = parseDelegationTasks(value.tasks);
|
|
142
|
-
const worktree = value.worktree === true ? true : undefined;
|
|
143
|
-
const hasSingle =
|
|
144
|
-
typeof value.agent === "string" &&
|
|
145
|
-
value.agent.length > 0 &&
|
|
146
|
-
typeof value.task === "string" &&
|
|
147
|
-
value.task.length > 0;
|
|
148
|
-
if (!hasSingle && tasks.length === 0) return undefined;
|
|
149
|
-
|
|
150
|
-
const fallbackTask = tasks[0];
|
|
151
|
-
return {
|
|
152
|
-
requestId: value.requestId,
|
|
153
|
-
agent: hasSingle ? value.agent : fallbackTask!.agent,
|
|
154
|
-
task: hasSingle ? value.task : fallbackTask!.task,
|
|
155
|
-
...(tasks.length > 0 ? { tasks } : {}),
|
|
156
|
-
context: value.context,
|
|
157
|
-
model: value.model,
|
|
158
|
-
cwd: value.cwd,
|
|
159
|
-
...(worktree ? { worktree } : {}),
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function firstTextContent(content: unknown): string | undefined {
|
|
164
|
-
if (!Array.isArray(content)) return undefined;
|
|
165
|
-
for (const part of content) {
|
|
166
|
-
if (!part || typeof part !== "object") continue;
|
|
167
|
-
if ((part as { type?: string }).type !== "text") continue;
|
|
168
|
-
const text = (part as { text?: unknown }).text;
|
|
169
|
-
if (typeof text === "string" && text.trim()) return text.trim();
|
|
170
|
-
}
|
|
171
|
-
return undefined;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function filterRecentOutput(lines: string[] | undefined): string[] | undefined {
|
|
175
|
-
if (!lines || lines.length === 0) return undefined;
|
|
176
|
-
const filtered = lines.filter((line) => typeof line === "string" && line.trim() && line.trim() !== "(running...)");
|
|
177
|
-
if (filtered.length === 0) return undefined;
|
|
178
|
-
return filtered;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function sanitizeRecentTools(
|
|
182
|
-
tools: Array<{ tool?: string; args?: string }> | undefined,
|
|
183
|
-
): Array<{ tool: string; args: string }> | undefined {
|
|
184
|
-
if (!tools || tools.length === 0) return undefined;
|
|
185
|
-
const sanitized = tools.flatMap((entry) => {
|
|
186
|
-
if (typeof entry.tool !== "string" || entry.tool.trim().length === 0) return [];
|
|
187
|
-
return [{
|
|
188
|
-
tool: entry.tool,
|
|
189
|
-
args: typeof entry.args === "string" ? entry.args : String(entry.args ?? ""),
|
|
190
|
-
}];
|
|
191
|
-
});
|
|
192
|
-
return sanitized.length > 0 ? sanitized : undefined;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function resolveProgressModel(
|
|
196
|
-
update: PromptTemplateBridgeResult,
|
|
197
|
-
entry: { index?: number; agent?: string },
|
|
198
|
-
): string | undefined {
|
|
199
|
-
const results = update.details?.results;
|
|
200
|
-
if (!results || results.length === 0) return undefined;
|
|
201
|
-
if (typeof entry.index === "number" && entry.index >= 0) {
|
|
202
|
-
const byIndex = results[entry.index];
|
|
203
|
-
if (typeof byIndex?.model === "string") return byIndex.model;
|
|
204
|
-
}
|
|
205
|
-
if (entry.agent) {
|
|
206
|
-
const byAgent = results.find((result) => result.agent === entry.agent && typeof result.model === "string");
|
|
207
|
-
if (byAgent?.model) return byAgent.model;
|
|
208
|
-
}
|
|
209
|
-
const firstWithModel = results.find((result) => typeof result.model === "string");
|
|
210
|
-
return firstWithModel?.model;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function toolCallNameFromSummary(summary: { text?: string; expandedText?: string }): string | undefined {
|
|
214
|
-
const text = typeof summary.expandedText === "string" && summary.expandedText.trim().length > 0
|
|
215
|
-
? summary.expandedText.trim()
|
|
216
|
-
: typeof summary.text === "string"
|
|
217
|
-
? summary.text.trim()
|
|
218
|
-
: "";
|
|
219
|
-
if (!text) return undefined;
|
|
220
|
-
if (text.startsWith("$ ")) return "bash";
|
|
221
|
-
return text.match(/^[A-Za-z_][\w.-]*/)?.[0];
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function buildDelegationMessages(
|
|
225
|
-
result: { messages?: unknown[]; finalOutput?: string; toolCalls?: Array<{ text?: string; expandedText?: string }> },
|
|
226
|
-
fallbackText?: string,
|
|
227
|
-
): unknown[] {
|
|
228
|
-
if (Array.isArray(result.messages) && result.messages.length > 0) return result.messages;
|
|
229
|
-
const toolCallParts = (result.toolCalls ?? []).flatMap((summary) => {
|
|
230
|
-
const name = toolCallNameFromSummary(summary);
|
|
231
|
-
return name ? [{ type: "toolCall", name, arguments: { summary: summary.expandedText ?? summary.text ?? "" } }] : [];
|
|
232
|
-
});
|
|
233
|
-
const text = typeof result.finalOutput === "string" && result.finalOutput.trim().length > 0
|
|
234
|
-
? result.finalOutput.trim()
|
|
235
|
-
: fallbackText;
|
|
236
|
-
const content = [
|
|
237
|
-
...toolCallParts,
|
|
238
|
-
...(text ? [{ type: "text", text }] : []),
|
|
239
|
-
];
|
|
240
|
-
if (content.length === 0) return [];
|
|
241
|
-
return [{ role: "assistant", content }];
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function toDelegationUpdate(requestId: string, update: PromptTemplateBridgeResult): PromptTemplateDelegationUpdate | undefined {
|
|
245
|
-
const progress = update.details?.progress?.[0];
|
|
246
|
-
const taskProgress = update.details?.progress?.map((entry) => {
|
|
247
|
-
const lastOutput = entry.recentOutput?.[entry.recentOutput.length - 1];
|
|
248
|
-
const safeLastOutput =
|
|
249
|
-
typeof lastOutput === "string" && lastOutput.trim() && lastOutput !== "(running...)"
|
|
250
|
-
? lastOutput
|
|
251
|
-
: undefined;
|
|
252
|
-
return {
|
|
253
|
-
index: entry.index,
|
|
254
|
-
agent: entry.agent ?? "delegate",
|
|
255
|
-
status: entry.status,
|
|
256
|
-
currentTool: entry.currentTool,
|
|
257
|
-
currentToolArgs: entry.currentToolArgs,
|
|
258
|
-
recentOutput: safeLastOutput,
|
|
259
|
-
recentOutputLines: filterRecentOutput(entry.recentOutput),
|
|
260
|
-
recentTools: sanitizeRecentTools(entry.recentTools),
|
|
261
|
-
model: resolveProgressModel(update, entry),
|
|
262
|
-
toolCount: entry.toolCount,
|
|
263
|
-
durationMs: entry.durationMs,
|
|
264
|
-
tokens: entry.tokens,
|
|
265
|
-
};
|
|
266
|
-
});
|
|
267
|
-
if (!progress && (!taskProgress || taskProgress.length === 0)) return undefined;
|
|
268
|
-
const lastOutput = progress?.recentOutput?.[progress.recentOutput.length - 1];
|
|
269
|
-
const safeLastOutput =
|
|
270
|
-
typeof lastOutput === "string" && lastOutput.trim() && lastOutput !== "(running...)"
|
|
271
|
-
? lastOutput
|
|
272
|
-
: undefined;
|
|
273
|
-
return {
|
|
274
|
-
requestId,
|
|
275
|
-
currentTool: progress?.currentTool,
|
|
276
|
-
currentToolArgs: progress?.currentToolArgs,
|
|
277
|
-
recentOutput: safeLastOutput,
|
|
278
|
-
recentOutputLines: filterRecentOutput(progress?.recentOutput),
|
|
279
|
-
recentTools: sanitizeRecentTools(progress?.recentTools),
|
|
280
|
-
model: progress ? resolveProgressModel(update, progress) : undefined,
|
|
281
|
-
toolCount: progress?.toolCount,
|
|
282
|
-
durationMs: progress?.durationMs,
|
|
283
|
-
tokens: progress?.tokens,
|
|
284
|
-
taskProgress,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
49
|
export function registerPromptTemplateDelegationBridge<Ctx extends { cwd?: string }>(
|
|
289
50
|
options: PromptTemplateBridgeOptions<Ctx>,
|
|
290
51
|
): {
|
|
@@ -292,126 +53,188 @@ export function registerPromptTemplateDelegationBridge<Ctx extends { cwd?: strin
|
|
|
292
53
|
dispose: () => void;
|
|
293
54
|
} {
|
|
294
55
|
const controllers = new Map<string, AbortController>();
|
|
295
|
-
const pendingCancels = new
|
|
56
|
+
const pendingCancels = new Map<string, true>();
|
|
296
57
|
const subscriptions: Array<() => void> = [];
|
|
58
|
+
let disposed = false;
|
|
297
59
|
|
|
298
60
|
const subscribe = (event: string, handler: (data: unknown) => void): void => {
|
|
299
61
|
const unsubscribe = options.events.on(event, handler);
|
|
300
62
|
if (typeof unsubscribe === "function") subscriptions.push(unsubscribe);
|
|
301
63
|
};
|
|
64
|
+
const ownsRequest = (requestId: string, controller: AbortController): boolean =>
|
|
65
|
+
!disposed && controllers.get(requestId) === controller;
|
|
66
|
+
const rememberPendingCancel = (requestId: string): void => {
|
|
67
|
+
pendingCancels.delete(requestId);
|
|
68
|
+
pendingCancels.set(requestId, true);
|
|
69
|
+
while (pendingCancels.size > 256) {
|
|
70
|
+
const oldest = pendingCancels.keys().next().value;
|
|
71
|
+
if (typeof oldest !== "string") break;
|
|
72
|
+
pendingCancels.delete(oldest);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
302
75
|
|
|
303
76
|
subscribe(PROMPT_TEMPLATE_SUBAGENT_CANCEL_EVENT, (data) => {
|
|
304
|
-
if (!data || typeof data !== "object") return;
|
|
77
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return;
|
|
78
|
+
if (Object.hasOwn(data, "version")) {
|
|
79
|
+
const value = data as Record<string, unknown>;
|
|
80
|
+
if (value.version !== SUBAGENT_DELEGATION_PROTOCOL_VERSION) return;
|
|
81
|
+
if (Object.keys(value).some((key) => key !== "version" && key !== "requestId")) return;
|
|
82
|
+
}
|
|
305
83
|
const requestId = (data as { requestId?: unknown }).requestId;
|
|
306
|
-
if (typeof requestId !== "string") return;
|
|
84
|
+
if (typeof requestId !== "string" || !requestId.trim() || requestId.length > 256 || /[\r\n]/.test(requestId)) return;
|
|
307
85
|
const controller = controllers.get(requestId);
|
|
308
86
|
if (controller) {
|
|
309
87
|
controller.abort();
|
|
310
88
|
return;
|
|
311
89
|
}
|
|
312
|
-
|
|
90
|
+
rememberPendingCancel(requestId);
|
|
313
91
|
});
|
|
314
92
|
|
|
315
93
|
subscribe(PROMPT_TEMPLATE_SUBAGENT_REQUEST_EVENT, async (data) => {
|
|
316
|
-
const
|
|
317
|
-
|
|
94
|
+
const isVersioned = !!data && typeof data === "object" && Object.hasOwn(data, "version");
|
|
95
|
+
let requestId: string;
|
|
96
|
+
let params: DelegatedSubagentExecutionParams;
|
|
97
|
+
let versionedRequest: SubagentDelegationRequest | undefined;
|
|
98
|
+
let legacyRequest: PromptTemplateDelegationRequest | undefined;
|
|
99
|
+
|
|
100
|
+
if (isVersioned) {
|
|
101
|
+
const parsed = parseSubagentDelegationRequest(data);
|
|
102
|
+
if (parsed.ok === false) {
|
|
103
|
+
if (!disposed && parsed.requestId && !controllers.has(parsed.requestId)) {
|
|
104
|
+
options.events.emit(SUBAGENT_DELEGATION_RESPONSE_EVENT, {
|
|
105
|
+
version: SUBAGENT_DELEGATION_PROTOCOL_VERSION,
|
|
106
|
+
requestId: parsed.requestId,
|
|
107
|
+
status: "invalid_request",
|
|
108
|
+
error: parsed.error,
|
|
109
|
+
} satisfies SubagentDelegationResponse);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
versionedRequest = parsed.request;
|
|
114
|
+
requestId = parsed.request.requestId;
|
|
115
|
+
params = toSubagentDelegationExecutionParams(parsed.request);
|
|
116
|
+
} else {
|
|
117
|
+
legacyRequest = parsePromptTemplateRequest(data);
|
|
118
|
+
if (!legacyRequest) return;
|
|
119
|
+
requestId = legacyRequest.requestId;
|
|
120
|
+
params = toLegacyExecutionParams(legacyRequest);
|
|
121
|
+
}
|
|
318
122
|
|
|
123
|
+
// The first request owns its correlation ID until it settles. A duplicate
|
|
124
|
+
// cannot receive a terminal response without stealing the original caller's.
|
|
125
|
+
if (controllers.has(requestId)) return;
|
|
319
126
|
const ctx = options.getContext();
|
|
320
127
|
if (!ctx) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
128
|
+
if (versionedRequest) {
|
|
129
|
+
options.events.emit(SUBAGENT_DELEGATION_RESPONSE_EVENT, {
|
|
130
|
+
version: SUBAGENT_DELEGATION_PROTOCOL_VERSION,
|
|
131
|
+
requestId,
|
|
132
|
+
status: "unavailable_context",
|
|
133
|
+
error: "No active extension context for delegated subagent execution.",
|
|
134
|
+
} satisfies SubagentDelegationResponse);
|
|
135
|
+
} else if (legacyRequest) {
|
|
136
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, {
|
|
137
|
+
...legacyRequest,
|
|
138
|
+
messages: [],
|
|
139
|
+
isError: true,
|
|
140
|
+
errorText: "No active extension context for delegated subagent execution.",
|
|
141
|
+
} satisfies PromptTemplateDelegationResponse);
|
|
142
|
+
}
|
|
328
143
|
return;
|
|
329
144
|
}
|
|
330
145
|
|
|
331
146
|
const controller = new AbortController();
|
|
332
|
-
controllers.set(
|
|
333
|
-
|
|
334
|
-
if (
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
|
|
147
|
+
controllers.set(requestId, controller);
|
|
148
|
+
if (pendingCancels.delete(requestId)) controller.abort();
|
|
149
|
+
if (controller.signal.aborted) {
|
|
150
|
+
if (versionedRequest) {
|
|
151
|
+
options.events.emit(SUBAGENT_DELEGATION_RESPONSE_EVENT, {
|
|
152
|
+
version: SUBAGENT_DELEGATION_PROTOCOL_VERSION,
|
|
153
|
+
requestId,
|
|
154
|
+
status: "cancelled",
|
|
155
|
+
} satisfies SubagentDelegationResponse);
|
|
156
|
+
} else if (legacyRequest) {
|
|
157
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, {
|
|
158
|
+
...legacyRequest,
|
|
159
|
+
messages: [],
|
|
160
|
+
isError: true,
|
|
161
|
+
errorText: "Delegated prompt cancelled.",
|
|
162
|
+
} satisfies PromptTemplateDelegationResponse);
|
|
163
|
+
}
|
|
164
|
+
controllers.delete(requestId);
|
|
344
165
|
return;
|
|
345
166
|
}
|
|
346
167
|
|
|
347
|
-
options.events.emit(
|
|
168
|
+
options.events.emit(
|
|
169
|
+
versionedRequest ? SUBAGENT_DELEGATION_STARTED_EVENT : PROMPT_TEMPLATE_SUBAGENT_STARTED_EVENT,
|
|
170
|
+
versionedRequest
|
|
171
|
+
? { version: SUBAGENT_DELEGATION_PROTOCOL_VERSION, requestId }
|
|
172
|
+
: { requestId },
|
|
173
|
+
);
|
|
348
174
|
|
|
349
175
|
try {
|
|
350
176
|
const result = await options.execute(
|
|
351
|
-
|
|
352
|
-
|
|
177
|
+
requestId,
|
|
178
|
+
params,
|
|
353
179
|
controller.signal,
|
|
354
180
|
ctx,
|
|
355
181
|
(update) => {
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
|
|
182
|
+
if (!ownsRequest(requestId, controller)) return;
|
|
183
|
+
if (versionedRequest) {
|
|
184
|
+
const payload = toSubagentDelegationUpdate(requestId, update);
|
|
185
|
+
if (payload) options.events.emit(SUBAGENT_DELEGATION_UPDATE_EVENT, payload);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const payload = toDelegationUpdate(requestId, update);
|
|
189
|
+
if (payload) options.events.emit(PROMPT_TEMPLATE_SUBAGENT_UPDATE_EVENT, payload);
|
|
359
190
|
},
|
|
360
191
|
);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const errorText = step.error;
|
|
376
|
-
return {
|
|
377
|
-
agent: step.agent ?? task.agent,
|
|
378
|
-
messages: buildDelegationMessages(step),
|
|
379
|
-
isError: (exitCode !== undefined && exitCode !== 0) || !!errorText,
|
|
380
|
-
errorText: errorText || undefined,
|
|
381
|
-
};
|
|
382
|
-
})
|
|
383
|
-
: undefined;
|
|
384
|
-
const response: PromptTemplateDelegationResponse = {
|
|
385
|
-
...request,
|
|
386
|
-
messages,
|
|
387
|
-
...(parallelResults ? { parallelResults } : {}),
|
|
388
|
-
...(contentText ? { contentText } : {}),
|
|
389
|
-
isError: result.isError === true,
|
|
390
|
-
errorText: result.isError ? contentText : undefined,
|
|
391
|
-
};
|
|
392
|
-
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, response);
|
|
192
|
+
if (!ownsRequest(requestId, controller)) return;
|
|
193
|
+
if (versionedRequest) {
|
|
194
|
+
options.events.emit(
|
|
195
|
+
SUBAGENT_DELEGATION_RESPONSE_EVENT,
|
|
196
|
+
toSubagentDelegationResponse(requestId, result, controller.signal.aborted),
|
|
197
|
+
);
|
|
198
|
+
} else if (legacyRequest) {
|
|
199
|
+
options.events.emit(
|
|
200
|
+
PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT,
|
|
201
|
+
controller.signal.aborted
|
|
202
|
+
? { ...legacyRequest, messages: [], isError: true, errorText: "Delegated prompt cancelled." }
|
|
203
|
+
: toPromptTemplateResponse(legacyRequest, result),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
393
206
|
} catch (error) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
207
|
+
if (!ownsRequest(requestId, controller)) return;
|
|
208
|
+
if (versionedRequest) {
|
|
209
|
+
options.events.emit(SUBAGENT_DELEGATION_RESPONSE_EVENT, {
|
|
210
|
+
version: SUBAGENT_DELEGATION_PROTOCOL_VERSION,
|
|
211
|
+
requestId,
|
|
212
|
+
status: controller.signal.aborted ? "cancelled" : "failed",
|
|
213
|
+
...(controller.signal.aborted ? {} : { error: error instanceof Error ? error.message : String(error) }),
|
|
214
|
+
} satisfies SubagentDelegationResponse);
|
|
215
|
+
} else if (legacyRequest) {
|
|
216
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, {
|
|
217
|
+
...legacyRequest,
|
|
218
|
+
messages: [],
|
|
219
|
+
isError: true,
|
|
220
|
+
errorText: error instanceof Error ? error.message : String(error),
|
|
221
|
+
} satisfies PromptTemplateDelegationResponse);
|
|
222
|
+
}
|
|
401
223
|
} finally {
|
|
402
|
-
controllers.delete(
|
|
224
|
+
if (controllers.get(requestId) === controller) controllers.delete(requestId);
|
|
403
225
|
}
|
|
404
226
|
});
|
|
405
227
|
|
|
406
228
|
return {
|
|
407
229
|
cancelAll: () => {
|
|
408
|
-
for (const controller of controllers.values())
|
|
409
|
-
controller.abort();
|
|
410
|
-
}
|
|
230
|
+
for (const controller of controllers.values()) controller.abort();
|
|
411
231
|
controllers.clear();
|
|
412
232
|
pendingCancels.clear();
|
|
413
233
|
},
|
|
414
234
|
dispose: () => {
|
|
235
|
+
disposed = true;
|
|
236
|
+
for (const controller of controllers.values()) controller.abort();
|
|
237
|
+
controllers.clear();
|
|
415
238
|
for (const unsubscribe of subscriptions) unsubscribe();
|
|
416
239
|
subscriptions.length = 0;
|
|
417
240
|
pendingCancels.clear();
|