@narumitw/pi-subagents 2.0.1 → 2.0.2
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 +21 -8
- package/dist/chunks/auto-transport-SY2VHUFH.ts +109 -0
- package/dist/chunks/auto-transport-SY2VHUFH.ts.map +7 -0
- package/dist/chunks/capability-grant-CGEWOEKE.ts +19 -0
- package/dist/chunks/capability-grant-CGEWOEKE.ts.map +7 -0
- package/dist/chunks/chunk-434NII74.ts +282 -0
- package/dist/chunks/chunk-434NII74.ts.map +7 -0
- package/dist/chunks/chunk-47SK2QYC.ts +56 -0
- package/dist/chunks/chunk-47SK2QYC.ts.map +7 -0
- package/dist/chunks/chunk-6KJ34M6S.ts +159 -0
- package/dist/chunks/chunk-6KJ34M6S.ts.map +7 -0
- package/dist/chunks/chunk-7OBINKFM.ts +80 -0
- package/dist/chunks/chunk-7OBINKFM.ts.map +7 -0
- package/dist/chunks/chunk-7QPPBBXZ.ts +335 -0
- package/dist/chunks/chunk-7QPPBBXZ.ts.map +7 -0
- package/dist/chunks/chunk-AEUYS7JC.ts +73 -0
- package/dist/chunks/chunk-AEUYS7JC.ts.map +7 -0
- package/dist/chunks/chunk-BFK2Z4ZF.ts +189 -0
- package/dist/chunks/chunk-BFK2Z4ZF.ts.map +7 -0
- package/dist/chunks/chunk-BOAXY55Y.ts +153 -0
- package/dist/chunks/chunk-BOAXY55Y.ts.map +7 -0
- package/dist/chunks/chunk-C4L6P266.ts +22 -0
- package/dist/chunks/chunk-C4L6P266.ts.map +7 -0
- package/dist/chunks/chunk-DPPVEQAM.ts +29 -0
- package/dist/chunks/chunk-DPPVEQAM.ts.map +7 -0
- package/dist/chunks/chunk-DQMN4OYM.ts +913 -0
- package/dist/chunks/chunk-DQMN4OYM.ts.map +7 -0
- package/dist/chunks/chunk-DSOOH73Y.ts +238 -0
- package/dist/chunks/chunk-DSOOH73Y.ts.map +7 -0
- package/dist/chunks/chunk-EFBPISNT.ts +421 -0
- package/dist/chunks/chunk-EFBPISNT.ts.map +7 -0
- package/dist/chunks/chunk-EWWXFU7N.ts +150 -0
- package/dist/chunks/chunk-EWWXFU7N.ts.map +7 -0
- package/dist/chunks/chunk-F4L2UVSK.ts +380 -0
- package/dist/chunks/chunk-F4L2UVSK.ts.map +7 -0
- package/dist/chunks/chunk-F5YWVHQX.ts +1543 -0
- package/dist/chunks/chunk-F5YWVHQX.ts.map +7 -0
- package/dist/chunks/chunk-G3RSMSXJ.ts +63 -0
- package/dist/chunks/chunk-G3RSMSXJ.ts.map +7 -0
- package/dist/chunks/chunk-G6VZTCFH.ts +70 -0
- package/dist/chunks/chunk-G6VZTCFH.ts.map +7 -0
- package/dist/chunks/chunk-H3FP6DLR.ts +160 -0
- package/dist/chunks/chunk-H3FP6DLR.ts.map +7 -0
- package/dist/chunks/chunk-HA36LPCO.ts +130 -0
- package/dist/chunks/chunk-HA36LPCO.ts.map +7 -0
- package/dist/chunks/chunk-HKC4ES4B.ts +65 -0
- package/dist/chunks/chunk-HKC4ES4B.ts.map +7 -0
- package/dist/chunks/chunk-HOP5FXDT.ts +27 -0
- package/dist/chunks/chunk-HOP5FXDT.ts.map +7 -0
- package/dist/chunks/chunk-I2FAK44T.ts +324 -0
- package/dist/chunks/chunk-I2FAK44T.ts.map +7 -0
- package/dist/chunks/chunk-IGAMVWFN.ts +139 -0
- package/dist/chunks/chunk-IGAMVWFN.ts.map +7 -0
- package/dist/chunks/chunk-KMGKCEO4.ts +38 -0
- package/dist/chunks/chunk-KMGKCEO4.ts.map +7 -0
- package/dist/chunks/chunk-N7BLVXKK.ts +73 -0
- package/dist/chunks/chunk-N7BLVXKK.ts.map +7 -0
- package/dist/chunks/chunk-NIF42QMF.ts +297 -0
- package/dist/chunks/chunk-NIF42QMF.ts.map +7 -0
- package/dist/chunks/chunk-O4VO6JUJ.ts +52 -0
- package/dist/chunks/chunk-O4VO6JUJ.ts.map +7 -0
- package/dist/chunks/chunk-ONDTY4EL.ts +91 -0
- package/dist/chunks/chunk-ONDTY4EL.ts.map +7 -0
- package/dist/chunks/chunk-OVPHFGKO.ts +1769 -0
- package/dist/chunks/chunk-OVPHFGKO.ts.map +7 -0
- package/dist/chunks/chunk-P7OH4XMF.ts +60 -0
- package/dist/chunks/chunk-P7OH4XMF.ts.map +7 -0
- package/dist/chunks/chunk-PSK43Y6A.ts +339 -0
- package/dist/chunks/chunk-PSK43Y6A.ts.map +7 -0
- package/dist/chunks/chunk-QBORTI4W.ts +468 -0
- package/dist/chunks/chunk-QBORTI4W.ts.map +7 -0
- package/dist/chunks/chunk-QDVGH2X7.ts +171 -0
- package/dist/chunks/chunk-QDVGH2X7.ts.map +7 -0
- package/dist/chunks/chunk-RHRQMT7Q.ts +490 -0
- package/dist/chunks/chunk-RHRQMT7Q.ts.map +7 -0
- package/dist/chunks/chunk-RRR66UWR.ts +28 -0
- package/dist/chunks/chunk-RRR66UWR.ts.map +7 -0
- package/dist/chunks/chunk-RSUXZD6S.ts +275 -0
- package/dist/chunks/chunk-RSUXZD6S.ts.map +7 -0
- package/dist/chunks/chunk-RTCVYIZA.ts +221 -0
- package/dist/chunks/chunk-RTCVYIZA.ts.map +7 -0
- package/dist/chunks/chunk-S2IWVK3J.ts +44 -0
- package/dist/chunks/chunk-S2IWVK3J.ts.map +7 -0
- package/dist/chunks/chunk-SCZ33MYW.ts +52 -0
- package/dist/chunks/chunk-SCZ33MYW.ts.map +7 -0
- package/dist/chunks/chunk-SWGQLFSD.ts +60 -0
- package/dist/chunks/chunk-SWGQLFSD.ts.map +7 -0
- package/dist/chunks/chunk-UMBPMVJW.ts +22 -0
- package/dist/chunks/chunk-UMBPMVJW.ts.map +7 -0
- package/dist/chunks/chunk-UVFVHIOP.ts +735 -0
- package/dist/chunks/chunk-UVFVHIOP.ts.map +7 -0
- package/dist/chunks/completion-delivery-YPOWSSV3.ts +16 -0
- package/dist/chunks/completion-delivery-YPOWSSV3.ts.map +7 -0
- package/dist/chunks/config-status-J4GPWP46.ts +34 -0
- package/dist/chunks/config-status-J4GPWP46.ts.map +7 -0
- package/dist/chunks/config-ui-3IBMHTQB.ts +1146 -0
- package/dist/chunks/config-ui-3IBMHTQB.ts.map +7 -0
- package/dist/chunks/consult-NYXVDN5G.ts +518 -0
- package/dist/chunks/consult-NYXVDN5G.ts.map +7 -0
- package/dist/chunks/context-QDKLVQXE.ts +11 -0
- package/dist/chunks/context-QDKLVQXE.ts.map +7 -0
- package/dist/chunks/create-stateful-transport-4WKZHRSI.ts +130 -0
- package/dist/chunks/create-stateful-transport-4WKZHRSI.ts.map +7 -0
- package/dist/chunks/cwd-policy-NB6XZ5GF.ts +17 -0
- package/dist/chunks/cwd-policy-NB6XZ5GF.ts.map +7 -0
- package/dist/chunks/delegation-contract-WJPT4FVS.ts +24 -0
- package/dist/chunks/delegation-contract-WJPT4FVS.ts.map +7 -0
- package/dist/chunks/discovery-K25T3SH4.ts +12 -0
- package/dist/chunks/discovery-K25T3SH4.ts.map +7 -0
- package/dist/chunks/execution-DS2DEIQZ.ts +4034 -0
- package/dist/chunks/execution-DS2DEIQZ.ts.map +7 -0
- package/dist/chunks/in-process-transport-TEYAWSEC.ts +38 -0
- package/dist/chunks/in-process-transport-TEYAWSEC.ts.map +7 -0
- package/dist/chunks/inspect-UOQQ3KNL.ts +619 -0
- package/dist/chunks/inspect-UOQQ3KNL.ts.map +7 -0
- package/dist/chunks/peer-communication-KL36Z6CO.ts +294 -0
- package/dist/chunks/peer-communication-KL36Z6CO.ts.map +7 -0
- package/dist/chunks/persistence-VSGXAW4P.ts +344 -0
- package/dist/chunks/persistence-VSGXAW4P.ts.map +7 -0
- package/dist/chunks/registry-E6XPJB7L.ts +1324 -0
- package/dist/chunks/registry-E6XPJB7L.ts.map +7 -0
- package/dist/chunks/retained-semantic-state-7WXSGRJU.ts +81 -0
- package/dist/chunks/retained-semantic-state-7WXSGRJU.ts.map +7 -0
- package/dist/chunks/rpc-transport-Y6LA5BOP.ts +1154 -0
- package/dist/chunks/rpc-transport-Y6LA5BOP.ts.map +7 -0
- package/dist/chunks/runtime-policy-5YELCOVC.ts +15 -0
- package/dist/chunks/runtime-policy-5YELCOVC.ts.map +7 -0
- package/dist/chunks/semantic-snapshot-WPJ3UAFW.ts +19 -0
- package/dist/chunks/semantic-snapshot-WPJ3UAFW.ts.map +7 -0
- package/dist/chunks/spawn-idempotency-QGHO2WKC.ts +13 -0
- package/dist/chunks/spawn-idempotency-QGHO2WKC.ts.map +7 -0
- package/dist/chunks/stateful-lifecycle-JAR6K55H.ts +15 -0
- package/dist/chunks/stateful-lifecycle-JAR6K55H.ts.map +7 -0
- package/dist/chunks/subprocess-transport-C2XVR25R.ts +158 -0
- package/dist/chunks/subprocess-transport-C2XVR25R.ts.map +7 -0
- package/dist/chunks/workspace-WDKLN72J.ts +11 -0
- package/dist/chunks/workspace-WDKLN72J.ts.map +7 -0
- package/dist/index.ts +1057 -0
- package/dist/index.ts.map +7 -0
- package/package.json +7 -3
- package/src/index.ts +1 -1
- package/src/settings-reader.ts +208 -0
- package/src/settings.ts +22 -187
- package/src/stateful-registration.ts +1230 -0
- package/src/stateful-safety.ts +1 -1
- package/src/stateful.ts +2 -1234
- package/src/subagents-extension.ts +307 -0
- package/src/subagents.ts +1 -308
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent Tool - Delegate tasks to specialized agents
|
|
3
|
+
*
|
|
4
|
+
* Spawns a separate `pi` process for each subagent invocation,
|
|
5
|
+
* giving it an isolated context window.
|
|
6
|
+
*
|
|
7
|
+
* Supports blocking single, parallel, chain, workflow, and panel modes.
|
|
8
|
+
*
|
|
9
|
+
* Uses JSON mode to capture structured output from subagents.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
CONFIG_DIR_NAME,
|
|
14
|
+
type ExtensionAPI,
|
|
15
|
+
type ToolDefinition,
|
|
16
|
+
} from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import { discoverAgentCatalog, formatAgentCatalog } from "./agents/catalog.js";
|
|
18
|
+
import type {
|
|
19
|
+
ConsultationCwdPolicy,
|
|
20
|
+
ConsultResourcePolicy,
|
|
21
|
+
DelegationCwdPolicy,
|
|
22
|
+
SubagentSettings,
|
|
23
|
+
} from "./agents/types.js";
|
|
24
|
+
import { cachedModuleLoader, throwIfAborted } from "./cached-module-loader.js";
|
|
25
|
+
import { renderCompletionMessage, SUBAGENT_COMPLETION_MESSAGE_TYPE } from "./completion-render.js";
|
|
26
|
+
import {
|
|
27
|
+
type ConfigRegistrationDependencies,
|
|
28
|
+
registerSubagentConfigCommand,
|
|
29
|
+
registerSubagentConfigLifecycle,
|
|
30
|
+
} from "./config-registration.js";
|
|
31
|
+
import {
|
|
32
|
+
type ConsultRegistrationDependencies,
|
|
33
|
+
registerSubagentConsult,
|
|
34
|
+
} from "./consult-registration.js";
|
|
35
|
+
import {
|
|
36
|
+
type InspectRegistrationDependencies,
|
|
37
|
+
registerSubagentInspect,
|
|
38
|
+
} from "./inspect-registration.js";
|
|
39
|
+
import { MAX_BLOCKING_PARALLEL_CONCURRENCY } from "./limits.js";
|
|
40
|
+
import { SubagentParams } from "./params.js";
|
|
41
|
+
import { renderSubagentCall, renderSubagentResult } from "./render.js";
|
|
42
|
+
import type { SubagentDetails } from "./runner.js";
|
|
43
|
+
import {
|
|
44
|
+
consumeSubagentSettingsNotice,
|
|
45
|
+
DEFAULT_CONSULT_RESOURCE_POLICY,
|
|
46
|
+
DEFAULT_CONSULTATION_CWD_POLICY,
|
|
47
|
+
DEFAULT_DELEGATION_CWD_POLICY,
|
|
48
|
+
inspectSubagentSettings,
|
|
49
|
+
readSubagentSettings,
|
|
50
|
+
resolveBlockingMaxParallelTasks,
|
|
51
|
+
} from "./settings-reader.js";
|
|
52
|
+
import { registerStatefulSubagents } from "./stateful-registration.js";
|
|
53
|
+
import type { SubagentTransport } from "./transport.js";
|
|
54
|
+
|
|
55
|
+
type BlockingExecutionModule = Pick<typeof import("./execution.js"), "executeSubagent">;
|
|
56
|
+
|
|
57
|
+
export interface SubagentsDependencies {
|
|
58
|
+
loadBlockingExecution?: () => Promise<BlockingExecutionModule>;
|
|
59
|
+
loadStatefulTransport?: () => Promise<SubagentTransport>;
|
|
60
|
+
config?: ConfigRegistrationDependencies;
|
|
61
|
+
consult?: ConsultRegistrationDependencies;
|
|
62
|
+
inspect?: InspectRegistrationDependencies;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies = {}) {
|
|
66
|
+
pi.registerMessageRenderer(SUBAGENT_COMPLETION_MESSAGE_TYPE, renderCompletionMessage);
|
|
67
|
+
const loadBlockingExecution = cachedModuleLoader(
|
|
68
|
+
dependencies.loadBlockingExecution ?? (() => import("./execution.js")),
|
|
69
|
+
);
|
|
70
|
+
const configOwner = registerSubagentConfigLifecycle(pi);
|
|
71
|
+
const settings = readSubagentSettings();
|
|
72
|
+
let currentSettings: SubagentSettings | undefined = settings;
|
|
73
|
+
let currentCatalog = "";
|
|
74
|
+
const blockingEnabled = settings?.blocking?.enabled !== false;
|
|
75
|
+
const refreshBlockingCatalog = blockingEnabled
|
|
76
|
+
? registerBlockingSubagent(pi, () => currentSettings, loadBlockingExecution)
|
|
77
|
+
: () => undefined;
|
|
78
|
+
let refreshStatefulCatalog: (catalog: string) => void = () => undefined;
|
|
79
|
+
let refreshConsultCatalog: (catalog: string) => void = () => undefined;
|
|
80
|
+
|
|
81
|
+
pi.on("session_start", (_event, ctx) => {
|
|
82
|
+
// Preserve a one-shot migration notice from extension load while refreshing
|
|
83
|
+
// validation against settings that may have changed before this session.
|
|
84
|
+
const loadNotice = consumeSubagentSettingsNotice();
|
|
85
|
+
const refreshedSettings = readSubagentSettings();
|
|
86
|
+
const refreshedNotice = consumeSubagentSettingsNotice();
|
|
87
|
+
if (!inspectSubagentSettings().error) currentSettings = refreshedSettings;
|
|
88
|
+
const notice = [
|
|
89
|
+
...new Set([loadNotice, refreshedNotice].filter((value) => value !== undefined)),
|
|
90
|
+
].join("\n");
|
|
91
|
+
if (notice) ctx.ui.notify(notice, "warning");
|
|
92
|
+
|
|
93
|
+
currentCatalog = formatAgentCatalog(
|
|
94
|
+
discoverAgentCatalog(ctx.cwd, ctx.isProjectTrusted(), refreshedSettings),
|
|
95
|
+
).text;
|
|
96
|
+
refreshBlockingCatalog(currentCatalog);
|
|
97
|
+
refreshStatefulCatalog(currentCatalog);
|
|
98
|
+
refreshConsultCatalog(currentCatalog);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const statefulRuntime = registerStatefulSubagents(pi, {
|
|
102
|
+
blockingEnabled,
|
|
103
|
+
settings: settings?.stateful,
|
|
104
|
+
getSettings: () => currentSettings,
|
|
105
|
+
loadTransport: dependencies.loadStatefulTransport,
|
|
106
|
+
});
|
|
107
|
+
refreshStatefulCatalog = statefulRuntime.setAgentCatalog;
|
|
108
|
+
const getBlockingEnabled = () => blockingEnabled;
|
|
109
|
+
const getMaxParallelTasks = () => resolveBlockingMaxParallelTasks(currentSettings);
|
|
110
|
+
const getConsultResourcePolicy = () =>
|
|
111
|
+
currentSettings?.consult?.resources ?? DEFAULT_CONSULT_RESOURCE_POLICY;
|
|
112
|
+
const getConsultationCwdPolicy = () =>
|
|
113
|
+
currentSettings?.cwdPolicy?.consultation ?? DEFAULT_CONSULTATION_CWD_POLICY;
|
|
114
|
+
const getDelegationCwdPolicy = () =>
|
|
115
|
+
currentSettings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY;
|
|
116
|
+
registerSubagentInspect(
|
|
117
|
+
pi,
|
|
118
|
+
{
|
|
119
|
+
...statefulRuntime,
|
|
120
|
+
getBlockingEnabled,
|
|
121
|
+
getMaxParallelTasks,
|
|
122
|
+
getConsultResourcePolicy,
|
|
123
|
+
getConsultationCwdPolicy,
|
|
124
|
+
getDelegationCwdPolicy,
|
|
125
|
+
},
|
|
126
|
+
dependencies.inspect,
|
|
127
|
+
);
|
|
128
|
+
if (blockingEnabled) {
|
|
129
|
+
refreshConsultCatalog = registerSubagentConsult(
|
|
130
|
+
pi,
|
|
131
|
+
{ getSettings: () => currentSettings },
|
|
132
|
+
dependencies.consult,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
registerSubagentConfigCommand(
|
|
136
|
+
pi,
|
|
137
|
+
{
|
|
138
|
+
...statefulRuntime,
|
|
139
|
+
getBlockingEnabled,
|
|
140
|
+
getMaxParallelTasks,
|
|
141
|
+
getConsultResourcePolicy,
|
|
142
|
+
getConsultationCwdPolicy,
|
|
143
|
+
getDelegationCwdPolicy,
|
|
144
|
+
setMaxParallelTasks(value: number) {
|
|
145
|
+
const previousSettings = currentSettings;
|
|
146
|
+
currentSettings = {
|
|
147
|
+
...(currentSettings ?? {}),
|
|
148
|
+
blocking: { ...(currentSettings?.blocking ?? {}), maxParallelTasks: value },
|
|
149
|
+
};
|
|
150
|
+
try {
|
|
151
|
+
refreshBlockingCatalog(currentCatalog);
|
|
152
|
+
} catch (applyError) {
|
|
153
|
+
currentSettings = previousSettings;
|
|
154
|
+
try {
|
|
155
|
+
refreshBlockingCatalog(currentCatalog);
|
|
156
|
+
} catch (rollbackError) {
|
|
157
|
+
throw new AggregateError(
|
|
158
|
+
[applyError, rollbackError],
|
|
159
|
+
"Failed to apply and roll back the parallel-worker limit",
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
throw applyError;
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
setConsultResourcePolicy(value: ConsultResourcePolicy) {
|
|
166
|
+
currentSettings = {
|
|
167
|
+
...(currentSettings ?? {}),
|
|
168
|
+
consult: { ...(currentSettings?.consult ?? {}), resources: value },
|
|
169
|
+
};
|
|
170
|
+
refreshConsultCatalog(currentCatalog);
|
|
171
|
+
},
|
|
172
|
+
setConsultationCwdPolicy(value: ConsultationCwdPolicy) {
|
|
173
|
+
currentSettings = {
|
|
174
|
+
...(currentSettings ?? {}),
|
|
175
|
+
cwdPolicy: { ...(currentSettings?.cwdPolicy ?? {}), consultation: value },
|
|
176
|
+
};
|
|
177
|
+
refreshConsultCatalog(currentCatalog);
|
|
178
|
+
},
|
|
179
|
+
setDelegationCwdPolicy(value: DelegationCwdPolicy) {
|
|
180
|
+
currentSettings = {
|
|
181
|
+
...(currentSettings ?? {}),
|
|
182
|
+
cwdPolicy: { ...(currentSettings?.cwdPolicy ?? {}), delegation: value },
|
|
183
|
+
};
|
|
184
|
+
refreshBlockingCatalog(currentCatalog);
|
|
185
|
+
statefulRuntime.refreshSettingsGuidance();
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
configOwner,
|
|
189
|
+
dependencies.config,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function registerBlockingSubagent(
|
|
194
|
+
pi: ExtensionAPI,
|
|
195
|
+
getSettings: () => SubagentSettings | undefined,
|
|
196
|
+
loadExecution: () => Promise<BlockingExecutionModule>,
|
|
197
|
+
): (catalog: string) => void {
|
|
198
|
+
let catalog = "";
|
|
199
|
+
const activeControllers = new Set<AbortController>();
|
|
200
|
+
const activeWork = new Set<Promise<unknown>>();
|
|
201
|
+
const cancelAndWaitForWork = async (reason: string) => {
|
|
202
|
+
for (const controller of activeControllers) {
|
|
203
|
+
controller.abort(new DOMException(reason, "AbortError"));
|
|
204
|
+
}
|
|
205
|
+
await Promise.allSettled([...activeWork]);
|
|
206
|
+
};
|
|
207
|
+
pi.on("session_start", () => cancelAndWaitForWork("Blocking subagent session replaced"));
|
|
208
|
+
pi.on("session_shutdown", () => cancelAndWaitForWork("Blocking subagent session shut down"));
|
|
209
|
+
const baseDescription = () =>
|
|
210
|
+
[
|
|
211
|
+
"Run specialized subagents as a blocking operation with isolated contexts.",
|
|
212
|
+
"The call blocks the main agent until every worker and optional aggregator finishes, so queued steering waits.",
|
|
213
|
+
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder), workflow (named dependency tasks with optional capability routing), or panel (independent reviewers plus evidence-preserving synthesis).",
|
|
214
|
+
"Parallel mode may include an aggregator fan-in step; workflow mode validates dependencies, authority, artifacts, scope conflicts, retries, and hedging before scheduling. Use subagent_consult instead for one synchronous child that must be executor-constrained to read-only tools.",
|
|
215
|
+
'Default agent scope is "user" (from ~/.pi/agent/agents).',
|
|
216
|
+
`To enable project-local agents in ${CONFIG_DIR_NAME}/agents, pass agentScope: "both" (or "project") as a top-level argument for that call.`,
|
|
217
|
+
`Maximum parallel worker tasks per call: ${resolveBlockingMaxParallelTasks(getSettings())}. Parallel execution starts at most ${MAX_BLOCKING_PARALLEL_CONCURRENCY} workers at once.`,
|
|
218
|
+
`Working-directory target policy: ${getSettings()?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY}. This controls launch targets and protected project resources, not filesystem access or sandboxing.`,
|
|
219
|
+
].join(" ");
|
|
220
|
+
const promptGuidelines = () => [
|
|
221
|
+
"Use subagent only when delegation fits; the main agent should decide how many subagents to spawn from task shape instead of waiting for the user to specify a count.",
|
|
222
|
+
"The main agent retains overall planning, immediate critical-path work, integration, final verification, and the final answer.",
|
|
223
|
+
"Use no subagent for simple answers, quick targeted edits, latency-sensitive one-step work, tasks requiring frequent user back-and-forth, or critical-path work the main agent can perform directly.",
|
|
224
|
+
"One ordinary implementation worker should not replace work the main agent can perform directly; use a blocking single only when intentional synchronous isolation or a user-requested specialist justifies waiting.",
|
|
225
|
+
"Keep ordinary planning in the main agent, or use explicit workflow mode when a genuine dependency graph requires caller-authored orchestration.",
|
|
226
|
+
"Keep ordinary review in the main agent with a review skill and deterministic checks; reserve panel mode or custom verifier agents for consequential independent verification.",
|
|
227
|
+
"Use the blocking subagent tool only when delegated outputs are required before the main agent's next action and waiting is intentional; the main agent cannot process queued steering until the call returns.",
|
|
228
|
+
"Use a blocking subagent single, parallel, chain, workflow, panel, or fan-in call only when synchronous context or output isolation is worth making the main agent unavailable while it runs.",
|
|
229
|
+
`If a blocking parallel subagent call is genuinely required, keep tasks independent, stay within the configured max ${resolveBlockingMaxParallelTasks(getSettings())}, and avoid write-heavy implementation touching the same files or shared state.`,
|
|
230
|
+
"For parallel subagent calls, omit the aggregator key entirely unless a fan-in step is required; do not send null, empty strings, or an empty object for unused optional fields.",
|
|
231
|
+
"Use workflow mode for explicit dependencies or capability routing; declare read/write or ownership scopes, require structured-v2 artifacts when downstream tasks consume them, and use retry or hedging only with the required side-effect contract.",
|
|
232
|
+
"Use panel mode only for consequential review or research that benefits from at least two independent reviewers and one bounded synthesis; agreement is not proof, dissent and blocking objections remain visible, and simple or latency-sensitive work should not use a panel.",
|
|
233
|
+
'Do not use subagent with project-local agents unless the user explicitly wants project agents or sets agentScope to "project" or "both"; keep confirmation enabled for untrusted repositories.',
|
|
234
|
+
"When using subagent, write self-contained tasks with file paths, context, expected output, and whether the subagent may edit files.",
|
|
235
|
+
"Set subagent timeoutMs to the shortest realistic work deadline for the task difficulty, just as thinkingLevel should match reasoning difficulty; split oversized tasks instead of extending the deadline merely to compensate for broad scope. Use totalTimeoutMs to cap an entire blocking workflow, idleTimeoutMs for stalled work, and maxTurns or maxToolCalls to stop repeated work without progress. Every budget stop preserves a bounded checkpoint and may make one separately bounded summary attempt.",
|
|
236
|
+
];
|
|
237
|
+
const definition: ToolDefinition<typeof SubagentParams, SubagentDetails> = {
|
|
238
|
+
name: "subagent",
|
|
239
|
+
label: "Blocking Subagent",
|
|
240
|
+
description: appendAgentCatalog(baseDescription(), catalog),
|
|
241
|
+
promptSnippet:
|
|
242
|
+
"Run blocking isolated subagents only when their outputs are required before the main agent can continue.",
|
|
243
|
+
promptGuidelines: promptGuidelines(),
|
|
244
|
+
parameters: SubagentParams,
|
|
245
|
+
|
|
246
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
247
|
+
const lifecycleController = new AbortController();
|
|
248
|
+
activeControllers.add(lifecycleController);
|
|
249
|
+
const effectiveSignal = signal
|
|
250
|
+
? AbortSignal.any([signal, lifecycleController.signal])
|
|
251
|
+
: lifecycleController.signal;
|
|
252
|
+
const work = (async () => {
|
|
253
|
+
throwIfAborted(effectiveSignal, "Blocking subagent execution was cancelled");
|
|
254
|
+
let executionModule: BlockingExecutionModule;
|
|
255
|
+
try {
|
|
256
|
+
executionModule = await loadExecution();
|
|
257
|
+
} catch (error) {
|
|
258
|
+
throwIfAborted(
|
|
259
|
+
effectiveSignal,
|
|
260
|
+
"Blocking subagent execution was cancelled while loading",
|
|
261
|
+
);
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
throwIfAborted(effectiveSignal, "Blocking subagent execution was cancelled while loading");
|
|
265
|
+
return executionModule.executeSubagent(
|
|
266
|
+
toolCallId,
|
|
267
|
+
params,
|
|
268
|
+
effectiveSignal,
|
|
269
|
+
onUpdate,
|
|
270
|
+
ctx,
|
|
271
|
+
getSettings(),
|
|
272
|
+
);
|
|
273
|
+
})();
|
|
274
|
+
activeWork.add(work);
|
|
275
|
+
try {
|
|
276
|
+
return await work;
|
|
277
|
+
} finally {
|
|
278
|
+
activeControllers.delete(lifecycleController);
|
|
279
|
+
activeWork.delete(work);
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
renderCall(args, theme) {
|
|
284
|
+
return renderSubagentCall(args, theme);
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
renderResult(result, options, theme) {
|
|
288
|
+
return renderSubagentResult(result, options, theme);
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
pi.registerTool<typeof SubagentParams, SubagentDetails>(definition);
|
|
292
|
+
pi.on("tool_result", (event) => {
|
|
293
|
+
if (event.toolName !== "subagent") return;
|
|
294
|
+
if ((event.details as (SubagentDetails & { isError?: boolean }) | undefined)?.isError)
|
|
295
|
+
return { isError: true };
|
|
296
|
+
});
|
|
297
|
+
return (nextCatalog: string) => {
|
|
298
|
+
catalog = nextCatalog;
|
|
299
|
+
definition.description = appendAgentCatalog(baseDescription(), catalog);
|
|
300
|
+
definition.promptGuidelines = promptGuidelines();
|
|
301
|
+
pi.registerTool<typeof SubagentParams, SubagentDetails>(definition);
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function appendAgentCatalog(baseDescription: string, catalog: string): string {
|
|
306
|
+
return catalog ? `${baseDescription}\n\n${catalog}` : baseDescription;
|
|
307
|
+
}
|
package/src/subagents.ts
CHANGED
|
@@ -1,311 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Subagent Tool - Delegate tasks to specialized agents
|
|
3
|
-
*
|
|
4
|
-
* Spawns a separate `pi` process for each subagent invocation,
|
|
5
|
-
* giving it an isolated context window.
|
|
6
|
-
*
|
|
7
|
-
* Supports blocking single, parallel, chain, workflow, and panel modes.
|
|
8
|
-
*
|
|
9
|
-
* Uses JSON mode to capture structured output from subagents.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
CONFIG_DIR_NAME,
|
|
14
|
-
type ExtensionAPI,
|
|
15
|
-
type ToolDefinition,
|
|
16
|
-
} from "@earendil-works/pi-coding-agent";
|
|
17
|
-
import { discoverAgentCatalog, formatAgentCatalog } from "./agents/catalog.js";
|
|
18
|
-
import type {
|
|
19
|
-
ConsultationCwdPolicy,
|
|
20
|
-
ConsultResourcePolicy,
|
|
21
|
-
DelegationCwdPolicy,
|
|
22
|
-
SubagentSettings,
|
|
23
|
-
} from "./agents/types.js";
|
|
24
|
-
import { cachedModuleLoader, throwIfAborted } from "./cached-module-loader.js";
|
|
25
|
-
import { renderCompletionMessage, SUBAGENT_COMPLETION_MESSAGE_TYPE } from "./completion-render.js";
|
|
26
|
-
import {
|
|
27
|
-
type ConfigRegistrationDependencies,
|
|
28
|
-
registerSubagentConfigCommand,
|
|
29
|
-
registerSubagentConfigLifecycle,
|
|
30
|
-
} from "./config-registration.js";
|
|
31
|
-
import {
|
|
32
|
-
type ConsultRegistrationDependencies,
|
|
33
|
-
registerSubagentConsult,
|
|
34
|
-
} from "./consult-registration.js";
|
|
35
|
-
import {
|
|
36
|
-
type InspectRegistrationDependencies,
|
|
37
|
-
registerSubagentInspect,
|
|
38
|
-
} from "./inspect-registration.js";
|
|
39
|
-
import { MAX_BLOCKING_PARALLEL_CONCURRENCY } from "./limits.js";
|
|
40
|
-
import { SubagentParams } from "./params.js";
|
|
41
|
-
import { renderSubagentCall, renderSubagentResult } from "./render.js";
|
|
42
|
-
import type { SubagentDetails } from "./runner.js";
|
|
43
|
-
import {
|
|
44
|
-
consumeSubagentSettingsNotice,
|
|
45
|
-
DEFAULT_CONSULT_RESOURCE_POLICY,
|
|
46
|
-
DEFAULT_CONSULTATION_CWD_POLICY,
|
|
47
|
-
DEFAULT_DELEGATION_CWD_POLICY,
|
|
48
|
-
inspectSubagentSettings,
|
|
49
|
-
readSubagentSettings,
|
|
50
|
-
resolveBlockingMaxParallelTasks,
|
|
51
|
-
} from "./settings.js";
|
|
52
|
-
import { registerStatefulSubagents } from "./stateful.js";
|
|
53
|
-
import type { SubagentTransport } from "./transport.js";
|
|
54
|
-
|
|
55
|
-
type BlockingExecutionModule = Pick<typeof import("./execution.js"), "executeSubagent">;
|
|
56
|
-
|
|
57
|
-
export interface SubagentsDependencies {
|
|
58
|
-
loadBlockingExecution?: () => Promise<BlockingExecutionModule>;
|
|
59
|
-
loadStatefulTransport?: () => Promise<SubagentTransport>;
|
|
60
|
-
config?: ConfigRegistrationDependencies;
|
|
61
|
-
consult?: ConsultRegistrationDependencies;
|
|
62
|
-
inspect?: InspectRegistrationDependencies;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export default function (pi: ExtensionAPI, dependencies: SubagentsDependencies = {}) {
|
|
66
|
-
pi.registerMessageRenderer(SUBAGENT_COMPLETION_MESSAGE_TYPE, renderCompletionMessage);
|
|
67
|
-
const loadBlockingExecution = cachedModuleLoader(
|
|
68
|
-
dependencies.loadBlockingExecution ?? (() => import("./execution.js")),
|
|
69
|
-
);
|
|
70
|
-
const configOwner = registerSubagentConfigLifecycle(pi);
|
|
71
|
-
const settings = readSubagentSettings();
|
|
72
|
-
let currentSettings: SubagentSettings | undefined = settings;
|
|
73
|
-
let currentCatalog = "";
|
|
74
|
-
const blockingEnabled = settings?.blocking?.enabled !== false;
|
|
75
|
-
const refreshBlockingCatalog = blockingEnabled
|
|
76
|
-
? registerBlockingSubagent(pi, () => currentSettings, loadBlockingExecution)
|
|
77
|
-
: () => undefined;
|
|
78
|
-
let refreshStatefulCatalog: (catalog: string) => void = () => undefined;
|
|
79
|
-
let refreshConsultCatalog: (catalog: string) => void = () => undefined;
|
|
80
|
-
|
|
81
|
-
pi.on("session_start", (_event, ctx) => {
|
|
82
|
-
// Preserve a one-shot migration notice from extension load while refreshing
|
|
83
|
-
// validation against settings that may have changed before this session.
|
|
84
|
-
const loadNotice = consumeSubagentSettingsNotice();
|
|
85
|
-
const refreshedSettings = readSubagentSettings();
|
|
86
|
-
const refreshedNotice = consumeSubagentSettingsNotice();
|
|
87
|
-
if (!inspectSubagentSettings().error) currentSettings = refreshedSettings;
|
|
88
|
-
const notice = [
|
|
89
|
-
...new Set([loadNotice, refreshedNotice].filter((value) => value !== undefined)),
|
|
90
|
-
].join("\n");
|
|
91
|
-
if (notice) ctx.ui.notify(notice, "warning");
|
|
92
|
-
|
|
93
|
-
currentCatalog = formatAgentCatalog(
|
|
94
|
-
discoverAgentCatalog(ctx.cwd, ctx.isProjectTrusted(), refreshedSettings),
|
|
95
|
-
).text;
|
|
96
|
-
refreshBlockingCatalog(currentCatalog);
|
|
97
|
-
refreshStatefulCatalog(currentCatalog);
|
|
98
|
-
refreshConsultCatalog(currentCatalog);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const statefulRuntime = registerStatefulSubagents(pi, {
|
|
102
|
-
blockingEnabled,
|
|
103
|
-
settings: settings?.stateful,
|
|
104
|
-
getSettings: () => currentSettings,
|
|
105
|
-
loadTransport: dependencies.loadStatefulTransport,
|
|
106
|
-
});
|
|
107
|
-
refreshStatefulCatalog = statefulRuntime.setAgentCatalog;
|
|
108
|
-
const getBlockingEnabled = () => blockingEnabled;
|
|
109
|
-
const getMaxParallelTasks = () => resolveBlockingMaxParallelTasks(currentSettings);
|
|
110
|
-
const getConsultResourcePolicy = () =>
|
|
111
|
-
currentSettings?.consult?.resources ?? DEFAULT_CONSULT_RESOURCE_POLICY;
|
|
112
|
-
const getConsultationCwdPolicy = () =>
|
|
113
|
-
currentSettings?.cwdPolicy?.consultation ?? DEFAULT_CONSULTATION_CWD_POLICY;
|
|
114
|
-
const getDelegationCwdPolicy = () =>
|
|
115
|
-
currentSettings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY;
|
|
116
|
-
registerSubagentInspect(
|
|
117
|
-
pi,
|
|
118
|
-
{
|
|
119
|
-
...statefulRuntime,
|
|
120
|
-
getBlockingEnabled,
|
|
121
|
-
getMaxParallelTasks,
|
|
122
|
-
getConsultResourcePolicy,
|
|
123
|
-
getConsultationCwdPolicy,
|
|
124
|
-
getDelegationCwdPolicy,
|
|
125
|
-
},
|
|
126
|
-
dependencies.inspect,
|
|
127
|
-
);
|
|
128
|
-
if (blockingEnabled) {
|
|
129
|
-
refreshConsultCatalog = registerSubagentConsult(
|
|
130
|
-
pi,
|
|
131
|
-
{ getSettings: () => currentSettings },
|
|
132
|
-
dependencies.consult,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
registerSubagentConfigCommand(
|
|
136
|
-
pi,
|
|
137
|
-
{
|
|
138
|
-
...statefulRuntime,
|
|
139
|
-
getBlockingEnabled,
|
|
140
|
-
getMaxParallelTasks,
|
|
141
|
-
getConsultResourcePolicy,
|
|
142
|
-
getConsultationCwdPolicy,
|
|
143
|
-
getDelegationCwdPolicy,
|
|
144
|
-
setMaxParallelTasks(value: number) {
|
|
145
|
-
const previousSettings = currentSettings;
|
|
146
|
-
currentSettings = {
|
|
147
|
-
...(currentSettings ?? {}),
|
|
148
|
-
blocking: { ...(currentSettings?.blocking ?? {}), maxParallelTasks: value },
|
|
149
|
-
};
|
|
150
|
-
try {
|
|
151
|
-
refreshBlockingCatalog(currentCatalog);
|
|
152
|
-
} catch (applyError) {
|
|
153
|
-
currentSettings = previousSettings;
|
|
154
|
-
try {
|
|
155
|
-
refreshBlockingCatalog(currentCatalog);
|
|
156
|
-
} catch (rollbackError) {
|
|
157
|
-
throw new AggregateError(
|
|
158
|
-
[applyError, rollbackError],
|
|
159
|
-
"Failed to apply and roll back the parallel-worker limit",
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
throw applyError;
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
setConsultResourcePolicy(value: ConsultResourcePolicy) {
|
|
166
|
-
currentSettings = {
|
|
167
|
-
...(currentSettings ?? {}),
|
|
168
|
-
consult: { ...(currentSettings?.consult ?? {}), resources: value },
|
|
169
|
-
};
|
|
170
|
-
refreshConsultCatalog(currentCatalog);
|
|
171
|
-
},
|
|
172
|
-
setConsultationCwdPolicy(value: ConsultationCwdPolicy) {
|
|
173
|
-
currentSettings = {
|
|
174
|
-
...(currentSettings ?? {}),
|
|
175
|
-
cwdPolicy: { ...(currentSettings?.cwdPolicy ?? {}), consultation: value },
|
|
176
|
-
};
|
|
177
|
-
refreshConsultCatalog(currentCatalog);
|
|
178
|
-
},
|
|
179
|
-
setDelegationCwdPolicy(value: DelegationCwdPolicy) {
|
|
180
|
-
currentSettings = {
|
|
181
|
-
...(currentSettings ?? {}),
|
|
182
|
-
cwdPolicy: { ...(currentSettings?.cwdPolicy ?? {}), delegation: value },
|
|
183
|
-
};
|
|
184
|
-
refreshBlockingCatalog(currentCatalog);
|
|
185
|
-
statefulRuntime.refreshSettingsGuidance();
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
configOwner,
|
|
189
|
-
dependencies.config,
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function registerBlockingSubagent(
|
|
194
|
-
pi: ExtensionAPI,
|
|
195
|
-
getSettings: () => SubagentSettings | undefined,
|
|
196
|
-
loadExecution: () => Promise<BlockingExecutionModule>,
|
|
197
|
-
): (catalog: string) => void {
|
|
198
|
-
let catalog = "";
|
|
199
|
-
const activeControllers = new Set<AbortController>();
|
|
200
|
-
const activeWork = new Set<Promise<unknown>>();
|
|
201
|
-
const cancelAndWaitForWork = async (reason: string) => {
|
|
202
|
-
for (const controller of activeControllers) {
|
|
203
|
-
controller.abort(new DOMException(reason, "AbortError"));
|
|
204
|
-
}
|
|
205
|
-
await Promise.allSettled([...activeWork]);
|
|
206
|
-
};
|
|
207
|
-
pi.on("session_start", () => cancelAndWaitForWork("Blocking subagent session replaced"));
|
|
208
|
-
pi.on("session_shutdown", () => cancelAndWaitForWork("Blocking subagent session shut down"));
|
|
209
|
-
const baseDescription = () =>
|
|
210
|
-
[
|
|
211
|
-
"Run specialized subagents as a blocking operation with isolated contexts.",
|
|
212
|
-
"The call blocks the main agent until every worker and optional aggregator finishes, so queued steering waits.",
|
|
213
|
-
"Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder), workflow (named dependency tasks with optional capability routing), or panel (independent reviewers plus evidence-preserving synthesis).",
|
|
214
|
-
"Parallel mode may include an aggregator fan-in step; workflow mode validates dependencies, authority, artifacts, scope conflicts, retries, and hedging before scheduling. Use subagent_consult instead for one synchronous child that must be executor-constrained to read-only tools.",
|
|
215
|
-
'Default agent scope is "user" (from ~/.pi/agent/agents).',
|
|
216
|
-
`To enable project-local agents in ${CONFIG_DIR_NAME}/agents, pass agentScope: "both" (or "project") as a top-level argument for that call.`,
|
|
217
|
-
`Maximum parallel worker tasks per call: ${resolveBlockingMaxParallelTasks(getSettings())}. Parallel execution starts at most ${MAX_BLOCKING_PARALLEL_CONCURRENCY} workers at once.`,
|
|
218
|
-
`Working-directory target policy: ${getSettings()?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY}. This controls launch targets and protected project resources, not filesystem access or sandboxing.`,
|
|
219
|
-
].join(" ");
|
|
220
|
-
const promptGuidelines = () => [
|
|
221
|
-
"Use subagent only when delegation fits; the main agent should decide how many subagents to spawn from task shape instead of waiting for the user to specify a count.",
|
|
222
|
-
"The main agent retains overall planning, immediate critical-path work, integration, final verification, and the final answer.",
|
|
223
|
-
"Use no subagent for simple answers, quick targeted edits, latency-sensitive one-step work, tasks requiring frequent user back-and-forth, or critical-path work the main agent can perform directly.",
|
|
224
|
-
"One ordinary implementation worker should not replace work the main agent can perform directly; use a blocking single only when intentional synchronous isolation or a user-requested specialist justifies waiting.",
|
|
225
|
-
"Keep ordinary planning in the main agent, or use explicit workflow mode when a genuine dependency graph requires caller-authored orchestration.",
|
|
226
|
-
"Keep ordinary review in the main agent with a review skill and deterministic checks; reserve panel mode or custom verifier agents for consequential independent verification.",
|
|
227
|
-
"Use the blocking subagent tool only when delegated outputs are required before the main agent's next action and waiting is intentional; the main agent cannot process queued steering until the call returns.",
|
|
228
|
-
"Use a blocking subagent single, parallel, chain, workflow, panel, or fan-in call only when synchronous context or output isolation is worth making the main agent unavailable while it runs.",
|
|
229
|
-
`If a blocking parallel subagent call is genuinely required, keep tasks independent, stay within the configured max ${resolveBlockingMaxParallelTasks(getSettings())}, and avoid write-heavy implementation touching the same files or shared state.`,
|
|
230
|
-
"For parallel subagent calls, omit the aggregator key entirely unless a fan-in step is required; do not send null, empty strings, or an empty object for unused optional fields.",
|
|
231
|
-
"Use workflow mode for explicit dependencies or capability routing; declare read/write or ownership scopes, require structured-v2 artifacts when downstream tasks consume them, and use retry or hedging only with the required side-effect contract.",
|
|
232
|
-
"Use panel mode only for consequential review or research that benefits from at least two independent reviewers and one bounded synthesis; agreement is not proof, dissent and blocking objections remain visible, and simple or latency-sensitive work should not use a panel.",
|
|
233
|
-
'Do not use subagent with project-local agents unless the user explicitly wants project agents or sets agentScope to "project" or "both"; keep confirmation enabled for untrusted repositories.',
|
|
234
|
-
"When using subagent, write self-contained tasks with file paths, context, expected output, and whether the subagent may edit files.",
|
|
235
|
-
"Set subagent timeoutMs to the shortest realistic work deadline for the task difficulty, just as thinkingLevel should match reasoning difficulty; split oversized tasks instead of extending the deadline merely to compensate for broad scope. Use totalTimeoutMs to cap an entire blocking workflow, idleTimeoutMs for stalled work, and maxTurns or maxToolCalls to stop repeated work without progress. Every budget stop preserves a bounded checkpoint and may make one separately bounded summary attempt.",
|
|
236
|
-
];
|
|
237
|
-
const definition: ToolDefinition<typeof SubagentParams, SubagentDetails> = {
|
|
238
|
-
name: "subagent",
|
|
239
|
-
label: "Blocking Subagent",
|
|
240
|
-
description: appendAgentCatalog(baseDescription(), catalog),
|
|
241
|
-
promptSnippet:
|
|
242
|
-
"Run blocking isolated subagents only when their outputs are required before the main agent can continue.",
|
|
243
|
-
promptGuidelines: promptGuidelines(),
|
|
244
|
-
parameters: SubagentParams,
|
|
245
|
-
|
|
246
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
247
|
-
const lifecycleController = new AbortController();
|
|
248
|
-
activeControllers.add(lifecycleController);
|
|
249
|
-
const effectiveSignal = signal
|
|
250
|
-
? AbortSignal.any([signal, lifecycleController.signal])
|
|
251
|
-
: lifecycleController.signal;
|
|
252
|
-
const work = (async () => {
|
|
253
|
-
throwIfAborted(effectiveSignal, "Blocking subagent execution was cancelled");
|
|
254
|
-
let executionModule: BlockingExecutionModule;
|
|
255
|
-
try {
|
|
256
|
-
executionModule = await loadExecution();
|
|
257
|
-
} catch (error) {
|
|
258
|
-
throwIfAborted(
|
|
259
|
-
effectiveSignal,
|
|
260
|
-
"Blocking subagent execution was cancelled while loading",
|
|
261
|
-
);
|
|
262
|
-
throw error;
|
|
263
|
-
}
|
|
264
|
-
throwIfAborted(effectiveSignal, "Blocking subagent execution was cancelled while loading");
|
|
265
|
-
return executionModule.executeSubagent(
|
|
266
|
-
toolCallId,
|
|
267
|
-
params,
|
|
268
|
-
effectiveSignal,
|
|
269
|
-
onUpdate,
|
|
270
|
-
ctx,
|
|
271
|
-
getSettings(),
|
|
272
|
-
);
|
|
273
|
-
})();
|
|
274
|
-
activeWork.add(work);
|
|
275
|
-
try {
|
|
276
|
-
return await work;
|
|
277
|
-
} finally {
|
|
278
|
-
activeControllers.delete(lifecycleController);
|
|
279
|
-
activeWork.delete(work);
|
|
280
|
-
}
|
|
281
|
-
},
|
|
282
|
-
|
|
283
|
-
renderCall(args, theme) {
|
|
284
|
-
return renderSubagentCall(args, theme);
|
|
285
|
-
},
|
|
286
|
-
|
|
287
|
-
renderResult(result, options, theme) {
|
|
288
|
-
return renderSubagentResult(result, options, theme);
|
|
289
|
-
},
|
|
290
|
-
};
|
|
291
|
-
pi.registerTool<typeof SubagentParams, SubagentDetails>(definition);
|
|
292
|
-
pi.on("tool_result", (event) => {
|
|
293
|
-
if (event.toolName !== "subagent") return;
|
|
294
|
-
if ((event.details as (SubagentDetails & { isError?: boolean }) | undefined)?.isError)
|
|
295
|
-
return { isError: true };
|
|
296
|
-
});
|
|
297
|
-
return (nextCatalog: string) => {
|
|
298
|
-
catalog = nextCatalog;
|
|
299
|
-
definition.description = appendAgentCatalog(baseDescription(), catalog);
|
|
300
|
-
definition.promptGuidelines = promptGuidelines();
|
|
301
|
-
pi.registerTool<typeof SubagentParams, SubagentDetails>(definition);
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function appendAgentCatalog(baseDescription: string, catalog: string): string {
|
|
306
|
-
return catalog ? `${baseDescription}\n\n${catalog}` : baseDescription;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
1
|
export { parsePositiveInteger } from "./execution/runtime-policy.js";
|
|
310
2
|
export { buildPiArgs } from "./pi-args.js";
|
|
311
3
|
export {
|
|
@@ -336,4 +28,5 @@ export {
|
|
|
336
28
|
updateDelegationWorkflowSetting,
|
|
337
29
|
updateStatefulLimitSetting,
|
|
338
30
|
} from "./settings.js";
|
|
31
|
+
export { default, type SubagentsDependencies } from "./subagents-extension.js";
|
|
339
32
|
export { formatTokens, formatUsageStats } from "./usage-format.js";
|