pi-fast-subagent 0.9.1 → 0.9.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # pi-fast-subagent
2
2
 
3
- In-process subagent delegation for [pi](https://github.com/badlogic/pi-mono).
3
+ In-process subagent delegation for [pi](https://github.com/badlogic/pi-mono) with max visibility.
4
4
 
5
5
  Runs subagents with `createAgentSession()` in same process instead of spawning `pi` subprocesses. This removes subprocess cold-start and reuses pi auth/model registry.
6
6
 
@@ -289,7 +289,7 @@ Goal: keep this extension **small and focused** — aligned with pi's philosophy
289
289
 
290
290
  - Async/background isolation not supported in-process
291
291
  - Git worktree isolation not supported
292
- - Nested subagent depth limited to 2 by default
292
+ - Nested subagent spawning disabled by default (`maxDepth: 0`); opt in per agent via frontmatter
293
293
 
294
294
  ## Tool Reference
295
295
 
package/agents/scout.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: scout
3
3
  description: Explores codebases, maps structure, traces data flow, answers how things work across many files
4
- model: anthropic/claude-haiku-4-5
4
+ model: openai-codex/gpt-5.4-mini
5
5
 
6
6
  # tools: which tools this agent can use.
7
7
  # (omit) → all tools: builtins + every parent extension (default)
@@ -36,5 +36,6 @@ Output style:
36
36
  - use sections
37
37
  - include file paths
38
38
  - include short bullets
39
- - mention notable patterns, risks, and coupling
40
- - do not propose code changes unless asked
39
+ - describe what code does, not whether it is good or bad
40
+ - do not rate, review, or analyze quality
41
+ - do not propose code changes
package/index.ts CHANGED
@@ -49,6 +49,7 @@ function refreshBgStatus(): void {
49
49
  _setBgStatus?.(running.length > 0 ? `⧗ ${running.length} bg agent${running.length > 1 ? "s" : ""}` : undefined);
50
50
  }
51
51
 
52
+
52
53
  // ─── Foreground detach registry ─────────────────────────────────────────────
53
54
 
54
55
  interface ForegroundDetachEntry {
@@ -417,11 +418,13 @@ export default function (pi: ExtensionAPI) {
417
418
  const { agent, error } = findAgent(params.agent);
418
419
  if (error || !agent) return { content: [{ type: "text", text: error ?? "Not found" }] };
419
420
 
421
+ const effectiveModel = params.model;
422
+
420
423
  if (params.background) {
421
424
  const bgAbort = new AbortController();
422
425
  const handle: BackgroundHandleLike = { abort: () => bgAbort.abort() };
423
426
  const resultPromise: Promise<BackgroundJobResult> = runAgent(
424
- agent, params.task, cwd, params.model, bgAbort.signal, undefined,
427
+ agent, params.task, cwd, effectiveModel, bgAbort.signal, undefined,
425
428
  ).then((r) => ({ summary: r.output, exitCode: r.exitCode, error: r.error, model: r.model }));
426
429
  const jobId = getBgManager().adoptHandle(agent.name, params.task, cwd, handle, resultPromise);
427
430
  return { content: [{ type: "text", text: `Background job started: ${jobId}\nTo check status, ask me to poll job ${jobId}.` }] };
@@ -441,7 +444,7 @@ export default function (pi: ExtensionAPI) {
441
444
  : undefined;
442
445
 
443
446
  const agentRunPromise: Promise<RunResult> = runAgent(
444
- agent, params.task, cwd, params.model, agentAbort.signal, wrappedOnUpdate,
447
+ agent, params.task, cwd, effectiveModel, agentAbort.signal, wrappedOnUpdate,
445
448
  );
446
449
 
447
450
  const bgResultPromise: Promise<BackgroundJobResult> = agentRunPromise
@@ -546,7 +549,15 @@ export default function (pi: ExtensionAPI) {
546
549
  parallelAgents[i]!.responseText = (partial.content?.[0] as any)?.text || parallelAgents[i]!.responseText;
547
550
  emitParallel(true);
548
551
  };
549
- const result = await runAgent(agent, t.task, t.cwd ?? cwd, t.model, signal, agentOnUpdate, parentDepth);
552
+ const result = await runAgent(
553
+ agent,
554
+ t.task,
555
+ t.cwd ?? cwd,
556
+ t.model,
557
+ signal,
558
+ agentOnUpdate,
559
+ parentDepth,
560
+ );
550
561
  parallelAgents[i]!.status = result.exitCode === 0 ? "done" : "error";
551
562
  parallelAgents[i]!.durMs = Date.now() - agentStart;
552
563
  parallelAgents[i]!.toolCalls = result.toolCalls;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-fast-subagent",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "In-process subagent delegation for pi with single, parallel, and background modes",
5
5
  "type": "module",
6
6
  "keywords": [
package/render.ts CHANGED
@@ -302,6 +302,16 @@ export function renderSubagentResult(
302
302
  promptSkipped?: number;
303
303
  responseLines?: string[];
304
304
  skipped?: number;
305
+ expandedWidth?: number;
306
+ expandedEventsLen?: number;
307
+ expandedLastEventTs?: number;
308
+ expandedTask?: string;
309
+ expandedAgentName?: string;
310
+ expandedToolCallsLen?: number;
311
+ expandedAgentTextLen?: number;
312
+ expandedBodyLines?: string[];
313
+ expandedFooterKey?: string;
314
+ expandedOutputLines?: string[];
305
315
  } = {};
306
316
 
307
317
  function renderExpandedChronological(width: number): string[] {
@@ -334,11 +344,22 @@ export function renderSubagentResult(
334
344
  }
335
345
  }
336
346
  } else {
337
- // Render events chronologically
347
+ // Render events chronologically. Coalesce text deltas to avoid one-line-per-token output.
338
348
  let lastWasText = false;
349
+ let textBuffer = "";
339
350
  const agentLabel = `${details.agentName ?? "Agent"}:`;
351
+
352
+ const flushTextBuffer = () => {
353
+ if (!textBuffer) return;
354
+ for (const line of textBuffer.split("\n")) {
355
+ for (const w of wrapLine(indent + line, width)) out.push(w);
356
+ }
357
+ textBuffer = "";
358
+ };
359
+
340
360
  for (const evt of events) {
341
361
  if (evt.type === "tool_start") {
362
+ if (lastWasText) flushTextBuffer();
342
363
  lastWasText = false;
343
364
  const call = `${evt.toolName}(${evt.argSummary})`;
344
365
  toolLineMap.set(evt.toolCallId, out.length);
@@ -348,10 +369,9 @@ export function renderSubagentResult(
348
369
  out.push(truncateToWidth(theme.fg("toolTitle", agentLabel), width, "..."));
349
370
  lastWasText = true;
350
371
  }
351
- for (const line of evt.text.split("\n")) {
352
- for (const w of wrapLine(indent + line, width)) out.push(w);
353
- }
372
+ textBuffer += evt.text;
354
373
  } else if (evt.type === "tool_end") {
374
+ if (lastWasText) flushTextBuffer();
355
375
  lastWasText = false;
356
376
  const toolLineIdx = toolLineMap.get(evt.toolCallId);
357
377
  const dur = evt.durMs != null
@@ -368,13 +388,27 @@ export function renderSubagentResult(
368
388
  }
369
389
  }
370
390
  }
391
+
392
+ if (lastWasText) flushTextBuffer();
371
393
  }
372
394
 
373
395
  return out;
374
396
  }
375
397
 
376
398
  return {
377
- invalidate() { cache.width = undefined; },
399
+ invalidate() {
400
+ cache.width = undefined;
401
+ cache.expandedWidth = undefined;
402
+ cache.expandedEventsLen = undefined;
403
+ cache.expandedLastEventTs = undefined;
404
+ cache.expandedTask = undefined;
405
+ cache.expandedAgentName = undefined;
406
+ cache.expandedToolCallsLen = undefined;
407
+ cache.expandedAgentTextLen = undefined;
408
+ cache.expandedBodyLines = undefined;
409
+ cache.expandedFooterKey = undefined;
410
+ cache.expandedOutputLines = undefined;
411
+ },
378
412
  render(width: number): string[] {
379
413
  const out: string[] = [];
380
414
  const indent = " ";
@@ -382,15 +416,52 @@ export function renderSubagentResult(
382
416
  theme.fg("muted", `${indent}… (${count} more line${count === 1 ? "" : "s"})`);
383
417
 
384
418
  if (expanded) {
385
- // Expanded: render chronologically from events
386
- const expandedOut = renderExpandedChronological(width);
387
- expandedOut.push("");
419
+ const events = details.executionEvents || [];
420
+ const lastEventTs = events.length ? events[events.length - 1]!.timestamp : undefined;
421
+ const taskKey = details.task ?? "";
422
+ const agentKey = details.agentName ?? "";
423
+ const bodyCacheHit =
424
+ cache.expandedWidth === width
425
+ && cache.expandedEventsLen === events.length
426
+ && cache.expandedLastEventTs === lastEventTs
427
+ && cache.expandedTask === taskKey
428
+ && cache.expandedAgentName === agentKey
429
+ && cache.expandedToolCallsLen === toolCalls.length
430
+ && cache.expandedAgentTextLen === agentText.length
431
+ && Array.isArray(cache.expandedBodyLines);
432
+
433
+ let bodyLines: string[];
434
+ if (bodyCacheHit) {
435
+ bodyLines = cache.expandedBodyLines!;
436
+ } else {
437
+ bodyLines = renderExpandedChronological(width);
438
+ cache.expandedWidth = width;
439
+ cache.expandedEventsLen = events.length;
440
+ cache.expandedLastEventTs = lastEventTs;
441
+ cache.expandedTask = taskKey;
442
+ cache.expandedAgentName = agentKey;
443
+ cache.expandedToolCallsLen = toolCalls.length;
444
+ cache.expandedAgentTextLen = agentText.length;
445
+ cache.expandedBodyLines = bodyLines;
446
+ cache.expandedFooterKey = undefined;
447
+ cache.expandedOutputLines = undefined;
448
+ }
449
+
388
450
  const status = statusLine();
389
- if (status) expandedOut.push(truncateToWidth(status, width, "..."));
390
- if (details.running && !details.backgroundJobId) {
391
- expandedOut.push(truncateToWidth(theme.fg("dim", "Ctrl+Shift+B: move to background"), width, "..."));
451
+ const bgHint = details.running && !details.backgroundJobId
452
+ ? truncateToWidth(theme.fg("dim", "Ctrl+Shift+B: move to background"), width, "...")
453
+ : "";
454
+ const footerKey = `${status}__${bgHint}`;
455
+
456
+ if (cache.expandedFooterKey !== footerKey || !Array.isArray(cache.expandedOutputLines)) {
457
+ const expandedOut = [...bodyLines, ""];
458
+ if (status) expandedOut.push(truncateToWidth(status, width, "..."));
459
+ if (bgHint) expandedOut.push(bgHint);
460
+ cache.expandedFooterKey = footerKey;
461
+ cache.expandedOutputLines = expandedOut;
392
462
  }
393
- return expandedOut;
463
+
464
+ return cache.expandedOutputLines!;
394
465
  }
395
466
 
396
467
  // Collapsed view
package/runner.ts CHANGED
@@ -114,6 +114,8 @@ export async function runAgent(
114
114
  });
115
115
  await allowUiPaint(coldLoader);
116
116
 
117
+ const createPrevEnvDepth = process.env[DEPTH_ENV];
118
+ process.env[DEPTH_ENV] = String(depth + 1);
117
119
  const loaderLease = await pool.acquire(
118
120
  cwd,
119
121
  agentDir,
@@ -134,6 +136,8 @@ export async function runAgent(
134
136
  session = created.session;
135
137
  } catch (e) {
136
138
  loaderLease.release();
139
+ if (createPrevEnvDepth === undefined) delete process.env[DEPTH_ENV];
140
+ else process.env[DEPTH_ENV] = createPrevEnvDepth;
137
141
  return {
138
142
  output: "",
139
143
  exitCode: 1,
@@ -142,6 +146,8 @@ export async function runAgent(
142
146
  usage: { input: 0, output: 0, cost: 0, turns: 0 },
143
147
  };
144
148
  }
149
+ if (createPrevEnvDepth === undefined) delete process.env[DEPTH_ENV];
150
+ else process.env[DEPTH_ENV] = createPrevEnvDepth;
145
151
 
146
152
  // Resolve and apply model
147
153
  const modelStr = modelOverride ?? agent.model;