pi-fast-subagent 0.4.0 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +73 -3
  2. package/index.ts +301 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -9,6 +9,7 @@ Runs subagents with `createAgentSession()` in same process instead of spawning `
9
9
  - Single mode: `{ agent, task }`
10
10
  - Parallel mode: `{ tasks: [...] }`
11
11
  - Background mode: `{ agent, task, background: true }` — fire-and-forget with poll/cancel
12
+ - Slash commands for background job status + cancellation via selector UI
12
13
  - Per-call model override
13
14
  - User + project agent discovery
14
15
  - Project agents override user agents
@@ -62,22 +63,64 @@ You are code exploration specialist. Read relevant files, trace data flow, summa
62
63
 
63
64
  ## Slash Commands
64
65
 
65
- ### `/agent`
66
+ ### `/fast-subagent:agent`
66
67
 
67
68
  List all available agents:
68
69
 
69
70
  ```
70
- /agent
71
+ /fast-subagent:agent
71
72
  ```
72
73
 
73
74
  Show details for a specific agent (description, file path, model, tools, system prompt):
74
75
 
75
76
  ```
76
- /agent scout
77
+ /fast-subagent:agent scout
77
78
  ```
78
79
 
79
80
  Tab-completion is supported for agent names.
80
81
 
82
+ ### `/fast-subagent:bg`
83
+
84
+ Detach running foreground subagent to background:
85
+
86
+ ```
87
+ /fast-subagent:bg fg_ab12cd34
88
+ ```
89
+
90
+ Omit job id to list active foreground jobs:
91
+
92
+ ```
93
+ /fast-subagent:bg
94
+ ```
95
+
96
+ ### `/fast-subagent:bg-status`
97
+
98
+ Show active background subagents in selector UI. Arrow keys move selection. Enter shows full details for selected job.
99
+
100
+ ```
101
+ /fast-subagent:bg-status
102
+ ```
103
+
104
+ Show details for specific background job:
105
+
106
+ ```
107
+ /fast-subagent:bg-status sa_ab12cd34
108
+ ```
109
+
110
+ ### `/fast-subagent:bg-cancel`
111
+
112
+ Cancel running background subagent. Omit job id to open selector UI, then choose job with arrow keys.
113
+
114
+ ```
115
+ /fast-subagent:bg-cancel
116
+ ```
117
+
118
+ Cancel specific background job directly:
119
+
120
+ ```
121
+ /fast-subagent:bg-cancel sa_ab12cd34
122
+ ```
123
+
81
124
  ## Usage
82
125
 
83
126
  ### List agents
@@ -126,6 +169,33 @@ subagent({
126
169
  })
127
170
  ```
128
171
 
172
+ ### Background (fire-and-forget)
173
+
174
+ ```js
175
+ // Dispatch — returns job ID immediately
176
+ subagent({ agent: "scout", task: "Explore src", background: true })
177
+ // → { jobId: "sa_ab12cd34", status: "running" }
178
+
179
+ // Poll — check result / progress
180
+ subagent({ action: "poll", jobId: "sa_ab12cd34" })
181
+
182
+ // Cancel
183
+ subagent({ action: "cancel", jobId: "sa_ab12cd34" })
184
+
185
+ // List all background jobs
186
+ subagent({ action: "status" })
187
+
188
+ // Detach a running foreground job to background
189
+ subagent({ action: "detach", jobId: "fg_ab12cd34" })
190
+ ```
191
+
192
+ ## Roadmap
193
+
194
+ Goal: keep this extension **small and focused** — aligned with pi's philosophy of minimal, composable tooling. No feature creep. Every addition must earn its place.
195
+
196
+ - **UI/UX polish** — improve visibility of running subagents: clearer status lines, better progress feedback, agent name + task always visible during execution
197
+ - **Background agents** — ala claude code
198
+
129
199
  ## Notes
130
200
 
131
201
  - Async/background isolation not supported in-process
package/index.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  * Agent .md files are compatible with pi-subagents frontmatter format.
9
9
  */
10
10
 
