pi-subagents 0.11.4 → 0.11.6
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 +16 -0
- package/README.md +1 -1
- package/agents/delegate.md +6 -0
- package/index.ts +28 -4
- package/package.json +1 -1
- package/prompt-template-bridge.ts +225 -0
- package/subagent-executor.ts +40 -8
- package/types.ts +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.11.6] - 2026-03-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added `delegate` builtin agent — a lightweight subagent with no model, output, or default reads. Inherits the parent session's model, making it the natural target for prompt-template delegated execution.
|
|
9
|
+
|
|
10
|
+
## [0.11.5] - 2026-03-20
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added fork context preamble: tasks run with `context: "fork"` are now wrapped with a default preamble that anchors the subagent to its task, preventing it from continuing the parent conversation. The default is `DEFAULT_FORK_PREAMBLE` in `types.ts`. Internal/programmatic callers can use `wrapForkTask(task, false)` to disable it or pass a custom string (this is not exposed as a tool parameter).
|
|
14
|
+
- Added a prompt-template delegation bridge (`prompt-template-bridge.ts`) on the shared extension event bus. The subagent extension now listens for `prompt-template:subagent:request` and emits correlated `started`/`response`/`update` events, with cwd safety checks and race-safe cancellation handling.
|
|
15
|
+
- Added delegated progress streaming via `prompt-template:subagent:update`, mapped from subagent executor `onUpdate` progress payloads.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Session lifecycle reset now preserves the latest extension context for event-bus delegated runs.
|
|
19
|
+
- `[fork]` badge is now shown only on the result row, not duplicated on both the tool-call and result rows.
|
|
20
|
+
|
|
5
21
|
## [0.11.4] - 2026-03-19
|
|
6
22
|
|
|
7
23
|
### Added
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Agents are markdown files with YAML frontmatter that define specialized subagent
|
|
|
34
34
|
|
|
35
35
|
Use `agentScope` parameter to control discovery: `"user"`, `"project"`, or `"both"` (default; project takes priority).
|
|
36
36
|
|
|
37
|
-
**Builtin agents:** The extension ships with ready-to-use agents — `scout`, `planner`, `worker`, `reviewer`, `context-builder`, and `
|
|
37
|
+
**Builtin agents:** The extension ships with ready-to-use agents — `scout`, `planner`, `worker`, `reviewer`, `context-builder`, `researcher`, and `delegate`. They load at lowest priority so any user or project agent with the same name overrides them. Builtin agents appear with a `[builtin]` badge in listings and cannot be modified through management actions (create a same-named user agent to override instead).
|
|
38
38
|
|
|
39
39
|
> **Note:** The `researcher` agent uses `web_search`, `fetch_content`, and `get_search_content` tools which require the [pi-web-access](https://github.com/nicobailon/pi-web-access) extension. Install it with `pi install npm:pi-web-access`.
|
|
40
40
|
|
package/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { createSubagentExecutor } from "./subagent-executor.js";
|
|
|
27
27
|
import { createAsyncJobTracker } from "./async-job-tracker.js";
|
|
28
28
|
import { createResultWatcher } from "./result-watcher.js";
|
|
29
29
|
import { registerSlashCommands } from "./slash-commands.js";
|
|
30
|
+
import { registerPromptTemplateDelegationBridge } from "./prompt-template-bridge.js";
|
|
30
31
|
import {
|
|
31
32
|
type Details,
|
|
32
33
|
type ExtensionConfig,
|
|
@@ -138,6 +139,27 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
138
139
|
discoverAgents,
|
|
139
140
|
});
|
|
140
141
|
|
|
142
|
+
const promptTemplateBridge = registerPromptTemplateDelegationBridge({
|
|
143
|
+
events: pi.events,
|
|
144
|
+
getContext: () => state.lastUiContext,
|
|
145
|
+
execute: async (requestId, request, signal, ctx, onUpdate) =>
|
|
146
|
+
executor.execute(
|
|
147
|
+
requestId,
|
|
148
|
+
{
|
|
149
|
+
agent: request.agent,
|
|
150
|
+
task: request.task,
|
|
151
|
+
context: request.context,
|
|
152
|
+
cwd: request.cwd,
|
|
153
|
+
model: request.model,
|
|
154
|
+
async: false,
|
|
155
|
+
clarify: false,
|
|
156
|
+
},
|
|
157
|
+
signal,
|
|
158
|
+
onUpdate,
|
|
159
|
+
ctx,
|
|
160
|
+
),
|
|
161
|
+
});
|
|
162
|
+
|
|
141
163
|
const tool: ToolDefinition<typeof SubagentParams, Details> = {
|
|
142
164
|
name: "subagent",
|
|
143
165
|
label: "Subagent",
|
|
@@ -184,21 +206,20 @@ MANAGEMENT (use action field — omit agent/task/chain/tasks):
|
|
|
184
206
|
}
|
|
185
207
|
const isParallel = (args.tasks?.length ?? 0) > 0;
|
|
186
208
|
const asyncLabel = args.async === true && !isParallel ? theme.fg("warning", " [async]") : "";
|
|
187
|
-
const contextLabel = args.context === "fork" ? theme.fg("warning", " [fork]") : "";
|
|
188
209
|
if (args.chain?.length)
|
|
189
210
|
return new Text(
|
|
190
|
-
`${theme.fg("toolTitle", theme.bold("subagent "))}chain (${args.chain.length})${asyncLabel}
|
|
211
|
+
`${theme.fg("toolTitle", theme.bold("subagent "))}chain (${args.chain.length})${asyncLabel}`,
|
|
191
212
|
0,
|
|
192
213
|
0,
|
|
193
214
|
);
|
|
194
215
|
if (isParallel)
|
|
195
216
|
return new Text(
|
|
196
|
-
`${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${args.tasks!.length})
|
|
217
|
+
`${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${args.tasks!.length})`,
|
|
197
218
|
0,
|
|
198
219
|
0,
|
|
199
220
|
);
|
|
200
221
|
return new Text(
|
|
201
|
-
`${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent || "?")}${asyncLabel}
|
|
222
|
+
`${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent || "?")}${asyncLabel}`,
|
|
202
223
|
0,
|
|
203
224
|
0,
|
|
204
225
|
);
|
|
@@ -332,6 +353,7 @@ MANAGEMENT (use action field — omit agent/task/chain/tasks):
|
|
|
332
353
|
const resetSessionState = (ctx: ExtensionContext) => {
|
|
333
354
|
state.baseCwd = ctx.cwd;
|
|
334
355
|
state.currentSessionId = ctx.sessionManager.getSessionFile() ?? `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
356
|
+
state.lastUiContext = ctx;
|
|
335
357
|
cleanupSessionArtifacts(ctx);
|
|
336
358
|
resetJobs(ctx);
|
|
337
359
|
};
|
|
@@ -354,6 +376,8 @@ MANAGEMENT (use action field — omit agent/task/chain/tasks):
|
|
|
354
376
|
}
|
|
355
377
|
state.cleanupTimers.clear();
|
|
356
378
|
state.asyncJobs.clear();
|
|
379
|
+
promptTemplateBridge.cancelAll();
|
|
380
|
+
promptTemplateBridge.dispose();
|
|
357
381
|
if (state.lastUiContext?.hasUI) {
|
|
358
382
|
state.lastUiContext.ui.setWidget(WIDGET_KEY, undefined);
|
|
359
383
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
export const PROMPT_TEMPLATE_SUBAGENT_REQUEST_EVENT = "prompt-template:subagent:request";
|
|
2
|
+
export const PROMPT_TEMPLATE_SUBAGENT_STARTED_EVENT = "prompt-template:subagent:started";
|
|
3
|
+
export const PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT = "prompt-template:subagent:response";
|
|
4
|
+
export const PROMPT_TEMPLATE_SUBAGENT_UPDATE_EVENT = "prompt-template:subagent:update";
|
|
5
|
+
export const PROMPT_TEMPLATE_SUBAGENT_CANCEL_EVENT = "prompt-template:subagent:cancel";
|
|
6
|
+
|
|
7
|
+
export interface PromptTemplateDelegationRequest {
|
|
8
|
+
requestId: string;
|
|
9
|
+
agent: string;
|
|
10
|
+
task: string;
|
|
11
|
+
context: "fresh" | "fork";
|
|
12
|
+
model: string;
|
|
13
|
+
cwd: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PromptTemplateDelegationResponse extends PromptTemplateDelegationRequest {
|
|
17
|
+
messages: unknown[];
|
|
18
|
+
isError: boolean;
|
|
19
|
+
errorText?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PromptTemplateDelegationUpdate {
|
|
23
|
+
requestId: string;
|
|
24
|
+
currentTool?: string;
|
|
25
|
+
currentToolArgs?: string;
|
|
26
|
+
recentOutput?: string;
|
|
27
|
+
toolCount?: number;
|
|
28
|
+
durationMs?: number;
|
|
29
|
+
tokens?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PromptTemplateBridgeEvents {
|
|
33
|
+
on(event: string, handler: (data: unknown) => void): (() => void) | void;
|
|
34
|
+
emit(event: string, data: unknown): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface PromptTemplateBridgeResult {
|
|
38
|
+
isError?: boolean;
|
|
39
|
+
content?: unknown;
|
|
40
|
+
details?: {
|
|
41
|
+
results?: Array<{
|
|
42
|
+
messages?: unknown[];
|
|
43
|
+
}>;
|
|
44
|
+
progress?: Array<{
|
|
45
|
+
currentTool?: string;
|
|
46
|
+
currentToolArgs?: string;
|
|
47
|
+
recentOutput?: string[];
|
|
48
|
+
toolCount?: number;
|
|
49
|
+
}>;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface PromptTemplateBridgeOptions<Ctx extends { cwd?: string }> {
|
|
54
|
+
events: PromptTemplateBridgeEvents;
|
|
55
|
+
getContext: () => Ctx | null;
|
|
56
|
+
execute: (
|
|
57
|
+
requestId: string,
|
|
58
|
+
request: PromptTemplateDelegationRequest,
|
|
59
|
+
signal: AbortSignal,
|
|
60
|
+
ctx: Ctx,
|
|
61
|
+
onUpdate: (result: PromptTemplateBridgeResult) => void,
|
|
62
|
+
) => Promise<PromptTemplateBridgeResult>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function parsePromptTemplateRequest(data: unknown): PromptTemplateDelegationRequest | undefined {
|
|
66
|
+
if (!data || typeof data !== "object") return undefined;
|
|
67
|
+
const value = data as Partial<PromptTemplateDelegationRequest>;
|
|
68
|
+
if (!value.requestId || !value.agent || !value.task || !value.model || !value.cwd) return undefined;
|
|
69
|
+
if (value.context !== "fresh" && value.context !== "fork") return undefined;
|
|
70
|
+
return {
|
|
71
|
+
requestId: value.requestId,
|
|
72
|
+
agent: value.agent,
|
|
73
|
+
task: value.task,
|
|
74
|
+
context: value.context,
|
|
75
|
+
model: value.model,
|
|
76
|
+
cwd: value.cwd,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function firstTextContent(content: unknown): string | undefined {
|
|
81
|
+
if (!Array.isArray(content)) return undefined;
|
|
82
|
+
for (const part of content) {
|
|
83
|
+
if (!part || typeof part !== "object") continue;
|
|
84
|
+
if ((part as { type?: string }).type !== "text") continue;
|
|
85
|
+
const text = (part as { text?: unknown }).text;
|
|
86
|
+
if (typeof text === "string" && text.trim()) return text.trim();
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function toDelegationUpdate(requestId: string, update: PromptTemplateBridgeResult): PromptTemplateDelegationUpdate | undefined {
|
|
92
|
+
const progress = update.details?.progress?.[0];
|
|
93
|
+
if (!progress) return undefined;
|
|
94
|
+
const lastOutput = progress.recentOutput?.[progress.recentOutput.length - 1];
|
|
95
|
+
return {
|
|
96
|
+
requestId,
|
|
97
|
+
currentTool: progress.currentTool,
|
|
98
|
+
currentToolArgs: progress.currentToolArgs,
|
|
99
|
+
recentOutput: lastOutput && lastOutput !== "(running...)" ? lastOutput : undefined,
|
|
100
|
+
toolCount: progress.toolCount,
|
|
101
|
+
durationMs: (progress as { durationMs?: number }).durationMs,
|
|
102
|
+
tokens: (progress as { tokens?: number }).tokens,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function registerPromptTemplateDelegationBridge<Ctx extends { cwd?: string }>(
|
|
107
|
+
options: PromptTemplateBridgeOptions<Ctx>,
|
|
108
|
+
): {
|
|
109
|
+
cancelAll: () => void;
|
|
110
|
+
dispose: () => void;
|
|
111
|
+
} {
|
|
112
|
+
const controllers = new Map<string, AbortController>();
|
|
113
|
+
const pendingCancels = new Set<string>();
|
|
114
|
+
const subscriptions: Array<() => void> = [];
|
|
115
|
+
|
|
116
|
+
const subscribe = (event: string, handler: (data: unknown) => void): void => {
|
|
117
|
+
const unsubscribe = options.events.on(event, handler);
|
|
118
|
+
if (typeof unsubscribe === "function") subscriptions.push(unsubscribe);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
subscribe(PROMPT_TEMPLATE_SUBAGENT_CANCEL_EVENT, (data) => {
|
|
122
|
+
if (!data || typeof data !== "object") return;
|
|
123
|
+
const requestId = (data as { requestId?: unknown }).requestId;
|
|
124
|
+
if (typeof requestId !== "string") return;
|
|
125
|
+
const controller = controllers.get(requestId);
|
|
126
|
+
if (controller) {
|
|
127
|
+
controller.abort();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
pendingCancels.add(requestId);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
subscribe(PROMPT_TEMPLATE_SUBAGENT_REQUEST_EVENT, async (data) => {
|
|
134
|
+
const request = parsePromptTemplateRequest(data);
|
|
135
|
+
if (!request) return;
|
|
136
|
+
|
|
137
|
+
const ctx = options.getContext();
|
|
138
|
+
if (!ctx) {
|
|
139
|
+
const response: PromptTemplateDelegationResponse = {
|
|
140
|
+
...request,
|
|
141
|
+
messages: [],
|
|
142
|
+
isError: true,
|
|
143
|
+
errorText: "No active extension context for delegated subagent execution.",
|
|
144
|
+
};
|
|
145
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, response);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof ctx.cwd === "string" && ctx.cwd !== request.cwd) {
|
|
150
|
+
const response: PromptTemplateDelegationResponse = {
|
|
151
|
+
...request,
|
|
152
|
+
messages: [],
|
|
153
|
+
isError: true,
|
|
154
|
+
errorText: `Delegated request cwd mismatch: active context is '${ctx.cwd}' but request asked for '${request.cwd}'. Retry from the target session/cwd.`,
|
|
155
|
+
};
|
|
156
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, response);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
controllers.set(request.requestId, controller);
|
|
162
|
+
|
|
163
|
+
if (pendingCancels.delete(request.requestId)) {
|
|
164
|
+
controller.abort();
|
|
165
|
+
const response: PromptTemplateDelegationResponse = {
|
|
166
|
+
...request,
|
|
167
|
+
messages: [],
|
|
168
|
+
isError: true,
|
|
169
|
+
errorText: "Delegated prompt cancelled.",
|
|
170
|
+
};
|
|
171
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, response);
|
|
172
|
+
controllers.delete(request.requestId);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_STARTED_EVENT, { requestId: request.requestId });
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const result = await options.execute(
|
|
180
|
+
request.requestId,
|
|
181
|
+
request,
|
|
182
|
+
controller.signal,
|
|
183
|
+
ctx,
|
|
184
|
+
(update) => {
|
|
185
|
+
const payload = toDelegationUpdate(request.requestId, update);
|
|
186
|
+
if (!payload) return;
|
|
187
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_UPDATE_EVENT, payload);
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
const messages = result.details?.results?.[0]?.messages ?? [];
|
|
191
|
+
const response: PromptTemplateDelegationResponse = {
|
|
192
|
+
...request,
|
|
193
|
+
messages,
|
|
194
|
+
isError: result.isError === true,
|
|
195
|
+
errorText: result.isError ? firstTextContent(result.content) : undefined,
|
|
196
|
+
};
|
|
197
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, response);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
const response: PromptTemplateDelegationResponse = {
|
|
200
|
+
...request,
|
|
201
|
+
messages: [],
|
|
202
|
+
isError: true,
|
|
203
|
+
errorText: error instanceof Error ? error.message : String(error),
|
|
204
|
+
};
|
|
205
|
+
options.events.emit(PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT, response);
|
|
206
|
+
} finally {
|
|
207
|
+
controllers.delete(request.requestId);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
cancelAll: () => {
|
|
213
|
+
for (const controller of controllers.values()) {
|
|
214
|
+
controller.abort();
|
|
215
|
+
}
|
|
216
|
+
controllers.clear();
|
|
217
|
+
pendingCancels.clear();
|
|
218
|
+
},
|
|
219
|
+
dispose: () => {
|
|
220
|
+
for (const unsubscribe of subscriptions) unsubscribe();
|
|
221
|
+
subscriptions.length = 0;
|
|
222
|
+
pendingCancels.clear();
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
package/subagent-executor.ts
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
MAX_CONCURRENCY,
|
|
38
38
|
MAX_PARALLEL,
|
|
39
39
|
checkSubagentDepth,
|
|
40
|
+
wrapForkTask,
|
|
40
41
|
} from "./types.js";
|
|
41
42
|
|
|
42
43
|
interface TaskParam {
|
|
@@ -223,6 +224,26 @@ function collectChainSessionFiles(
|
|
|
223
224
|
return sessionFiles;
|
|
224
225
|
}
|
|
225
226
|
|
|
227
|
+
function wrapChainTasksForFork(chain: ChainStep[], context: SubagentParamsLike["context"]): ChainStep[] {
|
|
228
|
+
if (context !== "fork") return chain;
|
|
229
|
+
return chain.map((step, stepIndex) => {
|
|
230
|
+
if (isParallelStep(step)) {
|
|
231
|
+
return {
|
|
232
|
+
...step,
|
|
233
|
+
parallel: step.parallel.map((task) => ({
|
|
234
|
+
...task,
|
|
235
|
+
task: wrapForkTask(task.task ?? "{previous}"),
|
|
236
|
+
})),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const sequential = step as SequentialStep;
|
|
240
|
+
return {
|
|
241
|
+
...sequential,
|
|
242
|
+
task: wrapForkTask(sequential.task ?? (stepIndex === 0 ? "{task}" : "{previous}")),
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
226
247
|
function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentToolResult<Details> | null {
|
|
227
248
|
const {
|
|
228
249
|
params,
|
|
@@ -252,8 +273,9 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
252
273
|
if (hasChain && params.chain) {
|
|
253
274
|
const normalized = normalizeSkillInput(params.skill);
|
|
254
275
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
276
|
+
const chain = wrapChainTasksForFork(params.chain as ChainStep[], params.context);
|
|
255
277
|
return executeAsyncChain(id, {
|
|
256
|
-
chain
|
|
278
|
+
chain,
|
|
257
279
|
agents,
|
|
258
280
|
ctx: asyncCtx,
|
|
259
281
|
cwd: params.cwd,
|
|
@@ -263,7 +285,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
263
285
|
shareEnabled,
|
|
264
286
|
sessionRoot,
|
|
265
287
|
chainSkills,
|
|
266
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(
|
|
288
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForIndex),
|
|
267
289
|
});
|
|
268
290
|
}
|
|
269
291
|
|
|
@@ -282,7 +304,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
282
304
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
283
305
|
return executeAsyncSingle(id, {
|
|
284
306
|
agent: params.agent!,
|
|
285
|
-
task: params.task!,
|
|
307
|
+
task: params.context === "fork" ? wrapForkTask(params.task!) : params.task!,
|
|
286
308
|
agentConfig: a,
|
|
287
309
|
ctx: asyncCtx,
|
|
288
310
|
cwd: params.cwd,
|
|
@@ -317,8 +339,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
317
339
|
} = data;
|
|
318
340
|
const normalized = normalizeSkillInput(params.skill);
|
|
319
341
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
342
|
+
const chain = wrapChainTasksForFork(params.chain as ChainStep[], params.context);
|
|
320
343
|
const chainResult = await executeChain({
|
|
321
|
-
chain
|
|
344
|
+
chain,
|
|
322
345
|
task: params.task,
|
|
323
346
|
agents,
|
|
324
347
|
ctx,
|
|
@@ -347,8 +370,9 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
347
370
|
}
|
|
348
371
|
const id = randomUUID();
|
|
349
372
|
const asyncCtx = { pi: deps.pi, cwd: ctx.cwd, currentSessionId: deps.state.currentSessionId! };
|
|
373
|
+
const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, params.context);
|
|
350
374
|
return executeAsyncChain(id, {
|
|
351
|
-
chain:
|
|
375
|
+
chain: asyncChain,
|
|
352
376
|
agents,
|
|
353
377
|
ctx: asyncCtx,
|
|
354
378
|
cwd: params.cwd,
|
|
@@ -358,7 +382,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
358
382
|
shareEnabled,
|
|
359
383
|
sessionRoot,
|
|
360
384
|
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
361
|
-
sessionFilesByFlatIndex: collectChainSessionFiles(
|
|
385
|
+
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForIndex),
|
|
362
386
|
});
|
|
363
387
|
}
|
|
364
388
|
|
|
@@ -463,7 +487,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
463
487
|
const asyncCtx = { pi: deps.pi, cwd: ctx.cwd, currentSessionId: deps.state.currentSessionId! };
|
|
464
488
|
const parallelTasks = tasks.map((t, i) => ({
|
|
465
489
|
agent: t.agent,
|
|
466
|
-
task: taskTexts[i]!,
|
|
490
|
+
task: params.context === "fork" ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!,
|
|
467
491
|
cwd: t.cwd,
|
|
468
492
|
...(modelOverrides[i] ? { model: modelOverrides[i] } : {}),
|
|
469
493
|
...(skillOverrides[i] !== undefined ? { skill: skillOverrides[i] } : {}),
|
|
@@ -487,6 +511,11 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
487
511
|
const behaviors = agentConfigs.map((c) => resolveStepBehavior(c, {}));
|
|
488
512
|
const liveResults: (SingleResult | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
489
513
|
const liveProgress: (AgentProgress | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
514
|
+
if (params.context === "fork") {
|
|
515
|
+
for (let i = 0; i < taskTexts.length; i++) {
|
|
516
|
+
taskTexts[i] = wrapForkTask(taskTexts[i]!);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
490
519
|
const results = await mapConcurrent(tasks, MAX_CONCURRENCY, async (t, i) => {
|
|
491
520
|
const overrideSkills = skillOverrides[i];
|
|
492
521
|
const effectiveSkills = overrideSkills === undefined ? behaviors[i]?.skills : overrideSkills;
|
|
@@ -641,7 +670,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
641
670
|
const asyncCtx = { pi: deps.pi, cwd: ctx.cwd, currentSessionId: deps.state.currentSessionId! };
|
|
642
671
|
return executeAsyncSingle(id, {
|
|
643
672
|
agent: params.agent!,
|
|
644
|
-
task,
|
|
673
|
+
task: params.context === "fork" ? wrapForkTask(task) : task,
|
|
645
674
|
agentConfig,
|
|
646
675
|
ctx: asyncCtx,
|
|
647
676
|
cwd: params.cwd,
|
|
@@ -657,6 +686,9 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
657
686
|
}
|
|
658
687
|
}
|
|
659
688
|
|
|
689
|
+
if (params.context === "fork") {
|
|
690
|
+
task = wrapForkTask(task);
|
|
691
|
+
}
|
|
660
692
|
const cleanTask = task;
|
|
661
693
|
const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, params.cwd);
|
|
662
694
|
task = injectSingleOutputInstruction(task, outputPath);
|
package/types.ts
CHANGED
|
@@ -267,6 +267,19 @@ export const POLL_INTERVAL_MS = 250;
|
|
|
267
267
|
export const MAX_WIDGET_JOBS = 4;
|
|
268
268
|
export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
|
|
269
269
|
|
|
270
|
+
export const DEFAULT_FORK_PREAMBLE =
|
|
271
|
+
"You are a delegated subagent with access to the parent session's context for reference. " +
|
|
272
|
+
"Your sole job is to execute the task below. Do not continue or respond to the prior conversation " +
|
|
273
|
+
"— focus exclusively on completing this task using your tools.";
|
|
274
|
+
|
|
275
|
+
export function wrapForkTask(task: string, preamble?: string | false): string {
|
|
276
|
+
if (preamble === false) return task;
|
|
277
|
+
const effectivePreamble = preamble ?? DEFAULT_FORK_PREAMBLE;
|
|
278
|
+
const wrappedPrefix = `${effectivePreamble}\n\nTask:\n`;
|
|
279
|
+
if (task.startsWith(wrappedPrefix)) return task;
|
|
280
|
+
return `${wrappedPrefix}${task}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
270
283
|
// ============================================================================
|
|
271
284
|
// Recursion Depth Guard
|
|
272
285
|
// ============================================================================
|