@oai404iao/pi-subagent 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +294 -89
- package/agents/worker.md +1 -1
- package/config.example.json +3 -4
- package/config.schema.json +26 -20
- package/index.ts +2 -0
- package/package.json +13 -12
- package/src/agent-state.ts +125 -0
- package/src/agent-sync.ts +171 -88
- package/src/agents.ts +3 -22
- package/src/catalog.ts +47 -0
- package/src/completion-mailbox.ts +656 -0
- package/src/config.ts +43 -35
- package/src/coordinator.ts +2172 -326
- package/src/descriptor.ts +96 -33
- package/src/index.ts +176 -58
- package/src/mailbox.ts +451 -0
- package/src/providers.ts +221 -28
- package/src/render.ts +23 -16
- package/src/scheduler.ts +173 -0
- package/src/schemas.ts +76 -38
- package/src/task-path.ts +146 -0
- package/src/types.ts +53 -15
package/src/descriptor.ts
CHANGED
|
@@ -4,24 +4,33 @@ import type {
|
|
|
4
4
|
AgentScope,
|
|
5
5
|
AgentSnapshot,
|
|
6
6
|
AgentSource,
|
|
7
|
-
|
|
7
|
+
ContextInheritance,
|
|
8
|
+
RuntimeMode,
|
|
8
9
|
SubagentDescriptor,
|
|
9
10
|
SubagentMode,
|
|
10
11
|
SubagentProviderName,
|
|
12
|
+
SubagentRuntimeSnapshot,
|
|
11
13
|
} from "./types.ts";
|
|
14
|
+
import { DESCRIPTOR_VERSION } from "./types.ts";
|
|
15
|
+
import { validateDescriptorTask } from "./task-path.ts";
|
|
12
16
|
|
|
13
17
|
export const DESCRIPTOR_CUSTOM_TYPE = "pi-subagent/descriptor";
|
|
14
|
-
export
|
|
18
|
+
export { DESCRIPTOR_VERSION };
|
|
15
19
|
|
|
16
20
|
const THINKING_LEVELS = new Set<ThinkingLevel>(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
17
|
-
const AGENT_SOURCES = new Set<AgentSource>(["
|
|
21
|
+
const AGENT_SOURCES = new Set<AgentSource>(["user", "project"]);
|
|
18
22
|
const MODES = new Set<SubagentMode>(["one-shot", "continuable"]);
|
|
19
23
|
const PROVIDERS = new Set<SubagentProviderName>(["spawn", "fork"]);
|
|
20
|
-
const
|
|
24
|
+
const RUNTIME_MODES = new Set<RuntimeMode>(["foreground", "background"]);
|
|
21
25
|
const AGENT_SCOPES = new Set<AgentScope>(["user", "project", "both"]);
|
|
22
26
|
const AGENT_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
23
27
|
/** UUIDv7 with the standard RFC 9562 variant bits (version 7, variant 10xx). */
|
|
24
28
|
const AGENT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
29
|
+
const CONTEXT_MODES = new Set<ContextInheritance["mode"]>([
|
|
30
|
+
"fresh",
|
|
31
|
+
"all_completed",
|
|
32
|
+
"last_n_completed",
|
|
33
|
+
]);
|
|
25
34
|
|
|
26
35
|
type UnknownRecord = Record<string, unknown>;
|
|
27
36
|
|
|
@@ -116,10 +125,49 @@ function parseAgent(value: unknown): AgentSnapshot {
|
|
|
116
125
|
};
|
|
117
126
|
}
|
|
118
127
|
|
|
128
|
+
function parseContext(value: unknown): ContextInheritance {
|
|
129
|
+
const input = record(value, "context");
|
|
130
|
+
const mode = string(input.mode, "context.mode") as ContextInheritance["mode"];
|
|
131
|
+
if (!CONTEXT_MODES.has(mode)) {
|
|
132
|
+
throw new Error(`unsupported context.mode: ${mode}`);
|
|
133
|
+
}
|
|
134
|
+
if (mode === "last_n_completed") {
|
|
135
|
+
return {
|
|
136
|
+
mode,
|
|
137
|
+
completedTurns: boundedInteger(
|
|
138
|
+
input.completedTurns,
|
|
139
|
+
"context.completedTurns",
|
|
140
|
+
1,
|
|
141
|
+
100,
|
|
142
|
+
),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (input.completedTurns !== undefined) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
"context.completedTurns is available only for last_n_completed",
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return { mode };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseRuntimeMode(value: unknown): RuntimeMode {
|
|
154
|
+
if (
|
|
155
|
+
typeof value !== "string"
|
|
156
|
+
|| !RUNTIME_MODES.has(value as RuntimeMode)
|
|
157
|
+
) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
'runtime.runtimeMode must be "foreground" or "background"',
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return value as RuntimeMode;
|
|
163
|
+
}
|
|
164
|
+
|
|
119
165
|
export function parseDescriptor(value: unknown): SubagentDescriptor {
|
|
120
166
|
const input = record(value, "descriptor");
|
|
121
167
|
if (input.version !== DESCRIPTOR_VERSION) {
|
|
122
|
-
throw new Error(
|
|
168
|
+
throw new Error(
|
|
169
|
+
`unsupported descriptor version: ${String(input.version)}; this release persists version ${DESCRIPTOR_VERSION}`,
|
|
170
|
+
);
|
|
123
171
|
}
|
|
124
172
|
const mode = string(input.mode, "mode") as SubagentMode;
|
|
125
173
|
if (!MODES.has(mode)) throw new Error(`unsupported descriptor mode: ${mode}`);
|
|
@@ -132,16 +180,36 @@ export function parseDescriptor(value: unknown): SubagentDescriptor {
|
|
|
132
180
|
const runtime = record(input.runtime, "runtime");
|
|
133
181
|
const agentScope = string(runtime.agentScope, "runtime.agentScope") as AgentScope;
|
|
134
182
|
if (!AGENT_SCOPES.has(agentScope)) throw new Error(`unsupported runtime.agentScope: ${agentScope}`);
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
183
|
+
const parsedRuntime: SubagentRuntimeSnapshot = {
|
|
184
|
+
agentScope,
|
|
185
|
+
maxDepth: safeNatural(runtime.maxDepth, "runtime.maxDepth"),
|
|
186
|
+
runtimeMode: parseRuntimeMode(runtime.runtimeMode),
|
|
187
|
+
maxConcurrentBackgroundRuns: boundedInteger(
|
|
188
|
+
runtime.maxConcurrentBackgroundRuns,
|
|
189
|
+
"runtime.maxConcurrentBackgroundRuns",
|
|
190
|
+
1,
|
|
191
|
+
Number.MAX_SAFE_INTEGER,
|
|
192
|
+
),
|
|
193
|
+
maxIdleRuntimes: boundedInteger(
|
|
194
|
+
runtime.maxIdleRuntimes,
|
|
195
|
+
"runtime.maxIdleRuntimes",
|
|
196
|
+
0,
|
|
197
|
+
Number.MAX_SAFE_INTEGER,
|
|
198
|
+
),
|
|
199
|
+
inheritExtensions: boolean(runtime.inheritExtensions, "runtime.inheritExtensions"),
|
|
200
|
+
openAIIdentity: boolean(runtime.openAIIdentity, "runtime.openAIIdentity"),
|
|
201
|
+
maxOutputBytes: boundedInteger(
|
|
202
|
+
runtime.maxOutputBytes,
|
|
203
|
+
"runtime.maxOutputBytes",
|
|
204
|
+
1024,
|
|
205
|
+
1024 * 1024,
|
|
206
|
+
),
|
|
207
|
+
};
|
|
139
208
|
|
|
140
209
|
const createdAt = string(input.createdAt, "createdAt");
|
|
141
210
|
if (Number.isNaN(Date.parse(createdAt))) throw new Error("createdAt must be an ISO date string");
|
|
142
211
|
|
|
143
|
-
|
|
144
|
-
version: DESCRIPTOR_VERSION,
|
|
212
|
+
const base = {
|
|
145
213
|
mode,
|
|
146
214
|
provider,
|
|
147
215
|
label: limitedString(input.label, "label", 200),
|
|
@@ -160,28 +228,17 @@ export function parseDescriptor(value: unknown): SubagentDescriptor {
|
|
|
160
228
|
id: string(model.id, "model.id"),
|
|
161
229
|
},
|
|
162
230
|
thinkingLevel,
|
|
163
|
-
runtime:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
defaultBackground: boolean(runtime.defaultBackground, "runtime.defaultBackground"),
|
|
175
|
-
reportDelivery,
|
|
176
|
-
inheritExtensions: boolean(runtime.inheritExtensions, "runtime.inheritExtensions"),
|
|
177
|
-
openAIIdentity: boolean(runtime.openAIIdentity, "runtime.openAIIdentity"),
|
|
178
|
-
maxOutputBytes: boundedInteger(
|
|
179
|
-
runtime.maxOutputBytes,
|
|
180
|
-
"runtime.maxOutputBytes",
|
|
181
|
-
1024,
|
|
182
|
-
1024 * 1024,
|
|
183
|
-
),
|
|
184
|
-
},
|
|
231
|
+
runtime: parsedRuntime,
|
|
232
|
+
};
|
|
233
|
+
const task = record(input.task, "task");
|
|
234
|
+
return {
|
|
235
|
+
version: DESCRIPTOR_VERSION,
|
|
236
|
+
...base,
|
|
237
|
+
task: validateDescriptorTask({
|
|
238
|
+
name: string(task.name, "task.name"),
|
|
239
|
+
path: string(task.path, "task.path"),
|
|
240
|
+
}),
|
|
241
|
+
context: parseContext(input.context),
|
|
185
242
|
};
|
|
186
243
|
}
|
|
187
244
|
|
|
@@ -200,3 +257,9 @@ export function foldDescriptor(entries: readonly SessionEntry[]): DescriptorFold
|
|
|
200
257
|
return { kind: "corrupt", message: error instanceof Error ? error.message : String(error) };
|
|
201
258
|
}
|
|
202
259
|
}
|
|
260
|
+
|
|
261
|
+
export function descriptorContext(
|
|
262
|
+
descriptor: SubagentDescriptor,
|
|
263
|
+
): ContextInheritance {
|
|
264
|
+
return structuredClone(descriptor.context);
|
|
265
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
4
4
|
import { DEFAULT_SETTINGS, loadSettings } from "./config.ts";
|
|
5
5
|
import {
|
|
6
6
|
REPORT_CUSTOM_TYPE,
|
|
7
|
-
SETTLED_CUSTOM_TYPE,
|
|
8
7
|
SubagentCoordinator,
|
|
9
8
|
type DelegationInput,
|
|
10
9
|
} from "./coordinator.ts";
|
|
@@ -14,9 +13,11 @@ import {
|
|
|
14
13
|
} from "./agents.ts";
|
|
15
14
|
import type { AgentSyncResult } from "./agent-sync.ts";
|
|
16
15
|
import {
|
|
16
|
+
FollowupTaskParameters,
|
|
17
17
|
InterruptParameters,
|
|
18
18
|
ListAgentsParameters,
|
|
19
19
|
SendMessageParameters,
|
|
20
|
+
WaitAgentParameters,
|
|
20
21
|
delegationParameters,
|
|
21
22
|
forkDelegationParameters,
|
|
22
23
|
} from "./schemas.ts";
|
|
@@ -58,56 +59,53 @@ function disableOwnedTools(
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
function assertBackgroundControlsEnabled(settings: SubagentSettings, toolName: string): void {
|
|
61
|
-
if (
|
|
62
|
+
if (settings.runtimeMode === "foreground") {
|
|
62
63
|
throw new Error(
|
|
63
|
-
`tool "${toolName}" is unavailable in foreground-only mode (
|
|
64
|
+
`tool "${toolName}" is unavailable in foreground-only mode (runtimeMode: "foreground")`,
|
|
64
65
|
);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
function modeDescription(settings: SubagentSettings): string {
|
|
70
|
+
return settings.runtimeMode === "foreground"
|
|
71
|
+
? "Delegate a complete task to a named child path with selectable completed-turn context. " +
|
|
72
|
+
"This foreground-only tool waits for the child and returns its final answer. " +
|
|
73
|
+
"Independent sibling calls may still execute in parallel."
|
|
74
|
+
: "Delegate a complete task to a named child path with selectable completed-turn context. " +
|
|
75
|
+
"Background mode is continuable and returns a readable path plus durable id; use send_message to enqueue, followup_task to start, and wait_agent for quiet completions. " +
|
|
76
|
+
"Start independent children together in one assistant message.";
|
|
77
|
+
}
|
|
78
|
+
|
|
68
79
|
function registerDelegationTool(
|
|
69
80
|
pi: ExtensionAPI,
|
|
70
81
|
coordinator: SubagentCoordinator,
|
|
71
82
|
settings: SubagentSettings,
|
|
72
83
|
agentDiscovery?: AgentDiscoveryResult,
|
|
73
84
|
): unknown {
|
|
74
|
-
const
|
|
85
|
+
const foregroundOnly = settings.runtimeMode === "foreground";
|
|
75
86
|
const agentNames = agentDiscovery?.agents.map((agent) => agent.name);
|
|
76
|
-
const
|
|
77
|
-
? "Delegate a complete standalone task to a fresh child with its own Pi session and context. " +
|
|
78
|
-
"This foreground-only tool waits for the child and returns its final answer. " +
|
|
79
|
-
"Independent sibling calls may still execute in parallel."
|
|
80
|
-
: defaultBackground
|
|
81
|
-
? "Delegate a complete standalone task to a fresh child with its own Pi session and context. " +
|
|
82
|
-
"Background mode is continuable and returns a durable agent id; use send_message for later FIFO turns. " +
|
|
83
|
-
"Start independent children together in one assistant message."
|
|
84
|
-
: "Delegate a complete standalone task to a fresh child with its own Pi session and context. " +
|
|
85
|
-
"This tool waits for the result by default; set run_in_background to true to return a durable agent id.";
|
|
86
|
-
const promptGuidelines = !enableRunInBackground
|
|
87
|
+
const promptGuidelines = (foregroundOnly
|
|
87
88
|
? [
|
|
88
89
|
"Use subagent for focused independent work and give it a complete standalone prompt.",
|
|
89
90
|
"This subagent tool is foreground-only: every call waits for and returns the child's final answer.",
|
|
90
91
|
"Independent subagent calls can still be issued together in one assistant message and execute in parallel.",
|
|
91
92
|
]
|
|
92
|
-
:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"Independent subagent calls can still be issued together in one assistant message and execute in parallel.",
|
|
102
|
-
];
|
|
103
|
-
const parameters = delegationParameters(enableRunInBackground, agentNames);
|
|
93
|
+
: [
|
|
94
|
+
"Use subagent for focused independent work and give it a complete standalone prompt.",
|
|
95
|
+
"Call subagent multiple times in one assistant message when delegations are independent.",
|
|
96
|
+
"Every child is durable: continue parent work, then use send_message plus followup_task to give it more work and wait_agent for its completion updates.",
|
|
97
|
+
]).concat([
|
|
98
|
+
"Set task_name when a stable readable path will help later control calls; otherwise a name is generated from description.",
|
|
99
|
+
"Use fresh context by default and request all_completed or last_n_completed only when parent history materially helps.",
|
|
100
|
+
]);
|
|
101
|
+
const parameters = delegationParameters(agentNames);
|
|
104
102
|
pi.registerTool({
|
|
105
103
|
name: "subagent",
|
|
106
104
|
label: "Subagent",
|
|
107
|
-
description,
|
|
108
|
-
promptSnippet:
|
|
109
|
-
? "
|
|
110
|
-
: "
|
|
105
|
+
description: modeDescription(settings),
|
|
106
|
+
promptSnippet: foregroundOnly
|
|
107
|
+
? "Run focused independent work in foreground child agents"
|
|
108
|
+
: "Delegate focused work to named child agents",
|
|
111
109
|
promptGuidelines,
|
|
112
110
|
executionMode: "parallel",
|
|
113
111
|
parameters,
|
|
@@ -129,7 +127,7 @@ function registerDelegationTool(
|
|
|
129
127
|
return coordinator.outcomeToolResult(outcome);
|
|
130
128
|
},
|
|
131
129
|
renderCall(args, theme) {
|
|
132
|
-
return renderDelegationCall(args, theme, "spawn");
|
|
130
|
+
return renderDelegationCall(args, theme, "spawn", settings.runtimeMode);
|
|
133
131
|
},
|
|
134
132
|
renderResult(result, options, theme) {
|
|
135
133
|
return renderDelegationResult(
|
|
@@ -155,8 +153,11 @@ function registerForkDelegationTool(
|
|
|
155
153
|
name: "subagent_fork",
|
|
156
154
|
label: "Subagent Fork",
|
|
157
155
|
description:
|
|
158
|
-
"Delegate a
|
|
159
|
-
"The current in-flight tool-calling turn is excluded.
|
|
156
|
+
"Delegate a task to a child seeded with all completed turns in this conversation. " +
|
|
157
|
+
"The current in-flight tool-calling turn is excluded. " +
|
|
158
|
+
(settings.runtimeMode === "foreground"
|
|
159
|
+
? "This foreground-only tool waits for the child's final answer."
|
|
160
|
+
: "The fork is continuable and returns a readable path plus durable id."),
|
|
160
161
|
promptSnippet: "Delegate context-dependent work to a child seeded with completed turns",
|
|
161
162
|
promptGuidelines: [
|
|
162
163
|
"Use subagent_fork only when completed conversation history materially helps the delegated task.",
|
|
@@ -181,7 +182,7 @@ function registerForkDelegationTool(
|
|
|
181
182
|
return coordinator.outcomeToolResult(outcome);
|
|
182
183
|
},
|
|
183
184
|
renderCall(args, theme) {
|
|
184
|
-
return renderDelegationCall(args, theme, "fork");
|
|
185
|
+
return renderDelegationCall(args, theme, "fork", settings.runtimeMode);
|
|
185
186
|
},
|
|
186
187
|
renderResult(result, options, theme) {
|
|
187
188
|
return renderDelegationResult(
|
|
@@ -206,6 +207,8 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
206
207
|
registerForkDelegationTool(pi, coordinator, DEFAULT_SETTINGS);
|
|
207
208
|
const backgroundControlParameters = new Map<string, unknown>([
|
|
208
209
|
["send_message", SendMessageParameters],
|
|
210
|
+
["followup_task", FollowupTaskParameters],
|
|
211
|
+
["wait_agent", WaitAgentParameters],
|
|
209
212
|
["interrupt_agent", InterruptParameters],
|
|
210
213
|
["list_agents", ListAgentsParameters],
|
|
211
214
|
]);
|
|
@@ -214,23 +217,107 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
214
217
|
name: "send_message",
|
|
215
218
|
label: "Send Message",
|
|
216
219
|
description:
|
|
217
|
-
"
|
|
220
|
+
"Send a message to a direct continuable child. It is durably appended to the child's FIFO mailbox and requires followup_task to start a turn. " +
|
|
218
221
|
"This call returns acceptance only, never the child's answer.",
|
|
219
|
-
promptSnippet: "Send
|
|
222
|
+
promptSnippet: "Send or enqueue a message for a direct continuable subagent",
|
|
220
223
|
executionMode: "parallel",
|
|
221
224
|
parameters: SendMessageParameters,
|
|
222
225
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
223
226
|
assertBackgroundControlsEnabled(sessionSettings, "send_message");
|
|
224
227
|
const parent = await coordinator.parentFromContext(ctx);
|
|
225
|
-
await coordinator.
|
|
228
|
+
const delivery = await coordinator.sendMessageWithOutcome(
|
|
229
|
+
parent,
|
|
230
|
+
params.subagent_id,
|
|
231
|
+
params.message,
|
|
232
|
+
signal,
|
|
233
|
+
);
|
|
234
|
+
return {
|
|
235
|
+
content: [
|
|
236
|
+
{
|
|
237
|
+
type: "text",
|
|
238
|
+
text: `message ${delivery.messageId} durably enqueued for ${delivery.taskPath}; ${delivery.pendingMessages} pending`,
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
details: {
|
|
242
|
+
kind: "control",
|
|
243
|
+
action: "send",
|
|
244
|
+
agentId: delivery.agentId,
|
|
245
|
+
taskPath: delivery.taskPath,
|
|
246
|
+
messageId: delivery.messageId,
|
|
247
|
+
pendingMessages: delivery.pendingMessages,
|
|
248
|
+
} satisfies ControlDetails,
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
pi.registerTool({
|
|
254
|
+
name: "followup_task",
|
|
255
|
+
label: "Follow-up Task",
|
|
256
|
+
description:
|
|
257
|
+
"For a direct continuable child, atomically claim its current pending FIFO mailbox and start exactly one scheduled turn. " +
|
|
258
|
+
"This call returns turn acceptance, not the child's answer.",
|
|
259
|
+
promptSnippet: "Start one child turn from queued mailbox messages",
|
|
260
|
+
executionMode: "parallel",
|
|
261
|
+
parameters: FollowupTaskParameters,
|
|
262
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
263
|
+
assertBackgroundControlsEnabled(sessionSettings, "followup_task");
|
|
264
|
+
const parent = await coordinator.parentFromContext(ctx);
|
|
265
|
+
const outcome = await coordinator.followupTask(
|
|
266
|
+
parent,
|
|
267
|
+
params.subagent_id,
|
|
268
|
+
signal,
|
|
269
|
+
);
|
|
270
|
+
return {
|
|
271
|
+
content: [
|
|
272
|
+
{
|
|
273
|
+
type: "text",
|
|
274
|
+
text: `started turn ${outcome.turnId} for ${outcome.taskPath}, claiming ${outcome.claimedMessages} mailbox message${outcome.claimedMessages === 1 ? "" : "s"}`,
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
details: {
|
|
278
|
+
kind: "control",
|
|
279
|
+
action: "followup",
|
|
280
|
+
agentId: outcome.agentId,
|
|
281
|
+
taskPath: outcome.taskPath,
|
|
282
|
+
turnId: outcome.turnId,
|
|
283
|
+
claimedMessages: outcome.claimedMessages,
|
|
284
|
+
} satisfies ControlDetails,
|
|
285
|
+
};
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
pi.registerTool({
|
|
290
|
+
name: "wait_agent",
|
|
291
|
+
label: "Wait Agent",
|
|
292
|
+
description:
|
|
293
|
+
"Wait event-driven for unread completion updates from direct children. Existing updates return immediately; timeout does not consume later updates.",
|
|
294
|
+
promptSnippet: "Wait for quiet child completion updates",
|
|
295
|
+
parameters: WaitAgentParameters,
|
|
296
|
+
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
297
|
+
assertBackgroundControlsEnabled(sessionSettings, "wait_agent");
|
|
298
|
+
const parent = await coordinator.parentFromContext(ctx);
|
|
299
|
+
const outcome = await coordinator.waitAgent(
|
|
300
|
+
parent,
|
|
301
|
+
toolCallId,
|
|
302
|
+
params.timeout_ms,
|
|
303
|
+
signal,
|
|
304
|
+
);
|
|
226
305
|
return {
|
|
227
306
|
content: [
|
|
228
307
|
{
|
|
229
308
|
type: "text",
|
|
230
|
-
text:
|
|
309
|
+
text: coordinator.formatWaitAgentOutcome(outcome),
|
|
231
310
|
},
|
|
232
311
|
],
|
|
233
|
-
details: {
|
|
312
|
+
details: {
|
|
313
|
+
kind: "control",
|
|
314
|
+
action: "wait",
|
|
315
|
+
timedOut: outcome.timedOut,
|
|
316
|
+
completionIds: outcome.updates.map(
|
|
317
|
+
(update) => update.completionId,
|
|
318
|
+
),
|
|
319
|
+
unreadUpdates: outcome.unreadUpdates,
|
|
320
|
+
} satisfies ControlDetails,
|
|
234
321
|
};
|
|
235
322
|
},
|
|
236
323
|
});
|
|
@@ -247,10 +334,18 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
247
334
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
248
335
|
assertBackgroundControlsEnabled(sessionSettings, "interrupt_agent");
|
|
249
336
|
const parent = await coordinator.parentFromContext(ctx);
|
|
250
|
-
await coordinator.
|
|
337
|
+
const outcome = await coordinator.interruptWithOutcome(
|
|
338
|
+
parent,
|
|
339
|
+
params.agent_id,
|
|
340
|
+
);
|
|
251
341
|
return {
|
|
252
|
-
content: [{ type: "text", text: `interrupt requested for
|
|
253
|
-
details: {
|
|
342
|
+
content: [{ type: "text", text: `interrupt requested for ${outcome.taskPath}` }],
|
|
343
|
+
details: {
|
|
344
|
+
kind: "control",
|
|
345
|
+
action: "interrupt",
|
|
346
|
+
agentId: outcome.agentId,
|
|
347
|
+
taskPath: outcome.taskPath,
|
|
348
|
+
} satisfies ControlDetails,
|
|
254
349
|
};
|
|
255
350
|
},
|
|
256
351
|
});
|
|
@@ -260,7 +355,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
260
355
|
label: "List Agents",
|
|
261
356
|
description:
|
|
262
357
|
"List direct continuable children or all descendants. running means an active turn, idle means resident between turns, " +
|
|
263
|
-
"
|
|
358
|
+
"ready means persisted and cold-resumable, and children show pending task messages separately from unread completion updates.",
|
|
264
359
|
promptSnippet: "List continuable child agents and their lifecycle status",
|
|
265
360
|
parameters: ListAgentsParameters,
|
|
266
361
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -275,7 +370,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
275
370
|
},
|
|
276
371
|
});
|
|
277
372
|
|
|
278
|
-
for (const customType of [REPORT_CUSTOM_TYPE
|
|
373
|
+
for (const customType of [REPORT_CUSTOM_TYPE]) {
|
|
279
374
|
pi.registerMessageRenderer<ParentMessageDetails>(customType, (message, options, theme) => {
|
|
280
375
|
const content =
|
|
281
376
|
typeof message.content === "string"
|
|
@@ -300,18 +395,19 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
300
395
|
);
|
|
301
396
|
const parent = await coordinator.parentFromContext(ctx);
|
|
302
397
|
const entries = await coordinator.list(parent, "descendants");
|
|
303
|
-
const schedulingMode = !sessionSettings.enableRunInBackground
|
|
304
|
-
? "foreground-only"
|
|
305
|
-
: sessionSettings.defaultBackground
|
|
306
|
-
? "background-first"
|
|
307
|
-
: "foreground-first";
|
|
308
398
|
const sections = [
|
|
309
|
-
`Mode: ${
|
|
399
|
+
`Mode: ${sessionSettings.runtimeMode}`,
|
|
400
|
+
`Background concurrency: ${sessionSettings.maxConcurrentBackgroundRuns}`,
|
|
401
|
+
`Idle runtime LRU: ${sessionSettings.maxIdleRuntimes}`,
|
|
402
|
+
"Background protocol: durable mailbox",
|
|
310
403
|
`OpenAI identity inline: ${sessionSettings.openAIIdentity ? "enabled" : "disabled"}`,
|
|
311
|
-
|
|
312
|
-
?
|
|
313
|
-
:
|
|
404
|
+
agentSync?.diagnostics.length
|
|
405
|
+
? "Bundled templates: initialization skipped (see diagnostics)"
|
|
406
|
+
: `Bundled templates: initialization only (${agentSync?.packageVersion ?? "not initialized"})`,
|
|
314
407
|
`User agent dir: ${agentSync?.userAgentsDir ?? coordinator.getUserAgentsDir()}`,
|
|
408
|
+
agentSync && agentSync.retired.length > 0
|
|
409
|
+
? `Deleted presets (never restored): ${agentSync.retired.join(", ")}`
|
|
410
|
+
: null,
|
|
315
411
|
`Agents:\n${formatAgentCatalog(discovery.agents)}`,
|
|
316
412
|
`Children:\n${coordinator.formatCatalog(entries, "descendants")}`,
|
|
317
413
|
];
|
|
@@ -324,10 +420,14 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
324
420
|
|
|
325
421
|
pi.on("session_start", async (_event, ctx) => {
|
|
326
422
|
const loaded = loadSettings({ cwd: ctx.cwd, projectTrusted: ctx.isProjectTrusted() });
|
|
423
|
+
coordinator.configureBackgroundRuns(
|
|
424
|
+
loaded.settings.maxConcurrentBackgroundRuns,
|
|
425
|
+
);
|
|
426
|
+
await coordinator.configureIdleRuntimes(
|
|
427
|
+
loaded.settings.maxIdleRuntimes,
|
|
428
|
+
);
|
|
327
429
|
sessionSettings = loaded.settings;
|
|
328
|
-
agentSync =
|
|
329
|
-
? coordinator.synchronizeBundledAgents()
|
|
330
|
-
: undefined;
|
|
430
|
+
agentSync = coordinator.synchronizeBundledAgents();
|
|
331
431
|
sessionDiscovery = coordinator.discoverAvailableAgents(
|
|
332
432
|
ctx.cwd,
|
|
333
433
|
sessionSettings,
|
|
@@ -346,7 +446,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
346
446
|
if (sessionDiscovery.agents.length === 0) {
|
|
347
447
|
disableOwnedTools(pi, registeredParameters);
|
|
348
448
|
}
|
|
349
|
-
if (
|
|
449
|
+
if (sessionSettings.runtimeMode === "foreground") {
|
|
350
450
|
disableOwnedTools(pi, backgroundControlParameters);
|
|
351
451
|
}
|
|
352
452
|
if (!agentSyncNotified && agentSync) {
|
|
@@ -361,6 +461,14 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
361
461
|
if (agentSync.removed.length > 0) {
|
|
362
462
|
lines.push(`retired: ${agentSync.removed.join(", ")}`);
|
|
363
463
|
}
|
|
464
|
+
if (agentSync.retirementChanged && agentSync.retired.length > 0) {
|
|
465
|
+
lines.push(
|
|
466
|
+
`deleted by you (not restored): ${agentSync.retired.join(", ")}`,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
if (agentSync.restored.length > 0) {
|
|
470
|
+
lines.push(`restored as managed presets: ${agentSync.restored.join(", ")}`);
|
|
471
|
+
}
|
|
364
472
|
if (agentSync.backups.length > 0) {
|
|
365
473
|
lines.push("backups:", ...agentSync.backups.map((backup) => `- ${backup.name}: ${backup.path}`));
|
|
366
474
|
}
|
|
@@ -368,6 +476,8 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
368
476
|
agentSync.installed.length > 0 ||
|
|
369
477
|
agentSync.updated.length > 0 ||
|
|
370
478
|
agentSync.removed.length > 0 ||
|
|
479
|
+
(agentSync.retirementChanged && agentSync.retired.length > 0) ||
|
|
480
|
+
agentSync.restored.length > 0 ||
|
|
371
481
|
agentSync.backups.length > 0
|
|
372
482
|
) {
|
|
373
483
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
@@ -378,6 +488,14 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
378
488
|
}
|
|
379
489
|
});
|
|
380
490
|
|
|
491
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
492
|
+
const parent = await coordinator.parentFromContext(ctx);
|
|
493
|
+
await coordinator.releaseWaitAgentDeliveries(
|
|
494
|
+
parent,
|
|
495
|
+
"parent agent turn ended without a durable wait_agent result",
|
|
496
|
+
);
|
|
497
|
+
});
|
|
498
|
+
|
|
381
499
|
pi.on("session_shutdown", async () => {
|
|
382
500
|
await coordinator.shutdown();
|
|
383
501
|
});
|