11
+ import { randomUUID } from "node:crypto";
11
12
  import type {
12
13
  AgentToolResult,
13
14
  AgentToolUpdateCallback,
@@ -16,9 +17,9 @@ import type {
16
17
  ToolRenderResultOptions,
17
18
  } from "@mariozechner/pi-coding-agent";
18
19
  import { BackgroundJobManager } from "./background-job-manager.js";
19
- import type { BackgroundHandleLike, BackgroundJobResult } from "./background-types.js";
20
+ import type { BackgroundHandleLike, BackgroundJobResult, BackgroundSubagentJob } from "./background-types.js";
20
21
  import { Theme } from "@mariozechner/pi-coding-agent";
21
- import { truncateToWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
22
+ import { Key, truncateToWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
22
23
  import { truncateToVisualLines, keyHint } from "@mariozechner/pi-coding-agent";
23
24
  import {
24
25
  AuthStorage,
@@ -97,6 +98,8 @@ function summarizeToolArgs(toolName: unknown, toolInput: unknown): string {
97
98
  let _authStorage: ReturnType<typeof AuthStorage.create> | null = null;
98
99
  let _modelRegistry: ReturnType<typeof ModelRegistry.create> | null = null;
99
100
  let _bgManager: BackgroundJobManager | null = null;
101
+ let _onBgJobComplete: ((job: BackgroundSubagentJob) => void) | null = null;
102
+ let _setBgStatus: ((text: string | undefined) => void) | null = null;
100
103
 
101
104
  function getAuth() {
102
105
  if (!_authStorage) _authStorage = AuthStorage.create();
@@ -105,10 +108,26 @@ function getAuth() {
105
108
  }
106
109
 
107
110
  function getBgManager(): BackgroundJobManager {
108
- if (!_bgManager) _bgManager = new BackgroundJobManager();
111
+ if (!_bgManager) _bgManager = new BackgroundJobManager({
112
+ onJobComplete: (job) => _onBgJobComplete?.(job),
113
+ });
109
114
  return _bgManager;
110
115
  }
111
116
 
117
+ function refreshBgStatus(): void {
118
+ const running = getBgManager().getRunningJobs();
119
+ _setBgStatus?.(running.length > 0 ? `⧗ ${running.length} bg agent${running.length > 1 ? "s" : ""}` : undefined);
120
+ }
121
+
122
+ // ─── Foreground detach registry ───────────────────────────────────────────────
123
+
124
+ interface ForegroundDetachEntry {
125
+ agentName: string;
126
+ task: string;
127
+ detach: () => string; // returns bg job id
128
+ }
129
+ const _fgJobs = new Map<string, ForegroundDetachEntry>();
130
+
112
131
  // ─── In-process runner ───────────────────────────────────────────────────────
113
132
 
114
133
  const MAX_DEPTH = 2;
@@ -150,6 +169,7 @@ interface SubagentDetails {
150
169
  running: boolean;
151
170
  elapsedMs?: number;
152
171
  model?: string;
172
+ backgroundJobId?: string;
153
173
  toolCalls: ToolCallEntry[];
154
174
  }
155
175
 
@@ -162,6 +182,26 @@ function formatDuration(ms: number): string {
162
182
  return m > 0 ? `${m}m ${rem}s` : `${rem}s`;
163
183
  }
164
184
 
185
+ function summarizeTask(task: string, max = 60): string {
186
+ return task.length > max ? task.slice(0, max - 3) + "..." : task;
187
+ }
188
+
189
+ function formatBgJobSummary(job: BackgroundSubagentJob, now = Date.now()): string {
190
+ const dur = job.completedAt ? formatDuration(job.completedAt - job.startedAt) : formatDuration(now - job.startedAt);
191
+ return `${job.id} [${job.status}] ${job.agentName} · ${dur} · ${summarizeTask(job.task)}`;
192
+ }
193
+
194
+ function formatBgJobDetails(job: BackgroundSubagentJob, now = Date.now()): string {
195
+ const dur = job.completedAt ? formatDuration(job.completedAt - job.startedAt) : formatDuration(now - job.startedAt);
196
+ const lines = [`${job.id} [${job.status}] ${job.agentName} · ${dur}`, `Task: ${job.task}`];
197
+ if (job.model) lines.push(`Model: ${job.model}`);
198
+ if (job.status === "completed") lines.push(`\nResult:\n${job.resultSummary ?? "(no output)"}`);
199
+ if (job.status === "failed") lines.push(`\nError: ${job.error ?? "(unknown)"}`);
200
+ if (job.status === "cancelled") lines.push("\nCancelled.");
201
+ if (job.status === "running") lines.push("\nStill running.");
202
+ return lines.join("\n");
203
+ }
204
+
165
205
  // Module-level depth counter — avoids process.env race conditions in parallel mode
166
206
  let _currentDepth = 0;
167
207
 
@@ -461,8 +501,9 @@ const SubagentParams = Type.Object({
461
501
  Type.Literal("status"),
462
502
  Type.Literal("poll"),
463
503
  Type.Literal("cancel"),
504
+ Type.Literal("detach"),
464
505
  ],
465
- { description: "'list'/'get' for agents, 'status' for bg jobs, 'poll'/'cancel' for a specific job" },
506
+ { description: "'list'/'get' for agents, 'status' for bg jobs, 'poll'/'cancel' for a specific job, 'detach' to move a foreground job to background" },
466
507
  ),
467
508
  ),
468
509
  agentScope: Type.Optional(
@@ -476,9 +517,65 @@ const SubagentParams = Type.Object({
476
517
  // ─── Extension entry point ────────────────────────────────────────────────────
477
518
 
478
519
  export default function (pi: ExtensionAPI) {
520
+ // ─── Status keys ────────────────────────────────────────────────────────────────────
521
+ const BG_STATUS_KEY = "fast-subagent-bg";
522
+ const FG_STATUS_KEY = "fast-subagent-fg";
523
+
524
+ // ─── Background job lifecycle ─────────────────────────────────────────────────────
525
+ _onBgJobComplete = (job) => {
526
+ refreshBgStatus();
527
+ const elapsed = job.completedAt ? ((job.completedAt - job.startedAt) / 1000).toFixed(1) : "?";
528
+ const statusEmoji = job.status === "completed" ? "✓" : "✗";
529
+ const taskPreview = job.task.length > 80 ? `${job.task.slice(0, 80)}…` : job.task;
530
+ const output = job.status === "completed"
531
+ ? (job.resultSummary ?? "(no output)")
532
+ : `Error: ${job.error ?? "unknown"}`;
533
+ const modelInfo = job.model ? ` · ${job.model}` : "";
534
+ pi.sendUserMessage(
535
+ [
536
+ `**Background subagent ${statusEmoji}: ${job.id}** (${job.agentName}, ${elapsed}s${modelInfo})`,
537
+ `> ${taskPreview}`,
538
+ ``,
539
+ output,
540
+ ].join("\n"),
541
+ { deliverAs: "followUp" },
542
+ );
543
+ };
544
+
545
+ pi.on("session_start", async (_event, ctx) => {
546
+ _setBgStatus = (text) => ctx.ui.setStatus(BG_STATUS_KEY, text);
547
+ });
548
+
549
+ pi.on("session_shutdown", async () => {
550
+ getBgManager().shutdown();
551
+ _bgManager = null;
552
+ _setBgStatus = null;
553
+ });
554
+
555
+ // ─── Ctrl+Shift+B — move foreground subagent to background ─────────────────────────
556
+ pi.registerShortcut(Key.ctrlShift("b"), {
557
+ description: "Move foreground subagent to background",
558
+ handler: async (ctx) => {
559
+ const entry = [..._fgJobs.values()][0];
560
+ if (!entry) {
561
+ ctx.ui.notify("No foreground subagent running.", "info");
562
+ return;
563
+ }
564
+ try {
565
+ const bgJobId = entry.detach();
566
+ ctx.ui.notify(
567
+ `Moved ${entry.agentName} to background as ${bgJobId}. Completion will be announced automatically.`,
568
+ "info",
569
+ );
570
+ } catch (e) {
571
+ ctx.ui.notify(e instanceof Error ? e.message : String(e), "error");
572
+ }
573
+ },
574
+ });
575
+
479
576
  // ─── /agent slash command ─────────────────────────────────────────────────
480
- pi.registerCommand("agent", {
481
- description: "List available subagents. Usage: /agent [name] — show details for a specific agent.",
577
+ pi.registerCommand("fast-subagent:agent", {
578
+ description: "List available subagents. Usage: /fast-subagent:agent [name] — show details for a specific agent.",
482
579
  getArgumentCompletions(prefix: string) {
483
580
  const agents = discoverAgents(process.cwd());
484
581
  return agents
@@ -537,11 +634,132 @@ export default function (pi: ExtensionAPI) {
537
634
  }
538
635
  }
539
636
  lines.push("");
540
- lines.push("Tip: /agent <name> for details · Add .md files to .pi/agents/ to create new agents");
637
+ lines.push("Tip: /fast-subagent:agent <name> for details · Add .md files to .pi/agents/ to create new agents");
541
638
  ctx.ui.notify(lines.join("\n"), "info");
542
639
  },
543
640
  });
544
641
 
642
+ // ─── /bg slash command ────────────────────────────────────────────────────
643
+ pi.registerCommand("fast-subagent:bg", {
644
+ description: "Move a running foreground subagent to background. Shortcut: Ctrl+Shift+B. Usage: /fast-subagent:bg [fg-job-id] — omit ID to list active foreground jobs.",
645
+ getArgumentCompletions(_prefix: string) {
646
+ return [..._fgJobs.keys()].map((id) => ({ value: id, label: id }));
647
+ },
648
+ async handler(args: string, ctx) {
649
+ const id = args.trim();
650
+ if (!id) {
651
+ if (_fgJobs.size === 0) {
652
+ ctx.ui.notify("No active foreground subagent jobs.", "info");
653
+ return;
654
+ }
655
+ const lines = ["Active foreground jobs (use /fast-subagent:bg <id> to detach):"];
656
+ for (const [fgId, entry] of _fgJobs) {
657
+ lines.push(` ${fgId} ${entry.agentName}: ${summarizeTask(entry.task)}`);
658
+ }
659
+ ctx.ui.notify(lines.join("\n"), "info");
660
+ return;
661
+ }
662
+ const entry = _fgJobs.get(id);
663
+ if (!entry) {
664
+ ctx.ui.notify(`Foreground job "${id}" not found (already done or invalid).`, "warning");
665
+ return;
666
+ }
667
+ const bgJobId = entry.detach();
668
+ ctx.ui.notify(
669
+ `Moved to background: ${bgJobId}\nTo check status, ask me to poll job ${bgJobId}.`,
670
+ "info",
671
+ );
672
+ },
673
+ });
674
+
675
+ // ─── /bg-status slash command ─────────────────────────────────────────────
676
+ pi.registerCommand("fast-subagent:bg-status", {
677
+ description: "Show active background subagents. Usage: /fast-subagent:bg-status [sa-job-id] — omit ID to open selector.",
678
+ getArgumentCompletions(prefix: string) {
679
+ return getBgManager().getAllJobs()
680
+ .filter((job) => job.id.startsWith(prefix))
681
+ .map((job) => ({ value: job.id, label: formatBgJobSummary(job) }));
682
+ },
683
+ async handler(args: string, ctx) {
684
+ const id = args.trim();
685
+ if (id) {
686
+ const job = getBgManager().getJob(id);
687
+ if (!job) {
688
+ ctx.ui.notify(`Background job "${id}" not found.`, "warning");
689
+ return;
690
+ }
691
+ ctx.ui.notify(formatBgJobDetails(job), "info");
692
+ return;
693
+ }
694
+
695
+ const jobs = getBgManager().getRunningJobs().sort((a, b) => b.startedAt - a.startedAt);
696
+ if (jobs.length === 0) {
697
+ ctx.ui.notify("No active background subagent jobs.", "info");
698
+ return;
699
+ }
700
+
701
+ const options = jobs.map((job) => formatBgJobSummary(job));
702
+ const selected = await ctx.ui.select("Active background subagents", options);
703
+ if (!selected) return;
704
+
705
+ const jobId = selected.split(" ")[0] ?? "";
706
+ const job = getBgManager().getJob(jobId);
707
+ if (!job) {
708
+ ctx.ui.notify(`Background job "${jobId}" not found.`, "warning");
709
+ return;
710
+ }
711
+ ctx.ui.notify(formatBgJobDetails(job), "info");
712
+ },
713
+ });
714
+
715
+ // ─── /bg-cancel slash command ─────────────────────────────────────────────
716
+ pi.registerCommand("fast-subagent:bg-cancel", {
717
+ description: "Cancel running background subagent. Usage: /fast-subagent:bg-cancel [sa-job-id] — omit ID to choose with arrow keys.",
718
+ getArgumentCompletions(prefix: string) {
719
+ return getBgManager().getRunningJobs()
720
+ .filter((job) => job.id.startsWith(prefix))
721
+ .map((job) => ({ value: job.id, label: formatBgJobSummary(job) }));
722
+ },
723
+ async handler(args: string, ctx) {
724
+ let jobId = args.trim();
725
+
726
+ if (!jobId) {
727
+ const jobs = getBgManager().getRunningJobs().sort((a, b) => b.startedAt - a.startedAt);
728
+ if (jobs.length === 0) {
729
+ ctx.ui.notify("No running background subagent jobs to cancel.", "info");
730
+ return;
731
+ }
732
+
733
+ const options = jobs.map((job) => formatBgJobSummary(job));
734
+ const selected = await ctx.ui.select("Cancel background subagent", options);
735
+ if (!selected) return;
736
+ jobId = selected.split(" ")[0] ?? "";
737
+ }
738
+
739
+ const job = getBgManager().getJob(jobId);
740
+ if (!job) {
741
+ ctx.ui.notify(`Background job "${jobId}" not found.`, "warning");
742
+ return;
743
+ }
744
+ if (job.status !== "running") {
745
+ ctx.ui.notify(`Background job "${jobId}" already ${job.status}.`, "info");
746
+ return;
747
+ }
748
+
749
+ const confirmed = await ctx.ui.confirm(
750
+ "Cancel background subagent?",
751
+ `${formatBgJobSummary(job)}\n\nTask:\n${job.task}`,
752
+ );
753
+ if (!confirmed) return;
754
+
755
+ const result = getBgManager().cancel(jobId);
756
+ const msg = result === "cancelled" ? `Background job "${jobId}" cancelled.`
757
+ : result === "already_done" ? `Background job "${jobId}" already completed.`
758
+ : `Background job "${jobId}" not found.`;
759
+ ctx.ui.notify(msg, result === "cancelled" ? "info" : "warning");
760
+ },
761
+ });
762
+
545
763
  pi.registerTool({
546
764
  name: "subagent",
547
765
  label: "Subagent",
@@ -633,6 +851,7 @@ export default function (pi: ExtensionAPI) {
633
851
  }
634
852
 
635
853
  function statusLine(): string {
854
+ if (details.backgroundJobId) return `moved to background · ${details.backgroundJobId}`;
636
855
  if (details.running) {
637
856
  const parts: string[] = ["running"];
638
857
  if (details.usage?.turns) parts.push(`${details.usage.turns} turn${details.usage.turns > 1 ? "s" : ""}`);
@@ -720,6 +939,8 @@ export default function (pi: ExtensionAPI) {
720
939
  : "";
721
940
  const statusWithHint = [status, expandHint].filter(Boolean).join(" ");
722
941
  if (statusWithHint) out.push(truncateToWidth(statusWithHint, width, "..."));
942
+ if (details.running && !details.backgroundJobId)
943
+ out.push(truncateToWidth(theme.fg("dim", "Ctrl+Shift+B: move to background"), width, "..."));
723
944
 
724
945
  return out;
725
946
  },
@@ -803,6 +1024,15 @@ export default function (pi: ExtensionAPI) {
803
1024
  return { content: [{ type: "text", text: msg }] };
804
1025
  }
805
1026
 
1027
+ // ── Foreground → background detach ────────────────────────────────────────
1028
+ if (params.action === "detach") {
1029
+ if (!params.jobId) return { content: [{ type: "text", text: "Provide jobId (fg_xxxxx) to detach." }] };
1030
+ const fgEntry = _fgJobs.get(params.jobId);
1031
+ if (!fgEntry) return { content: [{ type: "text", text: `Foreground job "${params.jobId}" not found (already completed or invalid).` }] };
1032
+ const bgJobId = fgEntry.detach();
1033
+ return { content: [{ type: "text", text: `Moved to background: ${bgJobId}\nTo check status, ask me to poll job ${bgJobId}.` }] };
1034
+ }
1035
+
806
1036
  // ── Single mode ───────────────────────────────────────────────────────────
807
1037
  if (params.agent && params.task) {
808
1038
  const { agent, error } = findAgent(params.agent);
@@ -816,18 +1046,74 @@ export default function (pi: ExtensionAPI) {
816
1046
  agent, params.task, cwd, params.model, bgAbort.signal, undefined
817
1047
  ).then((r) => ({ summary: r.output, exitCode: r.exitCode, error: r.error, model: r.model }));
818
1048
  const jobId = getBgManager().adoptHandle(agent.name, params.task, cwd, handle, resultPromise);
819
- return { content: [{ type: "text", text: `Background job started: ${jobId}\nCheck progress: subagent({ action: "poll", jobId: "${jobId}" })` }] };
1049
+ return { content: [{ type: "text", text: `Background job started: ${jobId}\nTo check status, ask me to poll job ${jobId}.` }] };
820
1050
  }
821
1051
 
822
- const result = await runAgent(
823
- agent,
824
- params.task,
825
- cwd,
826
- params.model,
827
- signal,
828
- onUpdate,
1052
+ // Foreground run with detach support
1053
+ const fgId = `fg_${randomUUID().slice(0, 8)}`;
1054
+ const agentAbort = new AbortController();
1055
+ const forwardAbort = () => agentAbort.abort();
1056
+ signal?.addEventListener("abort", forwardAbort, { once: true });
1057
+
1058
+ let detachResolveFn: ((bgJobId: string) => void) | null = null;
1059
+ const detachPromise = new Promise<string>((resolve) => { detachResolveFn = resolve; });
1060
+
1061
+ // Wrap onUpdate so detach can stop forwarding updates to the parent
1062
+ // agent's listener (which becomes invalid once execute() returns).
1063
+ let forwardUpdates = true;
1064
+ const wrappedOnUpdate: OnUpdate | undefined = onUpdate
1065
+ ? (partial) => { if (forwardUpdates) onUpdate(partial); }
1066
+ : undefined;
1067
+
1068
+ const agentRunPromise: Promise<RunResult> = runAgent(
1069
+ agent, params.task, cwd, params.model, agentAbort.signal, wrappedOnUpdate,
829
1070
  );
830
1071
 
1072
+ // Derived promise for the bg manager (used only if we detach)
1073
+ const bgResultPromise: Promise<BackgroundJobResult> = agentRunPromise
1074
+ .then((r) => ({ summary: r.output, exitCode: r.exitCode, error: r.error, model: r.model }));
1075
+
1076
+ _fgJobs.set(fgId, {
1077
+ agentName: agent.name,
1078
+ task: params.task,
1079
+ detach: () => {
1080
+ forwardUpdates = false;
1081
+ signal?.removeEventListener("abort", forwardAbort);
1082
+ const bgHandle: BackgroundHandleLike = { abort: () => agentAbort.abort() };
1083
+ const bgJobId = getBgManager().adoptHandle(agent.name, params.task, cwd, bgHandle, bgResultPromise);
1084
+ refreshBgStatus();
1085
+ detachResolveFn?.(bgJobId);
1086
+ return bgJobId;
1087
+ },
1088
+ });
1089
+
1090
+ ctx.ui.setStatus(FG_STATUS_KEY, `${agent.name} running · Ctrl+Shift+B to move to background`);
1091
+
1092
+ let runResult: RunResult | null = null;
1093
+ const outcome = await Promise.race([
1094
+ agentRunPromise.then((r) => { runResult = r; return "done" as const; }),
1095
+ detachPromise.then(() => "detached" as const),
1096
+ ]).finally(() => {
1097
+ _fgJobs.delete(fgId);
1098
+ signal?.removeEventListener("abort", forwardAbort);
1099
+ ctx.ui.setStatus(FG_STATUS_KEY, undefined);
1100
+ });
1101
+
1102
+ if (outcome === "detached") {
1103
+ const bgJobId = await detachPromise; // already resolved — instant
1104
+ return {
1105
+ content: [{ type: "text", text: `Moved to background: ${bgJobId}. Completion will be announced automatically.` }],
1106
+ details: {
1107
+ task: params.task,
1108
+ usage: { input: 0, output: 0, cost: 0, turns: 0 },
1109
+ running: false,
1110
+ backgroundJobId: bgJobId,
1111
+ toolCalls: [],
1112
+ } satisfies SubagentDetails,
1113
+ };
1114
+ }
1115
+
1116
+ const result = runResult!;
831
1117
  return {
832
1118
  content: [{ type: "text", text: getFinalText(result) }],
833
1119
  details: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-fast-subagent",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "In-process subagent delegation for pi with single, parallel, and background modes",
5
5
  "type": "module",
6
6
  "keywords": [