@mystilleef/pi-subagent 0.5.0 → 0.7.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/src/index.ts CHANGED
@@ -5,18 +5,19 @@ import type {
5
5
  import {
6
6
  getCachedAgentCompletions,
7
7
  resetAgentDiscoveryCache,
8
- } from "./agent-cache.js";
9
- import { cancelSubagentCommandHandler } from "./cancel-command.js";
10
- import { jobsCommandHandler } from "./jobs-command.js";
11
- import { renderSubagentProgress } from "./progress.js";
12
- import { renderSubagentResultMessage } from "./run.js";
13
- import { runCommandHandler } from "./run-command.js";
8
+ } from "./agent/agent-cache.js";
9
+ import { isDirectoryAsync } from "./agent/agents.js";
10
+ import { cancelSubagentCommandHandler } from "./orchestration/cancel-command.js";
11
+ import { jobsCommandHandler } from "./orchestration/jobs-command.js";
12
+ import { renderSubagentResultMessage } from "./orchestration/run.js";
13
+ import { runCommandHandler } from "./orchestration/run-command.js";
14
14
  import {
15
- formatStartJobStatus,
15
+ formatSubagentToolResult,
16
16
  SubagentParams,
17
17
  startSubagentJob,
18
- } from "./subagent-orchestrator.js";
19
- import { renderSubagentCall, renderSubagentResult } from "./ui.js";
18
+ } from "./orchestration/subagent-orchestrator.js";
19
+ import { renderSubagentCall, renderSubagentToolResult } from "./output/ui.js";
20
+ import { renderSubagentProgress } from "./progress/progress.js";
20
21
 
21
22
  export { SubagentParams };
22
23
 
@@ -24,15 +25,40 @@ export function resetAgentCache() {
24
25
  resetAgentDiscoveryCache();
25
26
  }
26
27
 
28
+ function normalizeWorkspaceRoot(cwd: string | undefined): string | undefined {
29
+ if (typeof cwd !== "string") return undefined;
30
+ const root = cwd.trim();
31
+ return root.length > 0 ? root : undefined;
32
+ }
33
+
27
34
  export default function registerSubagentExtension(pi: ExtensionAPI) {
35
+ let activeWorkspaceRoot: string | undefined;
36
+ const setActiveWorkspaceRoot = (
37
+ cwd: string | undefined,
38
+ fallback?: string,
39
+ ) => {
40
+ activeWorkspaceRoot = normalizeWorkspaceRoot(cwd ?? fallback);
41
+ };
42
+ const getRunArgumentCompletions = async (prefix: string) => {
43
+ if (!activeWorkspaceRoot) return [];
44
+ if (!(await isDirectoryAsync(activeWorkspaceRoot))) return [];
45
+ return getCachedAgentCompletions(prefix, activeWorkspaceRoot);
46
+ };
47
+ pi.on("resources_discover", (event, ctx) => {
48
+ setActiveWorkspaceRoot(ctx.cwd, event.cwd);
49
+ });
50
+ pi.on("session_start", (_event, ctx) => {
51
+ setActiveWorkspaceRoot(ctx.cwd);
52
+ });
28
53
  pi.registerMessageRenderer("subagent-progress", renderSubagentProgress);
29
54
  pi.registerMessageRenderer("subagent-result", renderSubagentResultMessage);
30
55
  pi.registerCommand("run", {
31
56
  description: "Run a subagent directly: /run <agent> [task]",
32
- getArgumentCompletions: async (prefix: string) =>
33
- getCachedAgentCompletions(prefix),
34
- handler: async (args, ctx) =>
35
- runCommandHandler(pi, ctx as ExtensionContext, args),
57
+ getArgumentCompletions: getRunArgumentCompletions,
58
+ handler: async (args, ctx) => {
59
+ setActiveWorkspaceRoot(ctx.cwd);
60
+ return runCommandHandler(pi, ctx as ExtensionContext, args);
61
+ },
36
62
  });
37
63
  pi.registerCommand("cancel-subagent", {
38
64
  description:
@@ -48,28 +74,21 @@ export default function registerSubagentExtension(pi: ExtensionAPI) {
48
74
  label: "Subagent",
49
75
  description: "Delegate a task to a subagent with isolated context.",
50
76
  parameters: SubagentParams,
51
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
77
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
52
78
  const result = await startSubagentJob(
53
79
  pi,
54
80
  ctx,
55
81
  params,
56
82
  signal ?? undefined,
83
+ onUpdate ?? undefined,
57
84
  );
58
- return {
59
- content: [
60
- {
61
- type: "text" as const,
62
- text: formatStartJobStatus(params.agent, result),
63
- },
64
- ],
65
- details: result.makeDetails([]),
66
- };
85
+ return formatSubagentToolResult(params.agent, result);
67
86
  },
68
87
  renderCall(args, theme, _context) {
69
88
  return renderSubagentCall(args, theme);
70
89
  },
71
90
  renderResult(result, display, theme, _context) {
72
- return renderSubagentResult(result, theme, display);
91
+ return renderSubagentToolResult(result, theme, display);
73
92
  },
74
93
  });
75
94
  }
