@oai404iao/pi-subagent 0.4.0-alpha.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 +102 -72
- package/config.example.json +1 -4
- package/config.schema.json +14 -28
- package/index.ts +2 -0
- package/package.json +9 -8
- package/src/agent-sync.ts +112 -40
- package/src/catalog.ts +14 -16
- package/src/config.ts +15 -52
- package/src/coordinator.ts +114 -422
- package/src/descriptor.ts +51 -81
- package/src/index.ts +57 -88
- package/src/render.ts +5 -15
- package/src/schemas.ts +8 -70
- package/src/task-path.ts +5 -47
- package/src/types.ts +14 -25
package/src/descriptor.ts
CHANGED
|
@@ -3,29 +3,25 @@ import type { SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import type {
|
|
4
4
|
AgentScope,
|
|
5
5
|
AgentSnapshot,
|
|
6
|
-
|
|
7
|
-
BackgroundProtocol,
|
|
6
|
+
AgentSource,
|
|
8
7
|
ContextInheritance,
|
|
9
|
-
|
|
8
|
+
RuntimeMode,
|
|
10
9
|
SubagentDescriptor,
|
|
11
10
|
SubagentMode,
|
|
12
11
|
SubagentProviderName,
|
|
12
|
+
SubagentRuntimeSnapshot,
|
|
13
13
|
} from "./types.ts";
|
|
14
|
+
import { DESCRIPTOR_VERSION } from "./types.ts";
|
|
14
15
|
import { validateDescriptorTask } from "./task-path.ts";
|
|
15
16
|
|
|
16
17
|
export const DESCRIPTOR_CUSTOM_TYPE = "pi-subagent/descriptor";
|
|
17
|
-
export
|
|
18
|
-
export const LEGACY_DESCRIPTOR_VERSION = 2;
|
|
19
|
-
const LEGACY_MAX_CONCURRENT_BACKGROUND_RUNS = 4;
|
|
20
|
-
const LEGACY_MAX_IDLE_RUNTIMES = 0;
|
|
21
|
-
const LEGACY_BACKGROUND_PROTOCOL: BackgroundProtocol = "legacy";
|
|
18
|
+
export { DESCRIPTOR_VERSION };
|
|
22
19
|
|
|
23
20
|
const THINKING_LEVELS = new Set<ThinkingLevel>(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
24
|
-
const AGENT_SOURCES = new Set<
|
|
21
|
+
const AGENT_SOURCES = new Set<AgentSource>(["user", "project"]);
|
|
25
22
|
const MODES = new Set<SubagentMode>(["one-shot", "continuable"]);
|
|
26
23
|
const PROVIDERS = new Set<SubagentProviderName>(["spawn", "fork"]);
|
|
27
|
-
const
|
|
28
|
-
const BACKGROUND_PROTOCOLS = new Set<BackgroundProtocol>(["legacy", "mailbox-v2"]);
|
|
24
|
+
const RUNTIME_MODES = new Set<RuntimeMode>(["foreground", "background"]);
|
|
29
25
|
const AGENT_SCOPES = new Set<AgentScope>(["user", "project", "both"]);
|
|
30
26
|
const AGENT_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
31
27
|
/** UUIDv7 with the standard RFC 9562 variant bits (version 7, variant 10xx). */
|
|
@@ -110,7 +106,7 @@ function stringArray(value: unknown, field: string): string[] | undefined {
|
|
|
110
106
|
|
|
111
107
|
function parseAgent(value: unknown): AgentSnapshot {
|
|
112
108
|
const input = record(value, "agent");
|
|
113
|
-
const source = string(input.source, "agent.source") as
|
|
109
|
+
const source = string(input.source, "agent.source") as AgentSource;
|
|
114
110
|
if (!AGENT_SOURCES.has(source)) throw new Error(`agent.source is unsupported: ${source}`);
|
|
115
111
|
const thinking = optionalString(input.thinking, "agent.thinking") as ThinkingLevel | undefined;
|
|
116
112
|
if (thinking && !THINKING_LEVELS.has(thinking)) throw new Error(`agent.thinking is unsupported: ${thinking}`);
|
|
@@ -154,15 +150,25 @@ function parseContext(value: unknown): ContextInheritance {
|
|
|
154
150
|
return { mode };
|
|
155
151
|
}
|
|
156
152
|
|
|
157
|
-
|
|
158
|
-
const input = record(value, "descriptor");
|
|
153
|
+
function parseRuntimeMode(value: unknown): RuntimeMode {
|
|
159
154
|
if (
|
|
160
|
-
|
|
161
|
-
|
|
155
|
+
typeof value !== "string"
|
|
156
|
+
|| !RUNTIME_MODES.has(value as RuntimeMode)
|
|
162
157
|
) {
|
|
163
|
-
throw new Error(
|
|
158
|
+
throw new Error(
|
|
159
|
+
'runtime.runtimeMode must be "foreground" or "background"',
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return value as RuntimeMode;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function parseDescriptor(value: unknown): SubagentDescriptor {
|
|
166
|
+
const input = record(value, "descriptor");
|
|
167
|
+
if (input.version !== DESCRIPTOR_VERSION) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`unsupported descriptor version: ${String(input.version)}; this release persists version ${DESCRIPTOR_VERSION}`,
|
|
170
|
+
);
|
|
164
171
|
}
|
|
165
|
-
const version = input.version;
|
|
166
172
|
const mode = string(input.mode, "mode") as SubagentMode;
|
|
167
173
|
if (!MODES.has(mode)) throw new Error(`unsupported descriptor mode: ${mode}`);
|
|
168
174
|
const provider = string(input.provider, "provider") as SubagentProviderName;
|
|
@@ -174,22 +180,31 @@ export function parseDescriptor(value: unknown): SubagentDescriptor {
|
|
|
174
180
|
const runtime = record(input.runtime, "runtime");
|
|
175
181
|
const agentScope = string(runtime.agentScope, "runtime.agentScope") as AgentScope;
|
|
176
182
|
if (!AGENT_SCOPES.has(agentScope)) throw new Error(`unsupported runtime.agentScope: ${agentScope}`);
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
+
};
|
|
193
208
|
|
|
194
209
|
const createdAt = string(input.createdAt, "createdAt");
|
|
195
210
|
if (Number.isNaN(Date.parse(createdAt))) throw new Error("createdAt must be an ISO date string");
|
|
@@ -213,50 +228,8 @@ export function parseDescriptor(value: unknown): SubagentDescriptor {
|
|
|
213
228
|
id: string(model.id, "model.id"),
|
|
214
229
|
},
|
|
215
230
|
thinkingLevel,
|
|
216
|
-
runtime:
|
|
217
|
-
agentScope,
|
|
218
|
-
maxDepth: safeNatural(runtime.maxDepth, "runtime.maxDepth"),
|
|
219
|
-
enableRunInBackground:
|
|
220
|
-
runtime.enableRunInBackground === undefined
|
|
221
|
-
? true
|
|
222
|
-
: boolean(runtime.enableRunInBackground, "runtime.enableRunInBackground"),
|
|
223
|
-
defaultBackground: boolean(runtime.defaultBackground, "runtime.defaultBackground"),
|
|
224
|
-
maxConcurrentBackgroundRuns:
|
|
225
|
-
runtime.maxConcurrentBackgroundRuns === undefined
|
|
226
|
-
? LEGACY_MAX_CONCURRENT_BACKGROUND_RUNS
|
|
227
|
-
: boundedInteger(
|
|
228
|
-
runtime.maxConcurrentBackgroundRuns,
|
|
229
|
-
"runtime.maxConcurrentBackgroundRuns",
|
|
230
|
-
1,
|
|
231
|
-
Number.MAX_SAFE_INTEGER,
|
|
232
|
-
),
|
|
233
|
-
maxIdleRuntimes:
|
|
234
|
-
runtime.maxIdleRuntimes === undefined
|
|
235
|
-
? LEGACY_MAX_IDLE_RUNTIMES
|
|
236
|
-
: boundedInteger(
|
|
237
|
-
runtime.maxIdleRuntimes,
|
|
238
|
-
"runtime.maxIdleRuntimes",
|
|
239
|
-
0,
|
|
240
|
-
Number.MAX_SAFE_INTEGER,
|
|
241
|
-
),
|
|
242
|
-
backgroundProtocol,
|
|
243
|
-
reportDelivery,
|
|
244
|
-
inheritExtensions: boolean(runtime.inheritExtensions, "runtime.inheritExtensions"),
|
|
245
|
-
openAIIdentity: boolean(runtime.openAIIdentity, "runtime.openAIIdentity"),
|
|
246
|
-
maxOutputBytes: boundedInteger(
|
|
247
|
-
runtime.maxOutputBytes,
|
|
248
|
-
"runtime.maxOutputBytes",
|
|
249
|
-
1024,
|
|
250
|
-
1024 * 1024,
|
|
251
|
-
),
|
|
252
|
-
},
|
|
231
|
+
runtime: parsedRuntime,
|
|
253
232
|
};
|
|
254
|
-
if (version === LEGACY_DESCRIPTOR_VERSION) {
|
|
255
|
-
return {
|
|
256
|
-
version: LEGACY_DESCRIPTOR_VERSION,
|
|
257
|
-
...base,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
233
|
const task = record(input.task, "task");
|
|
261
234
|
return {
|
|
262
235
|
version: DESCRIPTOR_VERSION,
|
|
@@ -288,8 +261,5 @@ export function foldDescriptor(entries: readonly SessionEntry[]): DescriptorFold
|
|
|
288
261
|
export function descriptorContext(
|
|
289
262
|
descriptor: SubagentDescriptor,
|
|
290
263
|
): ContextInheritance {
|
|
291
|
-
|
|
292
|
-
return descriptor.provider === "fork"
|
|
293
|
-
? { mode: "all_completed" }
|
|
294
|
-
: { mode: "fresh" };
|
|
264
|
+
return structuredClone(descriptor.context);
|
|
295
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";
|
|
@@ -60,61 +59,53 @@ function disableOwnedTools(
|
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
function assertBackgroundControlsEnabled(settings: SubagentSettings, toolName: string): void {
|
|
63
|
-
if (
|
|
62
|
+
if (settings.runtimeMode === "foreground") {
|
|
64
63
|
throw new Error(
|
|
65
|
-
`tool "${toolName}" is unavailable in foreground-only mode (
|
|
64
|
+
`tool "${toolName}" is unavailable in foreground-only mode (runtimeMode: "foreground")`,
|
|
66
65
|
);
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
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
|
+
|
|
70
79
|
function registerDelegationTool(
|
|
71
80
|
pi: ExtensionAPI,
|
|
72
81
|
coordinator: SubagentCoordinator,
|
|
73
82
|
settings: SubagentSettings,
|
|
74
83
|
agentDiscovery?: AgentDiscoveryResult,
|
|
75
84
|
): unknown {
|
|
76
|
-
const
|
|
85
|
+
const foregroundOnly = settings.runtimeMode === "foreground";
|
|
77
86
|
const agentNames = agentDiscovery?.agents.map((agent) => agent.name);
|
|
78
|
-
const
|
|
79
|
-
? "Delegate a complete task to a named child path with selectable completed-turn context. " +
|
|
80
|
-
"This foreground-only tool waits for the child and returns its final answer. " +
|
|
81
|
-
"Independent sibling calls may still execute in parallel."
|
|
82
|
-
: defaultBackground
|
|
83
|
-
? "Delegate a complete task to a named child path with selectable completed-turn context. " +
|
|
84
|
-
(settings.backgroundProtocol === "mailbox-v2"
|
|
85
|
-
? "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. "
|
|
86
|
-
: "Background mode is continuable and returns a readable path plus durable id; use send_message for later FIFO turns. ") +
|
|
87
|
-
"Start independent children together in one assistant message."
|
|
88
|
-
: "Delegate a complete task to a named child path with selectable completed-turn context. " +
|
|
89
|
-
"This tool waits for the result by default; set run_in_background to true to return a readable path plus durable id.";
|
|
90
|
-
const promptGuidelines = (!enableRunInBackground
|
|
87
|
+
const promptGuidelines = (foregroundOnly
|
|
91
88
|
? [
|
|
92
89
|
"Use subagent for focused independent work and give it a complete standalone prompt.",
|
|
93
90
|
"This subagent tool is foreground-only: every call waits for and returns the child's final answer.",
|
|
94
91
|
"Independent subagent calls can still be issued together in one assistant message and execute in parallel.",
|
|
95
92
|
]
|
|
96
|
-
:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
]
|
|
102
|
-
: [
|
|
103
|
-
"Use subagent for focused independent work and give it a complete standalone prompt.",
|
|
104
|
-
"Subagent calls wait for the result by default; request background mode only when work can continue independently.",
|
|
105
|
-
"Independent subagent calls can still be issued together in one assistant message and execute in parallel.",
|
|
106
|
-
]).concat([
|
|
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([
|
|
107
98
|
"Set task_name when a stable readable path will help later control calls; otherwise a name is generated from description.",
|
|
108
99
|
"Use fresh context by default and request all_completed or last_n_completed only when parent history materially helps.",
|
|
109
100
|
]);
|
|
110
|
-
const parameters = delegationParameters(
|
|
101
|
+
const parameters = delegationParameters(agentNames);
|
|
111
102
|
pi.registerTool({
|
|
112
103
|
name: "subagent",
|
|
113
104
|
label: "Subagent",
|
|
114
|
-
description,
|
|
115
|
-
promptSnippet:
|
|
116
|
-
? "
|
|
117
|
-
: "
|
|
105
|
+
description: modeDescription(settings),
|
|
106
|
+
promptSnippet: foregroundOnly
|
|
107
|
+
? "Run focused independent work in foreground child agents"
|
|
108
|
+
: "Delegate focused work to named child agents",
|
|
118
109
|
promptGuidelines,
|
|
119
110
|
executionMode: "parallel",
|
|
120
111
|
parameters,
|
|
@@ -136,7 +127,7 @@ function registerDelegationTool(
|
|
|
136
127
|
return coordinator.outcomeToolResult(outcome);
|
|
137
128
|
},
|
|
138
129
|
renderCall(args, theme) {
|
|
139
|
-
return renderDelegationCall(args, theme, "spawn");
|
|
130
|
+
return renderDelegationCall(args, theme, "spawn", settings.runtimeMode);
|
|
140
131
|
},
|
|
141
132
|
renderResult(result, options, theme) {
|
|
142
133
|
return renderDelegationResult(
|
|
@@ -157,16 +148,16 @@ function registerForkDelegationTool(
|
|
|
157
148
|
agentDiscovery?: AgentDiscoveryResult,
|
|
158
149
|
): unknown {
|
|
159
150
|
const agentNames = agentDiscovery?.agents.map((agent) => agent.name);
|
|
160
|
-
const parameters = forkDelegationParameters(
|
|
161
|
-
agentNames,
|
|
162
|
-
settings.enableRunInBackground,
|
|
163
|
-
);
|
|
151
|
+
const parameters = forkDelegationParameters(agentNames);
|
|
164
152
|
pi.registerTool({
|
|
165
153
|
name: "subagent_fork",
|
|
166
154
|
label: "Subagent Fork",
|
|
167
155
|
description:
|
|
168
156
|
"Delegate a task to a child seeded with all completed turns in this conversation. " +
|
|
169
|
-
"The current in-flight tool-calling turn is excluded.
|
|
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."),
|
|
170
161
|
promptSnippet: "Delegate context-dependent work to a child seeded with completed turns",
|
|
171
162
|
promptGuidelines: [
|
|
172
163
|
"Use subagent_fork only when completed conversation history materially helps the delegated task.",
|
|
@@ -191,7 +182,7 @@ function registerForkDelegationTool(
|
|
|
191
182
|
return coordinator.outcomeToolResult(outcome);
|
|
192
183
|
},
|
|
193
184
|
renderCall(args, theme) {
|
|
194
|
-
return renderDelegationCall(args, theme, "fork");
|
|
185
|
+
return renderDelegationCall(args, theme, "fork", settings.runtimeMode);
|
|
195
186
|
},
|
|
196
187
|
renderResult(result, options, theme) {
|
|
197
188
|
return renderDelegationResult(
|
|
@@ -226,7 +217,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
226
217
|
name: "send_message",
|
|
227
218
|
label: "Send Message",
|
|
228
219
|
description:
|
|
229
|
-
"Send a message to a direct continuable child.
|
|
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. " +
|
|
230
221
|
"This call returns acceptance only, never the child's answer.",
|
|
231
222
|
promptSnippet: "Send or enqueue a message for a direct continuable subagent",
|
|
232
223
|
executionMode: "parallel",
|
|
@@ -244,10 +235,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
244
235
|
content: [
|
|
245
236
|
{
|
|
246
237
|
type: "text",
|
|
247
|
-
text:
|
|
248
|
-
delivery.kind === "mailbox-v2"
|
|
249
|
-
? `message ${delivery.messageId} durably enqueued for ${delivery.taskPath}; ${delivery.pendingMessages} pending`
|
|
250
|
-
: `message queued as the next turn for ${delivery.taskPath}`,
|
|
238
|
+
text: `message ${delivery.messageId} durably enqueued for ${delivery.taskPath}; ${delivery.pendingMessages} pending`,
|
|
251
239
|
},
|
|
252
240
|
],
|
|
253
241
|
details: {
|
|
@@ -255,12 +243,8 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
255
243
|
action: "send",
|
|
256
244
|
agentId: delivery.agentId,
|
|
257
245
|
taskPath: delivery.taskPath,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
messageId: delivery.messageId,
|
|
261
|
-
pendingMessages: delivery.pendingMessages,
|
|
262
|
-
}
|
|
263
|
-
: {}),
|
|
246
|
+
messageId: delivery.messageId,
|
|
247
|
+
pendingMessages: delivery.pendingMessages,
|
|
264
248
|
} satisfies ControlDetails,
|
|
265
249
|
};
|
|
266
250
|
},
|
|
@@ -270,9 +254,9 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
270
254
|
name: "followup_task",
|
|
271
255
|
label: "Follow-up Task",
|
|
272
256
|
description:
|
|
273
|
-
"For a direct
|
|
257
|
+
"For a direct continuable child, atomically claim its current pending FIFO mailbox and start exactly one scheduled turn. " +
|
|
274
258
|
"This call returns turn acceptance, not the child's answer.",
|
|
275
|
-
promptSnippet: "Start one
|
|
259
|
+
promptSnippet: "Start one child turn from queued mailbox messages",
|
|
276
260
|
executionMode: "parallel",
|
|
277
261
|
parameters: FollowupTaskParameters,
|
|
278
262
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
@@ -306,8 +290,8 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
306
290
|
name: "wait_agent",
|
|
307
291
|
label: "Wait Agent",
|
|
308
292
|
description:
|
|
309
|
-
"Wait event-driven for unread completion updates from direct
|
|
310
|
-
promptSnippet: "Wait for quiet
|
|
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",
|
|
311
295
|
parameters: WaitAgentParameters,
|
|
312
296
|
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
313
297
|
assertBackgroundControlsEnabled(sessionSettings, "wait_agent");
|
|
@@ -371,7 +355,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
371
355
|
label: "List Agents",
|
|
372
356
|
description:
|
|
373
357
|
"List direct continuable children or all descendants. running means an active turn, idle means resident between turns, " +
|
|
374
|
-
"ready means persisted and cold-resumable, and
|
|
358
|
+
"ready means persisted and cold-resumable, and children show pending task messages separately from unread completion updates.",
|
|
375
359
|
promptSnippet: "List continuable child agents and their lifecycle status",
|
|
376
360
|
parameters: ListAgentsParameters,
|
|
377
361
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -386,7 +370,7 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
386
370
|
},
|
|
387
371
|
});
|
|
388
372
|
|
|
389
|
-
for (const customType of [REPORT_CUSTOM_TYPE
|
|
373
|
+
for (const customType of [REPORT_CUSTOM_TYPE]) {
|
|
390
374
|
pi.registerMessageRenderer<ParentMessageDetails>(customType, (message, options, theme) => {
|
|
391
375
|
const content =
|
|
392
376
|
typeof message.content === "string"
|
|
@@ -411,21 +395,19 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
411
395
|
);
|
|
412
396
|
const parent = await coordinator.parentFromContext(ctx);
|
|
413
397
|
const entries = await coordinator.list(parent, "descendants");
|
|
414
|
-
const schedulingMode = !sessionSettings.enableRunInBackground
|
|
415
|
-
? "foreground-only"
|
|
416
|
-
: sessionSettings.defaultBackground
|
|
417
|
-
? "background-first"
|
|
418
|
-
: "foreground-first";
|
|
419
398
|
const sections = [
|
|
420
|
-
`Mode: ${
|
|
399
|
+
`Mode: ${sessionSettings.runtimeMode}`,
|
|
421
400
|
`Background concurrency: ${sessionSettings.maxConcurrentBackgroundRuns}`,
|
|
422
401
|
`Idle runtime LRU: ${sessionSettings.maxIdleRuntimes}`,
|
|
423
|
-
|
|
402
|
+
"Background protocol: durable mailbox",
|
|
424
403
|
`OpenAI identity inline: ${sessionSettings.openAIIdentity ? "enabled" : "disabled"}`,
|
|
425
404
|
agentSync?.diagnostics.length
|
|
426
405
|
? "Bundled templates: initialization skipped (see diagnostics)"
|
|
427
406
|
: `Bundled templates: initialization only (${agentSync?.packageVersion ?? "not initialized"})`,
|
|
428
407
|
`User agent dir: ${agentSync?.userAgentsDir ?? coordinator.getUserAgentsDir()}`,
|
|
408
|
+
agentSync && agentSync.retired.length > 0
|
|
409
|
+
? `Deleted presets (never restored): ${agentSync.retired.join(", ")}`
|
|
410
|
+
: null,
|
|
429
411
|
`Agents:\n${formatAgentCatalog(discovery.agents)}`,
|
|
430
412
|
`Children:\n${coordinator.formatCatalog(entries, "descendants")}`,
|
|
431
413
|
];
|
|
@@ -464,31 +446,8 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
464
446
|
if (sessionDiscovery.agents.length === 0) {
|
|
465
447
|
disableOwnedTools(pi, registeredParameters);
|
|
466
448
|
}
|
|
467
|
-
if (
|
|
449
|
+
if (sessionSettings.runtimeMode === "foreground") {
|
|
468
450
|
disableOwnedTools(pi, backgroundControlParameters);
|
|
469
|
-
} else if (sessionSettings.backgroundProtocol !== "mailbox-v2") {
|
|
470
|
-
let hasMailboxChild = true;
|
|
471
|
-
try {
|
|
472
|
-
const parent = await coordinator.parentFromContext(ctx);
|
|
473
|
-
const entries = await coordinator.list(parent, "descendants");
|
|
474
|
-
hasMailboxChild = entries.some(
|
|
475
|
-
(entry) =>
|
|
476
|
-
entry.kind === "child"
|
|
477
|
-
&& entry.descriptor.runtime.backgroundProtocol === "mailbox-v2",
|
|
478
|
-
);
|
|
479
|
-
} catch {
|
|
480
|
-
// Keep the control available when catalog inspection fails so a
|
|
481
|
-
// transient diagnostic cannot strand an existing mailbox child.
|
|
482
|
-
}
|
|
483
|
-
if (!hasMailboxChild) {
|
|
484
|
-
disableOwnedTools(
|
|
485
|
-
pi,
|
|
486
|
-
new Map<string, unknown>([
|
|
487
|
-
["followup_task", FollowupTaskParameters],
|
|
488
|
-
["wait_agent", WaitAgentParameters],
|
|
489
|
-
]),
|
|
490
|
-
);
|
|
491
|
-
}
|
|
492
451
|
}
|
|
493
452
|
if (!agentSyncNotified && agentSync) {
|
|
494
453
|
agentSyncNotified = true;
|
|
@@ -502,6 +461,14 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
502
461
|
if (agentSync.removed.length > 0) {
|
|
503
462
|
lines.push(`retired: ${agentSync.removed.join(", ")}`);
|
|
504
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
|
+
}
|
|
505
472
|
if (agentSync.backups.length > 0) {
|
|
506
473
|
lines.push("backups:", ...agentSync.backups.map((backup) => `- ${backup.name}: ${backup.path}`));
|
|
507
474
|
}
|
|
@@ -509,6 +476,8 @@ export default function subagentExtension(pi: ExtensionAPI): void {
|
|
|
509
476
|
agentSync.installed.length > 0 ||
|
|
510
477
|
agentSync.updated.length > 0 ||
|
|
511
478
|
agentSync.removed.length > 0 ||
|
|
479
|
+
(agentSync.retirementChanged && agentSync.retired.length > 0) ||
|
|
480
|
+
agentSync.restored.length > 0 ||
|
|
512
481
|
agentSync.backups.length > 0
|
|
513
482
|
) {
|
|
514
483
|
ctx.ui.notify(lines.join("\n"), "info");
|
package/src/render.ts
CHANGED
|
@@ -9,7 +9,6 @@ export function renderDelegationCall(
|
|
|
9
9
|
task_name?: string;
|
|
10
10
|
description?: string;
|
|
11
11
|
prompt?: string;
|
|
12
|
-
run_in_background?: boolean;
|
|
13
12
|
context?: { mode?: string };
|
|
14
13
|
},
|
|
15
14
|
theme: {
|
|
@@ -17,17 +16,10 @@ export function renderDelegationCall(
|
|
|
17
16
|
bold(text: string): string;
|
|
18
17
|
},
|
|
19
18
|
provider: "spawn" | "fork",
|
|
19
|
+
runtimeMode: "foreground" | "background" = "background",
|
|
20
20
|
): Text {
|
|
21
|
-
const mode
|
|
22
|
-
|
|
23
|
-
? args.run_in_background === true
|
|
24
|
-
? "fork · background"
|
|
25
|
-
: "fork · foreground"
|
|
26
|
-
: args.run_in_background === false
|
|
27
|
-
? `${args.context?.mode ?? "fresh"} · foreground`
|
|
28
|
-
: args.run_in_background === true
|
|
29
|
-
? `${args.context?.mode ?? "fresh"} · background`
|
|
30
|
-
: `${args.context?.mode ?? "fresh"} · configured default`;
|
|
21
|
+
const contextMode = provider === "fork" ? "all_completed" : (args.context?.mode ?? "fresh");
|
|
22
|
+
const mode = `${contextMode} · ${runtimeMode}`;
|
|
31
23
|
let text =
|
|
32
24
|
theme.fg("toolTitle", theme.bold(provider === "fork" ? "subagent_fork " : "subagent ")) +
|
|
33
25
|
theme.fg("accent", args.agent ?? "…") +
|
|
@@ -109,13 +101,11 @@ export function renderParentMessage(
|
|
|
109
101
|
},
|
|
110
102
|
): Text | Markdown {
|
|
111
103
|
if (expanded) return new Markdown(content, outputPad, 0, getMarkdownTheme());
|
|
112
|
-
const kind = details?.kind === "report" ? "report" : "settled";
|
|
113
|
-
const icon = details?.stopReason && details.stopReason !== "completed" ? "◐" : "●";
|
|
114
104
|
const label = details?.label ? ` — ${details.label}` : "";
|
|
115
105
|
return new Text(
|
|
116
|
-
theme.fg("accent",
|
|
106
|
+
theme.fg("accent", "●") +
|
|
117
107
|
" " +
|
|
118
|
-
theme.fg("toolTitle", theme.bold(
|
|
108
|
+
theme.fg("toolTitle", theme.bold("subagent report")) +
|
|
119
109
|
theme.fg(
|
|
120
110
|
"muted",
|
|
121
111
|
` ${details?.taskPath ?? details?.childAgentId ?? "unknown"}${label}`,
|
package/src/schemas.ts
CHANGED
|
@@ -70,87 +70,25 @@ function forkDelegationFields(agentNames?: readonly string[]) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function
|
|
74
|
-
enableRunInBackground: boolean,
|
|
75
|
-
agentNames?: readonly string[],
|
|
76
|
-
) {
|
|
73
|
+
export function delegationParameters(agentNames?: readonly string[]) {
|
|
77
74
|
const fields = delegationFields(agentNames);
|
|
78
|
-
const contextualFields = {
|
|
79
|
-
...fields,
|
|
80
|
-
context: Type.Optional(ContextParameters),
|
|
81
|
-
};
|
|
82
75
|
return Type.Object(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Type.Boolean({
|
|
88
|
-
description:
|
|
89
|
-
"Run as a continuable background child. The spawn provider defaults this from configuration.",
|
|
90
|
-
}),
|
|
91
|
-
),
|
|
92
|
-
}
|
|
93
|
-
: contextualFields,
|
|
76
|
+
{
|
|
77
|
+
...fields,
|
|
78
|
+
context: Type.Optional(ContextParameters),
|
|
79
|
+
},
|
|
94
80
|
{ additionalProperties: false },
|
|
95
81
|
);
|
|
96
82
|
}
|
|
97
83
|
|
|
98
|
-
export
|
|
99
|
-
|
|
100
|
-
export const DelegationParameters = createDelegationParameters(true);
|
|
101
|
-
|
|
102
|
-
export function delegationParameters(
|
|
103
|
-
enableRunInBackground: boolean,
|
|
104
|
-
agentNames?: readonly string[],
|
|
105
|
-
) {
|
|
106
|
-
if (agentNames === undefined) {
|
|
107
|
-
return enableRunInBackground ? DelegationParameters : ForegroundDelegationParameters;
|
|
108
|
-
}
|
|
109
|
-
return createDelegationParameters(enableRunInBackground, agentNames);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function createForkDelegationParameters(
|
|
113
|
-
enableRunInBackground: boolean,
|
|
114
|
-
agentNames?: readonly string[],
|
|
115
|
-
) {
|
|
84
|
+
export function forkDelegationParameters(agentNames?: readonly string[]) {
|
|
116
85
|
const fields = forkDelegationFields(agentNames);
|
|
117
86
|
return Type.Object(
|
|
118
|
-
|
|
119
|
-
? {
|
|
120
|
-
...fields,
|
|
121
|
-
run_in_background: Type.Optional(
|
|
122
|
-
Type.Boolean({
|
|
123
|
-
description:
|
|
124
|
-
"Run as a continuable inherited-context background child. Fork remains foreground by default.",
|
|
125
|
-
}),
|
|
126
|
-
),
|
|
127
|
-
}
|
|
128
|
-
: fields,
|
|
87
|
+
fields,
|
|
129
88
|
{ additionalProperties: false },
|
|
130
89
|
);
|
|
131
90
|
}
|
|
132
91
|
|
|
133
|
-
export const ForegroundForkDelegationParameters =
|
|
134
|
-
createForkDelegationParameters(false);
|
|
135
|
-
|
|
136
|
-
export const ForkDelegationParameters =
|
|
137
|
-
createForkDelegationParameters(true);
|
|
138
|
-
|
|
139
|
-
export function forkDelegationParameters(
|
|
140
|
-
agentNames?: readonly string[],
|
|
141
|
-
enableRunInBackground = true,
|
|
142
|
-
) {
|
|
143
|
-
if (agentNames === undefined) {
|
|
144
|
-
return enableRunInBackground
|
|
145
|
-
? ForkDelegationParameters
|
|
146
|
-
: ForegroundForkDelegationParameters;
|
|
147
|
-
}
|
|
148
|
-
return createForkDelegationParameters(
|
|
149
|
-
enableRunInBackground,
|
|
150
|
-
agentNames,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
92
|
export const SendMessageParameters = Type.Object(
|
|
155
93
|
{
|
|
156
94
|
subagent_id: Type.String({
|
|
@@ -171,7 +109,7 @@ export const FollowupTaskParameters = Type.Object(
|
|
|
171
109
|
{
|
|
172
110
|
subagent_id: Type.String({
|
|
173
111
|
description:
|
|
174
|
-
"Readable absolute/relative task path or durable id of a direct
|
|
112
|
+
"Readable absolute/relative task path or durable id of a direct continuable child",
|
|
175
113
|
minLength: 1,
|
|
176
114
|
maxLength: 4096,
|
|
177
115
|
}),
|