@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/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
- AgentSnapshotSource,
7
- BackgroundProtocol,
6
+ AgentSource,
8
7
  ContextInheritance,
9
- ReportDelivery,
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 const DESCRIPTOR_VERSION = 3;
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<AgentSnapshotSource>(["bundled", "user", "project"]);
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 REPORT_DELIVERIES = new Set<ReportDelivery>(["wakeup", "quiet"]);
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 AgentSnapshotSource;
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
- export function parseDescriptor(value: unknown): SubagentDescriptor {
158
- const input = record(value, "descriptor");
153
+ function parseRuntimeMode(value: unknown): RuntimeMode {
159
154
  if (
160
- input.version !== LEGACY_DESCRIPTOR_VERSION
161
- && input.version !== DESCRIPTOR_VERSION
155
+ typeof value !== "string"
156
+ || !RUNTIME_MODES.has(value as RuntimeMode)
162
157
  ) {
163
- throw new Error(`unsupported descriptor version: ${String(input.version)}`);
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 reportDelivery = string(runtime.reportDelivery, "runtime.reportDelivery") as ReportDelivery;
178
- if (!REPORT_DELIVERIES.has(reportDelivery)) {
179
- throw new Error(`unsupported runtime.reportDelivery: ${reportDelivery}`);
180
- }
181
- const backgroundProtocol =
182
- runtime.backgroundProtocol === undefined
183
- ? LEGACY_BACKGROUND_PROTOCOL
184
- : string(runtime.backgroundProtocol, "runtime.backgroundProtocol") as BackgroundProtocol;
185
- if (!BACKGROUND_PROTOCOLS.has(backgroundProtocol)) {
186
- throw new Error(`unsupported runtime.backgroundProtocol: ${backgroundProtocol}`);
187
- }
188
- if (runtime.syncBundledAgents !== undefined) {
189
- // Validate, then discard the retired 0.2/0.3 runtime switch. Keeping this
190
- // read compatibility allows persisted children to cold-resume.
191
- boolean(runtime.syncBundledAgents, "runtime.syncBundledAgents");
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
- if (descriptor.version === 3) return structuredClone(descriptor.context);
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 (!settings.enableRunInBackground) {
62
+ if (settings.runtimeMode === "foreground") {
64
63
  throw new Error(
65
- `tool "${toolName}" is unavailable in foreground-only mode (enableRunInBackground: false)`,
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 { enableRunInBackground, defaultBackground } = settings;
85
+ const foregroundOnly = settings.runtimeMode === "foreground";
77
86
  const agentNames = agentDiscovery?.agents.map((agent) => agent.name);
78
- const description = !enableRunInBackground
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
- : defaultBackground
97
- ? [
98
- "Use subagent for focused independent work and give it a complete standalone prompt.",
99
- "Call subagent multiple times in one assistant message when delegations are independent.",
100
- "Keep useful parent work moving after a background subagent starts; use foreground only when the next action needs its result.",
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(enableRunInBackground, agentNames);
101
+ const parameters = delegationParameters(agentNames);
111
102
  pi.registerTool({
112
103
  name: "subagent",
113
104
  label: "Subagent",
114
- description,
115
- promptSnippet: enableRunInBackground
116
- ? "Delegate focused work to named child agents"
117
- : "Run focused independent work in foreground child agents",
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. It is foreground by default; request background mode for a continuable fork.",
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. Legacy children start or join a FIFO turn; mailbox-v2 children only durably enqueue it and require followup_task to execute. " +
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
- ...(delivery.kind === "mailbox-v2"
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 mailbox-v2 continuable child, atomically claim its current pending FIFO mailbox and start exactly one scheduled turn. " +
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 mailbox-v2 child turn from queued messages",
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 mailbox-v2 children. Existing updates return immediately; timeout does not consume later updates.",
310
- promptSnippet: "Wait for quiet mailbox-v2 child completion updates",
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 mailbox-v2 children show pending task messages separately from unread completion updates.",
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, SETTLED_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: ${schedulingMode}`,
399
+ `Mode: ${sessionSettings.runtimeMode}`,
421
400
  `Background concurrency: ${sessionSettings.maxConcurrentBackgroundRuns}`,
422
401
  `Idle runtime LRU: ${sessionSettings.maxIdleRuntimes}`,
423
- `Background protocol: ${sessionSettings.backgroundProtocol ?? "legacy"}`,
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 (!sessionSettings.enableRunInBackground) {
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
- provider === "fork"
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", icon) +
106
+ theme.fg("accent", "●") +
117
107
  " " +
118
- theme.fg("toolTitle", theme.bold(`subagent ${kind}`)) +
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 createDelegationParameters(
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
- enableRunInBackground
84
- ? {
85
- ...contextualFields,
86
- run_in_background: Type.Optional(
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 const ForegroundDelegationParameters = createDelegationParameters(false);
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
- enableRunInBackground
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 mailbox-v2 continuable child",
112
+ "Readable absolute/relative task path or durable id of a direct continuable child",
175
113
  minLength: 1,
176
114
  maxLength: 4096,
177
115
  }),