@@ -1,11 +1,10 @@
1
1
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
-
2
+ import { renderRunsBoard } from "../output/ui.js";
3
3
  import {
4
4
  getAllProgressStates,
5
5
  type SubagentProgressState,
6
- } from "./progress-state.js";
6
+ } from "../progress/progress-state.js";
7
7
  import { listRunJobs } from "./run-registry.js";
8
- import { renderRunsBoard } from "./ui.js";
9
8
 
10
9
  export async function jobsCommandHandler(
11
10
  ctx: ExtensionCommandContext,
@@ -1,5 +1,5 @@
1
- import type { SubagentDetails } from "./types.js";
2
- import { renderSubagentResult, type SubagentTheme } from "./ui.js";
1
+ import { renderSubagentResult, type SubagentTheme } from "../output/ui.js";
2
+ import type { SubagentDetails } from "../shared/types.js";
3
3
 
4
4
  /**
5
5
  * Pi message renderer adapter for `"subagent-result"` messages.
@@ -1,46 +1,48 @@
1
+ import type { AgentToolUpdateCallback } from "@earendil-works/pi-agent-core";
1
2
  import { type Message, StringEnum } from "@earendil-works/pi-ai";
2
3
  import type {
3
4
  ExtensionAPI,
4
5
  ExtensionContext,
5
6
  } from "@earendil-works/pi-coding-agent";
6
7
  import { type Static, Type } from "typebox";
7
- import { getCachedAgentDiscovery } from "./agent-cache.js";
8
- import {
9
- type AgentConfig,
10
- type AgentScope,
11
- discoverAgents,
12
- type ThinkingLevel,
13
- } from "./agents.js";
14
- import { generateSubagentInstanceName } from "./instance-name.js";
15
- import { runSingleAgent } from "./process.js";
8
+ import { getCachedAgentDiscovery } from "../agent/agent-cache.js";
9
+ import type {
10
+ AgentConfig,
11
+ AgentScope,
12
+ ThinkingLevel,
13
+ } from "../agent/agents.js";
14
+ import { runSingleAgent, SubagentAbortError } from "../child/process.js";
15
+ import { formatSubagentResultForParent } from "../output/summary.js";
16
16
  import {
17
17
  cancelProgressState,
18
18
  createProgressState,
19
19
  failProgressState,
20
20
  finalizeProgressState,
21
21
  getProgressState,
22
- } from "./progress.js";
22
+ } from "../progress/progress.js";
23
23
  import {
24
24
  createSubagentError,
25
25
  getFeedbackSummaryText,
26
+ getLatestResult,
26
27
  getResultDisplayText,
27
28
  hasSubagentFailed,
28
29
  patchProgressFromDetails,
29
30
  sanitizeDetailsForDisplay,
30
- } from "./result-details.js";
31
+ } from "../progress/result-details.js";
32
+ import { generateSubagentInstanceName } from "../shared/instance-name.js";
33
+ import type {
34
+ OnUpdateCallback,
35
+ SingleResult,
36
+ SubagentDetails,
37
+ SubagentToolResult,
38
+ } from "../shared/types.js";
39
+ import { getSubagentDepth } from "../shared/utils.js";
31
40
  import {
32
41
  listRunJobs,
33
42
  type RunJob,
34
43
  registerRunJob,
35
44
  removeRunJob,
36
45
  } from "./run-registry.js";
37
- import { formatSubagentResultForParent } from "./summary.js";
38
- import type {
39
- OnUpdateCallback,
40
- SingleResult,
41
- SubagentDetails,
42
- SubagentToolResult,
43
- } from "./types.js";
44
46
 
45
47
  const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, {
46
48
  description:
@@ -75,6 +77,22 @@ type DetailsBuilder = (
75
77
  options?: DetailsOptions,
76
78
  ) => SubagentDetails;
77
79
 
80
+ interface LifecycleContext {
81
+ pi: ExtensionAPI;
82
+ ctx: ExtensionContext;
83
+ requestId: string;
84
+ job: RunJob;
85
+ debug: boolean;
86
+ makeDetails: DetailsBuilder;
87
+ mergedSignal: AbortSignal;
88
+ agents: AgentConfig[];
89
+ agentName: string;
90
+ task: string;
91
+ parentModel: { provider: string; id: string } | undefined;
92
+ parentThinking: ThinkingLevel;
93
+ hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>;
94
+ }
95
+
78
96
  function createDetailsBuilder(
79
97
  agentScope: AgentScope,
80
98
  projectAgentsDir: string | null,
@@ -99,37 +117,40 @@ function sanitizeResultDetails(
99
117
  includeDebugMessages && (options?.includeMessages ?? true);
100
118
  const { messages, termination, progress, stderr, usage, ...core } = result;
101
119
  const { contextWindowTokens, ...usageBase } = usage;
102
-
103
120
  const sanitized: Record<string, unknown> = {
104
121
  ...core,
105
122
  stderr: includeDebugMessages ? stderr : "",
106
123
  usage: { ...usageBase },
107
124
  };
108
-
109
125
  if (contextWindowTokens !== undefined) {
110
126
  (sanitized.usage as Record<string, unknown>).contextWindowTokens =
111
127
  contextWindowTokens;
112
128
  }
113
-
114
129
  if (progress !== undefined) {
115
- const { activityText, lastToolPreview, ...progBase } = progress;
130
+ const {
131
+ activityText,
132
+ activeToolActivity,
133
+ lastToolPreview,
134
+ toolResultCompleted,
135
+ ...progBase
136
+ } = progress;
116
137
  sanitized.progress = {
117
138
  toolCalls: progBase.toolCalls.map((tc) => ({
118
139
  id: tc.id,
119
140
  preview: tc.preview,
120
141
  })),
121
142
  ...(activityText !== undefined && { activityText }),
143
+ ...(activeToolActivity !== undefined && { activeToolActivity }),
122
144
  ...(lastToolPreview !== undefined && { lastToolPreview }),
145
+ ...(toolResultCompleted !== undefined && { toolResultCompleted }),
123
146
  };
124
147
  }
125
-
126
148
  if (includeMessages) {
127
149
  sanitized.messages = options?.recentMessages
128
150
  ? [...options.recentMessages]
129
151
  : messages !== undefined
130
152
  ? [...messages]
131
153
  : undefined;
132
-
133
154
  if (includeDebugMessages && termination !== undefined) {
134
155
  const { cancelReason, terminationSignal, fallbackCause, ...termBase } =
135
156
  termination;
@@ -141,7 +162,6 @@ function sanitizeResultDetails(
141
162
  };
142
163
  }
143
164
  }
144
-
145
165
  return sanitized as unknown as SingleResult;
146
166
  }
147
167
 
@@ -184,93 +204,136 @@ export function emitCompletionAlert(
184
204
  process.stdout.write("\x07");
185
205
  }
186
206
 
187
- async function runSubagentWorker(
188
- pi: ExtensionAPI,
189
- ctx: ExtensionContext,
190
- agents: AgentConfig[],
191
- agentName: string,
192
- task: string,
193
- debug: boolean,
194
- parentModel: { provider: string; id: string } | undefined,
195
- parentThinking: ThinkingLevel,
196
- makeDetails: DetailsBuilder,
197
- requestId: string,
198
- job: RunJob,
199
- mergedSignal: AbortSignal,
200
- ): Promise<void> {
207
+ function createCompletedToolResult(
208
+ content: string,
209
+ details: SubagentDetails,
210
+ ): SubagentToolResult {
211
+ return {
212
+ content: [{ type: "text", text: content }],
213
+ details: { ...details, renderedByMessage: true },
214
+ };
215
+ }
216
+
217
+ function finishLifecycleFailure(
218
+ lc: LifecycleContext,
219
+ errorMessage: string,
220
+ details: SubagentDetails,
221
+ ): SubagentToolResult {
222
+ const displayDetails = sanitizeDetailsForDisplay(details, lc.debug);
223
+ if (lc.mergedSignal.aborted) {
224
+ cancelProgressState(lc.requestId, lc.job.cancelReason ?? errorMessage);
225
+ if (getSubagentDepth() > 0) {
226
+ sendSubagentResultMessage(lc.pi, "Canceled", displayDetails);
227
+ }
228
+ return createCompletedToolResult("Canceled", displayDetails);
229
+ }
230
+ failProgressState(lc.requestId, errorMessage);
231
+ lc.ctx.ui?.notify(errorMessage, "error");
232
+ const latestResult = getLatestResult(details);
233
+ const content = latestResult
234
+ ? formatSubagentResultForParent(latestResult) || "(failed)"
235
+ : errorMessage;
236
+ sendSubagentResultMessage(lc.pi, content, displayDetails);
237
+ return createCompletedToolResult(content, displayDetails);
238
+ }
239
+
240
+ function finishLifecycleResult(
241
+ lc: LifecycleContext,
242
+ result: SingleResult,
243
+ ): SubagentToolResult {
244
+ const details = lc.makeDetails([result]);
245
+ if (hasSubagentFailed(result)) {
246
+ return finishLifecycleFailure(
247
+ lc,
248
+ lc.mergedSignal.aborted ? "Aborted" : createSubagentError(result).message,
249
+ details,
250
+ );
251
+ }
252
+ const displayDetails = sanitizeDetailsForDisplay(details, lc.debug);
253
+ const content = formatSubagentResultForParent(result) || "(no output)";
254
+ const toolResult = createCompletedToolResult(content, displayDetails);
255
+ finalizeProgressState(lc.requestId, getFeedbackSummaryText(toolResult));
256
+ sendSubagentResultMessage(
257
+ lc.pi,
258
+ getResultDisplayText(toolResult),
259
+ displayDetails,
260
+ );
261
+ return toolResult;
262
+ }
263
+
264
+ function createPayloadFingerprint(payload: {
265
+ content: { type: string; text?: string }[];
266
+ details: SubagentDetails;
267
+ }): string {
268
+ const contentText = payload.content[0]?.text ?? "";
269
+ const latestResult = payload.details.results[0];
270
+ const activityText = latestResult?.progress?.activityText ?? "";
271
+ const toolCallIds =
272
+ [...new Set(latestResult?.progress?.toolCalls?.map((tc) => tc.id))]
273
+ .sort()
274
+ .join(",") ?? "";
275
+ const exitCode = latestResult?.exitCode ?? 0;
276
+ const stopReason = latestResult?.stopReason ?? "";
277
+ return `${contentText}|${activityText}|${toolCallIds}|${exitCode}|${stopReason}`;
278
+ }
279
+
280
+ async function runSubagentLifecycle(
281
+ lc: LifecycleContext,
282
+ ): Promise<SubagentToolResult> {
201
283
  const seenToolCallIds = new Set<string>();
202
- const requestProgressRender = createProgressRenderRequester(ctx, requestId);
284
+ const requestProgressRender = createProgressRenderRequester(
285
+ lc.ctx,
286
+ lc.requestId,
287
+ );
288
+ let lastDeliveredFingerprint: string | undefined;
203
289
  const onUpdate: OnUpdateCallback = (result) => {
204
- patchProgressFromDetails(requestId, result.details, seenToolCallIds);
290
+ patchProgressFromDetails(lc.requestId, result.details, seenToolCallIds);
205
291
  requestProgressRender();
206
- };
207
- function handleWorkerFailure(
208
- errorMessage: string,
209
- isAborted: boolean,
210
- details: SubagentDetails,
211
- ) {
212
- if (isAborted) {
213
- cancelProgressState(requestId, job.cancelReason ?? errorMessage);
214
- } else {
215
- failProgressState(requestId, errorMessage);
216
- ctx.ui?.notify(errorMessage, "error");
217
- const content = details.results[0]
218
- ? formatSubagentResultForParent(details.results[0] as SingleResult) ||
219
- "(failed)"
220
- : errorMessage;
221
- sendSubagentResultMessage(
222
- pi,
223
- content,
224
- sanitizeDetailsForDisplay(details, debug),
292
+ if (lc.hostOnUpdate) {
293
+ const sanitizedDetails = sanitizeDetailsForDisplay(
294
+ result.details,
295
+ lc.debug,
225
296
  );
297
+ const { renderedByMessage, ...partialDetails } = sanitizedDetails;
298
+ const payload = {
299
+ content: result.content,
300
+ details: partialDetails,
301
+ };
302
+ const fingerprint = createPayloadFingerprint(payload);
303
+ if (fingerprint !== lastDeliveredFingerprint) {
304
+ lastDeliveredFingerprint = fingerprint;
305
+ lc.hostOnUpdate(payload);
306
+ }
226
307
  }
227
- }
308
+ };
309
+ const timerTick = setInterval(requestProgressRender, 500);
228
310
  try {
229
311
  const result = await runSingleAgent(
230
- ctx.cwd,
231
- agents,
232
- agentName,
233
- task,
234
- mergedSignal,
312
+ lc.ctx.cwd,
313
+ lc.agents,
314
+ lc.agentName,
315
+ lc.task,
316
+ lc.mergedSignal,
235
317
  onUpdate,
236
- makeDetails,
237
- parentModel,
238
- parentThinking,
318
+ lc.makeDetails,
319
+ lc.parentModel,
320
+ lc.parentThinking,
239
321
  );
240
- if (hasSubagentFailed(result)) {
241
- handleWorkerFailure(
242
- mergedSignal.aborted ? "Aborted" : createSubagentError(result).message,
243
- mergedSignal.aborted,
244
- makeDetails([result]),
245
- );
246
- } else {
247
- const toolResult: SubagentToolResult = {
248
- content: [
249
- {
250
- type: "text" as const,
251
- text: formatSubagentResultForParent(result) || "(no output)",
252
- },
253
- ],
254
- details: makeDetails([result]),
255
- };
256
- finalizeProgressState(requestId, getFeedbackSummaryText(toolResult));
257
- sendSubagentResultMessage(
258
- pi,
259
- getResultDisplayText(toolResult),
260
- sanitizeDetailsForDisplay(toolResult.details, debug),
261
- );
262
- }
322
+ return finishLifecycleResult(lc, result);
263
323
  } catch (error) {
264
- handleWorkerFailure(
324
+ const abortResult =
325
+ error instanceof SubagentAbortError ? error.result : undefined;
326
+ return finishLifecycleFailure(
327
+ lc,
265
328
  error instanceof Error ? error.message : String(error),
266
- mergedSignal.aborted,
267
- makeDetails([]),
329
+ abortResult ? lc.makeDetails([abortResult]) : lc.makeDetails([]),
268
330
  );
269
331
  } finally {
332
+ clearInterval(timerTick);
270
333
  requestProgressRender();
271
- removeRunJob(requestId);
334
+ removeRunJob(lc.requestId);
272
335
  if (listRunJobs().length === 0) {
273
- const state = getProgressState(requestId);
336
+ const state = getProgressState(lc.requestId);
274
337
  if (state) {
275
338
  emitCompletionAlert(state);
276
339
  }
@@ -278,23 +341,43 @@ async function runSubagentWorker(
278
341
  }
279
342
  }
280
343
 
281
- export type StartJobResult =
344
+ type StartJobResult =
282
345
  | {
283
346
  kind: "started";
284
347
  requestId: string;
285
348
  instanceName: string;
286
349
  makeDetails: DetailsBuilder;
287
350
  }
351
+ | { kind: "completed"; result: SubagentToolResult }
288
352
  | { kind: "cancelled"; makeDetails: DetailsBuilder }
289
353
  | { kind: "not_found"; makeDetails: DetailsBuilder };
290
354
 
291
- export function formatStartJobStatus(
355
+ type PrepareSubagentJobResult =
356
+ | {
357
+ kind: "ready";
358
+ lc: LifecycleContext;
359
+ instanceName: string;
360
+ requestProgressRender: () => void;
361
+ hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>;
362
+ }
363
+ | { kind: "not_found"; makeDetails: DetailsBuilder }
364
+ | { kind: "cancelled"; makeDetails: DetailsBuilder }
365
+ | { kind: "aborted"; makeDetails: DetailsBuilder };
366
+
367
+ export function formatSubagentToolResult(
292
368
  agentName: string,
293
369
  result: StartJobResult,
294
- ): string {
295
- if (result.kind === "not_found") return `Unknown agent: "${agentName}"`;
296
- if (result.kind === "cancelled") return "Canceled";
297
- return `Subagent ${agentName} ${result.instanceName} started (job: ${result.requestId})`;
370
+ ): SubagentToolResult {
371
+ if (result.kind === "completed") return result.result;
372
+ let text: string;
373
+ if (result.kind === "not_found") text = `Unknown agent: "${agentName}"`;
374
+ else if (result.kind === "cancelled") text = "Canceled";
375
+ else
376
+ text = `Subagent ${agentName} ${result.instanceName} started (job: ${result.requestId})`;
377
+ return {
378
+ content: [{ type: "text", text }],
379
+ details: result.makeDetails([]),
380
+ };
298
381
  }
299
382
 
300
383
  function needsProjectAgentConfirmation(
@@ -316,14 +399,15 @@ function confirmProjectAgentRun(
316
399
  );
317
400
  }
318
401
 
319
- export async function startSubagentJob(
402
+ async function prepareSubagentJob(
320
403
  pi: ExtensionAPI,
321
404
  ctx: ExtensionContext,
322
405
  params: Static<typeof SubagentParams>,
323
406
  hostSignal: AbortSignal | undefined,
324
- ): Promise<StartJobResult> {
407
+ hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>,
408
+ ): Promise<PrepareSubagentJobResult> {
325
409
  const agentScope: AgentScope = params.agentScope ?? "both";
326
- const discovery = getCachedAgentDiscovery(ctx.cwd, agentScope);
410
+ const discovery = await getCachedAgentDiscovery(ctx.cwd, agentScope);
327
411
  const agents = discovery.agents;
328
412
  const debug = params.debug === true;
329
413
  const makeDetails = createDetailsBuilder(
@@ -344,7 +428,7 @@ export async function startSubagentJob(
344
428
  if (!confirmed) return { kind: "cancelled", makeDetails };
345
429
  }
346
430
  if (requested.source === "project") {
347
- const userAgents = discoverAgents(ctx.cwd, "user");
431
+ const userAgents = await getCachedAgentDiscovery(ctx.cwd, "user");
348
432
  const hasUserCollision = userAgents.agents.some(
349
433
  (a) => a.name === requested.name,
350
434
  );
@@ -387,32 +471,70 @@ export async function startSubagentJob(
387
471
  details: { agent: params.agent, instanceName, requestId },
388
472
  });
389
473
  const requestProgressRender = createProgressRenderRequester(ctx, requestId);
390
- setImmediate(() => {
391
- if (mergedSignal.aborted) {
392
- cancelStartedJob(job, job.cancelReason ?? "Aborted");
393
- requestProgressRender();
394
- return;
395
- }
396
- void runSubagentWorker(
474
+ if (mergedSignal.aborted) {
475
+ cancelStartedJob(job, job.cancelReason ?? "Aborted");
476
+ requestProgressRender();
477
+ return { kind: "aborted", makeDetails: makeStartedDetails };
478
+ }
479
+ return {
480
+ kind: "ready",
481
+ lc: {
397
482
  pi,
398
483
  ctx,
484
+ requestId,
485
+ job,
486
+ debug,
487
+ makeDetails: makeStartedDetails,
488
+ mergedSignal,
399
489
  agents,
400
- params.agent,
490
+ agentName: params.agent,
401
491
  task,
402
- debug,
403
492
  parentModel,
404
493
  parentThinking,
405
- makeStartedDetails,
406
- requestId,
407
- job,
408
- mergedSignal,
409
- );
494
+ hostOnUpdate,
495
+ },
496
+ instanceName,
497
+ requestProgressRender,
498
+ hostOnUpdate,
499
+ };
500
+ }
501
+
502
+ export async function startSubagentJob(
503
+ pi: ExtensionAPI,
504
+ ctx: ExtensionContext,
505
+ params: Static<typeof SubagentParams>,
506
+ hostSignal: AbortSignal | undefined,
507
+ hostOnUpdate?: AgentToolUpdateCallback<SubagentDetails>,
508
+ ): Promise<StartJobResult> {
509
+ const prepared = await prepareSubagentJob(
510
+ pi,
511
+ ctx,
512
+ params,
513
+ hostSignal,
514
+ hostOnUpdate,
515
+ );
516
+ if (prepared.kind !== "ready") {
517
+ if (prepared.kind === "aborted")
518
+ return { kind: "cancelled", makeDetails: prepared.makeDetails };
519
+ return prepared;
520
+ }
521
+ const { lc, instanceName, requestProgressRender } = prepared;
522
+ if (getSubagentDepth() > 0) {
523
+ const result = await runSubagentLifecycle(lc);
524
+ return { kind: "completed", result };
525
+ }
526
+ setImmediate(() => {
527
+ if (lc.mergedSignal.aborted) {
528
+ cancelStartedJob(lc.job, lc.job.cancelReason ?? "Aborted");
529
+ requestProgressRender();
530
+ return;
531
+ }
532
+ runSubagentLifecycle(lc);
410
533
  });
411
- if (mergedSignal.aborted) return { kind: "cancelled", makeDetails };
412
534
  return {
413
535
  kind: "started",
414
- requestId,
536
+ requestId: lc.requestId,
415
537
  instanceName,
416
- makeDetails: makeStartedDetails,
538
+ makeDetails: lc.makeDetails,
417
539
  };
418
540
  }