@narumitw/pi-subagents 0.30.1 → 0.33.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/limits.ts CHANGED
@@ -31,7 +31,10 @@ export function truncateUtf8(text: string, maxBytes: number): BoundedText {
31
31
  originalBytes,
32
32
  };
33
33
  }
34
- const prefix = Buffer.from(text, "utf8").subarray(0, limit - marker.length).toString("utf8").replace(/�+$/g, "");
34
+ const prefix = Buffer.from(text, "utf8")
35
+ .subarray(0, limit - marker.length)
36
+ .toString("utf8")
37
+ .replace(/�+$/g, "");
35
38
  return { text: `${prefix}${TRUNCATION_MARKER}`, truncated: true, originalBytes };
36
39
  }
37
40
 
@@ -45,7 +48,10 @@ export function truncateUtf8Tail(text: string, maxBytes: number): BoundedText {
45
48
  const marker = Buffer.from(TAIL_TRUNCATION_MARKER, "utf8");
46
49
  if (marker.length >= limit) {
47
50
  return {
48
- text: bytes.subarray(originalBytes - limit).toString("utf8").replace(/^�+/g, ""),
51
+ text: bytes
52
+ .subarray(originalBytes - limit)
53
+ .toString("utf8")
54
+ .replace(/^�+/g, ""),
49
55
  truncated: true,
50
56
  originalBytes,
51
57
  };
package/src/params.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { StringEnum } from "@earendil-works/pi-ai";
2
- import { Type, type Static } from "typebox";
2
+ import { type Static, Type } from "typebox";
3
3
  import { THINKING_LEVELS } from "./agents.js";
4
4
 
5
5
  const TimeoutMs = Type.Number({
@@ -31,9 +31,15 @@ const ChainItem = Type.Object({
31
31
 
32
32
  const AggregatorItem = Type.Object(
33
33
  {
34
- agent: Type.String({ description: "Name of the fan-in agent to invoke after parallel tasks complete" }),
35
- task: Type.String({ description: "Fan-in task. Use {previous} to include all parallel outputs." }),
36
- cwd: Type.Optional(Type.String({ description: "Working directory for the aggregator process" })),
34
+ agent: Type.String({
35
+ description: "Name of the fan-in agent to invoke after parallel tasks complete",
36
+ }),
37
+ task: Type.String({
38
+ description: "Fan-in task. Use {previous} to include all parallel outputs.",
39
+ }),
40
+ cwd: Type.Optional(
41
+ Type.String({ description: "Working directory for the aggregator process" }),
42
+ ),
37
43
  timeoutMs: Type.Optional(TimeoutMs),
38
44
  thinkingLevel: Type.Optional(ThinkingLevelSchema),
39
45
  },
@@ -50,16 +56,27 @@ const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, {
50
56
  });
51
57
 
52
58
  export const SubagentParams = Type.Object({
53
- agent: Type.Optional(Type.String({ description: "Name of the agent to invoke (for single mode)" })),
59
+ agent: Type.Optional(
60
+ Type.String({ description: "Name of the agent to invoke (for single mode)" }),
61
+ ),
54
62
  task: Type.Optional(Type.String({ description: "Task to delegate (for single mode)" })),
55
- tasks: Type.Optional(Type.Array(TaskItem, { description: "Array of {agent, task} for parallel execution" })),
56
- chain: Type.Optional(Type.Array(ChainItem, { description: "Array of {agent, task} for sequential execution" })),
63
+ tasks: Type.Optional(
64
+ Type.Array(TaskItem, { description: "Array of {agent, task} for parallel execution" }),
65
+ ),
66
+ chain: Type.Optional(
67
+ Type.Array(ChainItem, { description: "Array of {agent, task} for sequential execution" }),
68
+ ),
57
69
  aggregator: Type.Optional(AggregatorItem),
58
70
  agentScope: Type.Optional(AgentScopeSchema),
59
71
  confirmProjectAgents: Type.Optional(
60
- Type.Boolean({ description: "Prompt before running project-local agents. Default: true.", default: true }),
72
+ Type.Boolean({
73
+ description: "Prompt before running project-local agents. Default: true.",
74
+ default: true,
75
+ }),
76
+ ),
77
+ cwd: Type.Optional(
78
+ Type.String({ description: "Working directory for the agent process (single mode)" }),
61
79
  ),
62
- cwd: Type.Optional(Type.String({ description: "Working directory for the agent process (single mode)" })),
63
80
  timeoutMs: Type.Optional(TimeoutMs),
64
81
  thinkingLevel: Type.Optional(ThinkingLevelSchema),
65
82
  });
package/src/render.ts CHANGED
@@ -440,8 +440,7 @@ export function renderSubagentResult(
440
440
  const collapsed = getCollapsedDisplayItems(r);
441
441
  const finalOutput = getResultFinalOutput(r).trim();
442
442
  text += `\n\n${theme.fg("muted", `─── Step ${r.step}: `)}${theme.fg("accent", r.agent)} ${rIcon}`;
443
- if (rFailed && r.errorMessage)
444
- text += `\n${theme.fg("error", `Error: ${r.errorMessage}`)}`;
443
+ if (rFailed && r.errorMessage) text += `\n${theme.fg("error", `Error: ${r.errorMessage}`)}`;
445
444
  if (collapsed.items.length > 0)
446
445
  text += `\n${renderDisplayItems(collapsed.items, 5, collapsed.total)}`;
447
446
  else if (currentIsRunning && r === currentResult)
@@ -605,8 +604,7 @@ export function renderSubagentResult(
605
604
  const collapsed = getCollapsedDisplayItems(r);
606
605
  const finalOutput = getResultFinalOutput(r).trim();
607
606
  text += `\n\n${theme.fg("muted", "─── ")}${theme.fg("accent", r.agent)} ${rIcon}`;
608
- if (rFailed && r.errorMessage)
609
- text += `\n${theme.fg("error", `Error: ${r.errorMessage}`)}`;
607
+ if (rFailed && r.errorMessage) text += `\n${theme.fg("error", `Error: ${r.errorMessage}`)}`;
610
608
  if (collapsed.items.length > 0)
611
609
  text += `\n${renderDisplayItems(collapsed.items, 5, collapsed.total)}`;
612
610
  else if (rRunning) text += `\n${theme.fg("muted", "(running...)")}`;
package/src/settings.ts CHANGED
@@ -80,6 +80,15 @@ export function normalizeSubagentSettings(value: unknown): SubagentSettings | un
80
80
  }
81
81
  if (Object.keys(agents).length > 0) settings.agents = agents;
82
82
  }
83
+ if (hasOwn(value, "blocking")) {
84
+ if (!isPlainObject(value.blocking)) return undefined;
85
+ const blocking: NonNullable<SubagentSettings["blocking"]> = {};
86
+ if (hasOwn(value.blocking, "enabled")) {
87
+ if (typeof value.blocking.enabled !== "boolean") return undefined;
88
+ blocking.enabled = value.blocking.enabled;
89
+ }
90
+ settings.blocking = blocking;
91
+ }
83
92
  if (hasOwn(value, "stateful")) {
84
93
  if (!isPlainObject(value.stateful)) return undefined;
85
94
  const runtime: NonNullable<SubagentSettings["stateful"]> = {};
@@ -241,6 +250,15 @@ export function saveSubagentConfig(settings: SubagentSettings): void {
241
250
  writeSettingsObject(settings);
242
251
  }
243
252
 
253
+ export type DelegationWorkflow = "all" | "async-only" | "blocking-only" | "disabled";
254
+
255
+ export interface DelegationWorkflowSettingsSnapshot {
256
+ path: string;
257
+ value: DelegationWorkflow;
258
+ source: "default" | "user settings";
259
+ error?: string;
260
+ }
261
+
244
262
  export interface CompletionDeliverySettingsSnapshot {
245
263
  path: string;
246
264
  value: CompletionDelivery;
@@ -252,6 +270,46 @@ export function subagentSettingsFilePath(): string {
252
270
  return path.join(getAgentDir(), SETTINGS_FILE);
253
271
  }
254
272
 
273
+ export function resolveDelegationWorkflow(
274
+ blockingEnabled: boolean,
275
+ statefulEnabled: boolean,
276
+ ): DelegationWorkflow {
277
+ if (blockingEnabled && statefulEnabled) return "all";
278
+ if (statefulEnabled) return "async-only";
279
+ if (blockingEnabled) return "blocking-only";
280
+ return "disabled";
281
+ }
282
+
283
+ export function inspectDelegationWorkflowSettings(): DelegationWorkflowSettingsSnapshot {
284
+ const configPath = subagentSettingsFilePath();
285
+ if (!fs.existsSync(configPath)) {
286
+ return { path: configPath, value: "all", source: "default" };
287
+ }
288
+ try {
289
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf8"));
290
+ const settings = normalizeSubagentSettings(raw);
291
+ if (!settings) throw new Error(`${SETTINGS_FILE} is not a valid settings object`);
292
+ const explicit =
293
+ (isPlainObject(raw.blocking) && hasOwn(raw.blocking, "enabled")) ||
294
+ (isPlainObject(raw.stateful) && hasOwn(raw.stateful, "enabled"));
295
+ return {
296
+ path: configPath,
297
+ value: resolveDelegationWorkflow(
298
+ settings.blocking?.enabled !== false,
299
+ settings.stateful?.enabled !== false,
300
+ ),
301
+ source: explicit ? "user settings" : "default",
302
+ };
303
+ } catch (error) {
304
+ return {
305
+ path: configPath,
306
+ value: "all",
307
+ source: "default",
308
+ error: formatError(error),
309
+ };
310
+ }
311
+ }
312
+
255
313
  export function inspectCompletionDeliverySettings(): CompletionDeliverySettingsSnapshot {
256
314
  const configPath = subagentSettingsFilePath();
257
315
  if (!fs.existsSync(configPath)) {
@@ -261,8 +319,7 @@ export function inspectCompletionDeliverySettings(): CompletionDeliverySettingsS
261
319
  const raw = JSON.parse(fs.readFileSync(configPath, "utf8"));
262
320
  const settings = normalizeSubagentSettings(raw);
263
321
  if (!settings) throw new Error(`${SETTINGS_FILE} is not a valid settings object`);
264
- const explicit =
265
- isPlainObject(raw.stateful) && hasOwn(raw.stateful, "completionDelivery");
322
+ const explicit = isPlainObject(raw.stateful) && hasOwn(raw.stateful, "completionDelivery");
266
323
  return {
267
324
  path: configPath,
268
325
  value: settings.stateful?.completionDelivery ?? DEFAULT_COMPLETION_DELIVERY,
@@ -278,6 +335,31 @@ export function inspectCompletionDeliverySettings(): CompletionDeliverySettingsS
278
335
  }
279
336
  }
280
337
 
338
+ export function updateDelegationWorkflowSetting(
339
+ value: Exclude<DelegationWorkflow, "disabled">,
340
+ ): void {
341
+ const raw = readSettingsObjectForUpdate();
342
+ const blocking = raw.blocking;
343
+ if (blocking !== undefined && !isPlainObject(blocking)) {
344
+ throw new Error(`Cannot update invalid ${SETTINGS_FILE} blocking settings`);
345
+ }
346
+ const stateful = raw.stateful;
347
+ if (stateful !== undefined && !isPlainObject(stateful)) {
348
+ throw new Error(`Cannot update invalid ${SETTINGS_FILE} stateful settings`);
349
+ }
350
+ writeSettingsObject({
351
+ ...raw,
352
+ blocking: {
353
+ ...(blocking ?? {}),
354
+ enabled: value !== "async-only",
355
+ },
356
+ stateful: {
357
+ ...(stateful ?? {}),
358
+ enabled: value !== "blocking-only",
359
+ },
360
+ });
361
+ }
362
+
281
363
  export function updateCompletionDeliverySetting(value: CompletionDelivery): void {
282
364
  const raw = readSettingsObjectForUpdate();
283
365
  const stateful = raw.stateful;
@@ -1,14 +1,11 @@
1
1
  import type { AgentConfig } from "./agents.js";
2
+ import { redactPrivateText } from "./context.js";
2
3
  import { resolveDefaultSubagentTimeoutMs } from "./execution.js";
3
4
  import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
4
- import { redactPrivateText } from "./context.js";
5
5
  import type { ManagedAgent } from "./registry.js";
6
6
 
7
7
  export function buildStatefulTurnPrompt(
8
- record: Pick<
9
- ManagedAgent,
10
- "context" | "history" | "mailbox" | "currentMailboxMessageIds"
11
- >,
8
+ record: Pick<ManagedAgent, "context" | "history" | "mailbox" | "currentMailboxMessageIds">,
12
9
  task: string,
13
10
  maxBytes = DEFAULT_MAX_CONTEXT_BYTES,
14
11
  ): { text: string; truncated: boolean } {
package/src/stateful.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  type CompletionDelivery,
13
13
  discoverAgents,
14
14
  isThinkingLevel,
15
+ type SubagentRuntimeSettings,
15
16
  THINKING_LEVELS,
16
17
  } from "./agents.js";
17
18
  import { buildContextSnapshot, type ContextMode, redactPrivateText } from "./context.js";
@@ -52,11 +53,18 @@ const MAX_COMPLETION_ERROR_BYTES = 512;
52
53
  const MAX_COMPLETIONS_PER_MESSAGE = 16;
53
54
  const COMPLETION_BATCH_DELAY_MS = 10;
54
55
 
55
- function createSpawnPromptGuidelines(completionDelivery: CompletionDelivery): string[] {
56
+ function createSpawnPromptGuidelines(
57
+ completionDelivery: CompletionDelivery,
58
+ blockingEnabled = true,
59
+ ): string[] {
56
60
  const deliveryGuidance =
57
61
  completionDelivery === "auto-resume"
58
- ? "With subagent_spawn completion delivery set to auto-resume, prefer one subagent_spawn for broad asynchronous research or review that covers related branches even when the final answer depends on its result; do not choose blocking parallel fan-out merely to keep delegation in the same turn."
59
- : "With subagent_spawn completion delivery set to next-turn (the default), prefer one subagent_spawn for broad asynchronous research or review only when the current response does not depend on its result; use the blocking subagent when the final answer depends on the detached result.";
62
+ ? blockingEnabled
63
+ ? "With subagent_spawn completion delivery set to auto-resume, prefer one subagent_spawn for broad asynchronous research or review that covers related branches even when the final answer depends on its result; do not choose blocking parallel fan-out merely to keep delegation in the same turn."
64
+ : "With subagent_spawn completion delivery set to auto-resume, prefer one subagent_spawn for broad asynchronous research or review that covers related branches even when the final answer depends on its result."
65
+ : blockingEnabled
66
+ ? "With subagent_spawn completion delivery set to next-turn (the default), prefer one subagent_spawn for broad asynchronous research or review only when the current response does not depend on its result; use the blocking subagent when the final answer depends on the detached result."
67
+ : "With subagent_spawn completion delivery set to next-turn (the default), use subagent_spawn only when the current response does not depend on its result; complete final-answer-dependent work directly because an idle root is not awakened.";
60
68
  const noLocalWorkGuidance =
61
69
  completionDelivery === "auto-resume"
62
70
  ? "After subagent_spawn returns, do useful non-overlapping local work immediately. If none remains, briefly tell the user what subagent_spawn launched and end the response; auto-resume will request a synthesis turn after completion."
@@ -66,8 +74,12 @@ function createSpawnPromptGuidelines(completionDelivery: CompletionDelivery): st
66
74
  "Set subagent_spawn thinkingLevel to the lowest sufficient thinking level for the delegated task: use off or minimal for extraction, formatting, or mechanical work; low for straightforward bounded work; medium for ordinary multi-step research or implementation; high for complex debugging, design, review, or cross-file analysis; xhigh for highly ambiguous, cross-system, or high-risk analysis; and max only for the hardest tasks when quality clearly outweighs latency and cost. Omit subagent_spawn thinkingLevel only to preserve the agent or child default.",
67
75
  deliveryGuidance,
68
76
  "Use a single subagent_spawn only for a concrete bounded subtask that can run independently and has an isolation or specialization benefit such as independent review, bounded context/output, a distinct model/tool profile, or workspace isolation.",
69
- "Use the blocking subagent instead of subagent_spawn when synchronous output is required before the main agent can continue and waiting is intentional; queued steering cannot be processed until that blocking call returns.",
70
- "When subagent_spawn fits the completion-delivery policy, do not choose a blocking parallel subagent merely to keep delegation in the same turn.",
77
+ ...(blockingEnabled
78
+ ? [
79
+ "Use the blocking subagent instead of subagent_spawn when synchronous output is required before the main agent can continue and waiting is intentional; queued steering cannot be processed until that blocking call returns.",
80
+ "When subagent_spawn fits the completion-delivery policy, do not choose a blocking parallel subagent merely to keep delegation in the same turn.",
81
+ ]
82
+ : []),
71
83
  "Add another subagent_spawn only for truly independent work with safe workspace concurrency.",
72
84
  noLocalWorkGuidance,
73
85
  'Consume and synthesize available subagent_spawn completion messages; use subagent_manage with action "interrupt" or "close" for agents that are no longer needed.',
@@ -76,8 +88,10 @@ function createSpawnPromptGuidelines(completionDelivery: CompletionDelivery): st
76
88
  }
77
89
 
78
90
  export interface StatefulSubagentDependencies {
91
+ blockingEnabled?: boolean;
79
92
  createInProcessSession?: ChildSessionFactory;
80
93
  workspaceManager?: WorkspaceManager;
94
+ settings?: SubagentRuntimeSettings;
81
95
  }
82
96
 
83
97
  export interface StatefulSubagentRuntimeStatus {
@@ -106,7 +120,10 @@ export function registerStatefulSubagents(
106
120
  pi: ExtensionAPI,
107
121
  dependencies: StatefulSubagentDependencies = {},
108
122
  ): StatefulSubagentController {
109
- const settings = readSubagentSettings()?.stateful ?? {};
123
+ const settings = Object.hasOwn(dependencies, "settings")
124
+ ? (dependencies.settings ?? {})
125
+ : (readSubagentSettings()?.stateful ?? {});
126
+ const blockingEnabled = dependencies.blockingEnabled !== false;
110
127
  const enabled = settings.enabled !== false;
111
128
  const transportKind = resolveStatefulTransportKind(settings.transport);
112
129
  let completionDelivery = resolveCompletionDelivery(settings.completionDelivery);
@@ -304,7 +321,7 @@ export function registerStatefulSubagents(
304
321
  description:
305
322
  "Start an addressable background subagent with an optional thinking level chosen for the task difficulty, return immediately with an agentId, and receive its completion asynchronously.",
306
323
  promptSnippet: "Start a reusable detached subagent; completion is delivered asynchronously",
307
- promptGuidelines: createSpawnPromptGuidelines(completionDelivery),
324
+ promptGuidelines: createSpawnPromptGuidelines(completionDelivery, blockingEnabled),
308
325
  parameters: Type.Object({
309
326
  agent: Type.String({ minLength: 1 }),
310
327
  task: Type.String({ minLength: 1, maxLength: DEFAULT_MAX_CONTEXT_BYTES }),
@@ -382,7 +399,7 @@ export function registerStatefulSubagents(
382
399
  },
383
400
  });
384
401
  refreshSpawnToolRegistration = () => {
385
- spawnTool.promptGuidelines = createSpawnPromptGuidelines(completionDelivery);
402
+ spawnTool.promptGuidelines = createSpawnPromptGuidelines(completionDelivery, blockingEnabled);
386
403
  pi.registerTool(spawnTool);
387
404
  };
388
405
  refreshSpawnToolRegistration();
@@ -531,39 +548,6 @@ export function registerStatefulSubagents(
531
548
  },
532
549
  });
533
550
 
534
- pi.registerCommand("subagents:agents", {
535
- description: "Inspect or clear current-session subagents",
536
- getArgumentCompletions(prefix: string) {
537
- return ["list", "clear"]
538
- .filter((value) => value.startsWith(prefix))
539
- .map((value) => ({ value, label: value }));
540
- },
541
- async handler(args, ctx) {
542
- const subcommand = args.trim().toLowerCase() || "list";
543
- if (subcommand === "clear") {
544
- const count = await controller.clearAgents();
545
- ctx.ui.notify(
546
- count > 0
547
- ? `Cleared ${count} current-session subagent${count === 1 ? "" : "s"}.`
548
- : statefulEmptyMessage(controller.getRuntimeStatus()),
549
- "info",
550
- );
551
- return;
552
- }
553
- if (subcommand !== "list") {
554
- ctx.ui.notify(`Unknown /subagents:agents subcommand: ${subcommand}`, "warning");
555
- return;
556
- }
557
- const agents = controller.listAgents(true);
558
- ctx.ui.notify(
559
- agents.length
560
- ? agents.map(formatLine).join("\n")
561
- : statefulEmptyMessage(controller.getRuntimeStatus()),
562
- "info",
563
- );
564
- },
565
- });
566
-
567
551
  return controller;
568
552
  }
569
553
 
@@ -652,12 +636,6 @@ export function resolveSpawnContextMode(
652
636
  return normalizeContextMode(value);
653
637
  }
654
638
 
655
- function statefulEmptyMessage(status: StatefulSubagentRuntimeStatus): string {
656
- if (!status.enabled) return "Stateful subagents are disabled in user settings.";
657
- if (!status.initialized) return "Stateful subagents are not initialized for this session.";
658
- return "No current-session subagents.";
659
- }
660
-
661
639
  export function formatStatefulAgentLine(agent: ManagedAgent): string {
662
640
  const elapsedSeconds = Math.max(0, Math.floor((Date.now() - agent.updatedAt) / 1000));
663
641
  const actions =
package/src/subagents.ts CHANGED
@@ -22,6 +22,33 @@ import { consumeSubagentSettingsNotice, readSubagentSettings } from "./settings.
22
22
  import { registerStatefulSubagents } from "./stateful.js";
23
23
 
24
24
  export default function (pi: ExtensionAPI) {
25
+ const settings = readSubagentSettings();
26
+ if (settings?.blocking?.enabled !== false) registerBlockingSubagent(pi);
27
+
28
+ pi.on("session_start", (_event, ctx) => {
29
+ // Preserve a one-shot migration notice from extension load while refreshing
30
+ // validation against settings that may have changed before this session.
31
+ const loadNotice = consumeSubagentSettingsNotice();
32
+ readSubagentSettings();
33
+ const refreshedNotice = consumeSubagentSettingsNotice();
34
+ const notice = [
35
+ ...new Set([loadNotice, refreshedNotice].filter((value) => value !== undefined)),
36
+ ].join("\n");
37
+ if (notice) ctx.ui.notify(notice, "warning");
38
+ });
39
+
40
+ const blockingEnabled = settings?.blocking?.enabled !== false;
41
+ const statefulRuntime = registerStatefulSubagents(pi, {
42
+ blockingEnabled,
43
+ settings: settings?.stateful,
44
+ });
45
+ registerSubagentConfigCommand(pi, {
46
+ ...statefulRuntime,
47
+ getBlockingEnabled: () => blockingEnabled,
48
+ });
49
+ }
50
+
51
+ function registerBlockingSubagent(pi: ExtensionAPI) {
25
52
  pi.registerTool<typeof SubagentParams, SubagentDetails>({
26
53
  name: "subagent",
27
54
  label: "Blocking Subagent",
@@ -65,24 +92,14 @@ export default function (pi: ExtensionAPI) {
65
92
  if ((event.details as (SubagentDetails & { isError?: boolean }) | undefined)?.isError)
66
93
  return { isError: true };
67
94
  });
68
-
69
- pi.on("session_start", (_event, ctx) => {
70
- let notice = consumeSubagentSettingsNotice();
71
- if (!notice) {
72
- readSubagentSettings();
73
- notice = consumeSubagentSettingsNotice();
74
- }
75
- if (notice) ctx.ui.notify(notice, "warning");
76
- });
77
-
78
- const statefulRuntime = registerStatefulSubagents(pi);
79
- registerSubagentConfigCommand(pi, statefulRuntime);
80
95
  }
96
+
81
97
  export { parsePositiveInteger } from "./execution.js";
82
98
  export { formatTokens, formatUsageStats } from "./render.js";
83
99
  export { buildPiArgs } from "./runner.js";
84
100
  export {
85
101
  inspectCompletionDeliverySettings,
102
+ inspectDelegationWorkflowSettings,
86
103
  normalizeAgentSettings,
87
104
  normalizeSubagentSettings,
88
105
  readSubagentSettings,
@@ -93,4 +110,5 @@ export {
93
110
  uniqueToolNames,
94
111
  updateAgentToolsSetting,
95
112
  updateCompletionDeliverySetting,
113
+ updateDelegationWorkflowSetting,
96
114
  } from "./settings.js";
package/src/workspace.ts CHANGED
@@ -27,10 +27,10 @@ export class WorkspaceManager {
27
27
  if (relativeCwd.startsWith("..") || path.isAbsolute(relativeCwd)) {
28
28
  throw new Error("Subagent cwd is outside the Git repository");
29
29
  }
30
- const status = (
31
- await execFileAsync("git", ["-C", repositoryRoot, "status", "--porcelain"])
32
- ).stdout;
33
- if (status.trim()) throw new Error("Isolated subagent workspace requires a clean Git repository");
30
+ const status = (await execFileAsync("git", ["-C", repositoryRoot, "status", "--porcelain"]))
31
+ .stdout;
32
+ if (status.trim())
33
+ throw new Error("Isolated subagent workspace requires a clean Git repository");
34
34
  const rootPath = await fs.promises.mkdtemp(path.join(os.tmpdir(), WORKTREE_PREFIX));
35
35
  let registered = false;
36
36
  try {
@@ -86,8 +86,9 @@ export class WorkspaceManager {
86
86
  workspace.rootPath,
87
87
  ]).catch(async () => {
88
88
  await fs.promises.rm(workspace.rootPath, { recursive: true, force: true });
89
- await execFileAsync("git", ["-C", workspace.repositoryRoot, "worktree", "prune"])
90
- .catch(() => undefined);
89
+ await execFileAsync("git", ["-C", workspace.repositoryRoot, "worktree", "prune"]).catch(
90
+ () => undefined,
91
+ );
91
92
  });
92
93
  await fs.promises.rm(`${workspace.rootPath}.owner`, { force: true });
93
94
  }