@leing2021/super-pi 0.22.0 → 0.22.1

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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +148 -361
  3. package/THIRD-PARTY-NOTICES.md +35 -0
  4. package/extensions/subagent/__tests__/async-job-tracker.test.ts +21 -0
  5. package/extensions/subagent/__tests__/execution-activity.test.ts +15 -0
  6. package/extensions/subagent/__tests__/parallel-render-stress.test.ts +82 -0
  7. package/extensions/subagent/__tests__/render-dedup.test.ts +98 -0
  8. package/extensions/subagent/__tests__/render-widget.test.ts +28 -0
  9. package/extensions/subagent/__tests__/throttle.test.ts +90 -0
  10. package/extensions/subagent/agent-management.ts +3 -2
  11. package/extensions/subagent/agent-manager-chain-detail.ts +3 -2
  12. package/extensions/subagent/agent-manager-detail.ts +3 -2
  13. package/extensions/subagent/agent-manager-edit.ts +3 -2
  14. package/extensions/subagent/agent-manager-list.ts +3 -2
  15. package/extensions/subagent/agent-manager-parallel.ts +3 -2
  16. package/extensions/subagent/agent-manager.ts +3 -2
  17. package/extensions/subagent/agent-scope.ts +3 -2
  18. package/extensions/subagent/agent-selection.ts +3 -2
  19. package/extensions/subagent/agent-serializer.ts +3 -2
  20. package/extensions/subagent/agent-templates.ts +3 -2
  21. package/extensions/subagent/agents.ts +3 -2
  22. package/extensions/subagent/artifacts.ts +3 -2
  23. package/extensions/subagent/async-execution.ts +3 -2
  24. package/extensions/subagent/async-job-tracker.ts +28 -4
  25. package/extensions/subagent/async-status.ts +3 -2
  26. package/extensions/subagent/chain-clarify.ts +3 -2
  27. package/extensions/subagent/chain-execution.ts +3 -2
  28. package/extensions/subagent/chain-serializer.ts +3 -2
  29. package/extensions/subagent/completion-dedupe.ts +3 -2
  30. package/extensions/subagent/doctor.ts +3 -2
  31. package/extensions/subagent/execution.ts +13 -3
  32. package/extensions/subagent/file-coalescer.ts +3 -2
  33. package/extensions/subagent/fork-context.ts +3 -2
  34. package/extensions/subagent/formatters.ts +3 -2
  35. package/extensions/subagent/frontmatter.ts +3 -2
  36. package/extensions/subagent/index.ts +3 -2
  37. package/extensions/subagent/intercom-bridge.ts +3 -2
  38. package/extensions/subagent/jsonl-writer.ts +3 -2
  39. package/extensions/subagent/model-fallback.ts +3 -2
  40. package/extensions/subagent/notify.ts +3 -2
  41. package/extensions/subagent/parallel-utils.ts +3 -2
  42. package/extensions/subagent/pi-args.ts +3 -2
  43. package/extensions/subagent/pi-spawn.ts +3 -2
  44. package/extensions/subagent/post-exit-stdio-guard.ts +3 -2
  45. package/extensions/subagent/prompt-template-bridge.ts +3 -2
  46. package/extensions/subagent/render-helpers.ts +3 -2
  47. package/extensions/subagent/render.ts +81 -9
  48. package/extensions/subagent/result-intercom.ts +3 -2
  49. package/extensions/subagent/result-watcher.ts +3 -2
  50. package/extensions/subagent/run-history.ts +3 -2
  51. package/extensions/subagent/run-status.ts +3 -2
  52. package/extensions/subagent/schemas.ts +3 -2
  53. package/extensions/subagent/session-tokens.ts +3 -2
  54. package/extensions/subagent/settings.ts +3 -2
  55. package/extensions/subagent/single-output.ts +3 -2
  56. package/extensions/subagent/skills.ts +3 -2
  57. package/extensions/subagent/slash-bridge.ts +3 -2
  58. package/extensions/subagent/slash-commands.ts +3 -2
  59. package/extensions/subagent/slash-live-state.ts +3 -2
  60. package/extensions/subagent/subagent-control.ts +3 -2
  61. package/extensions/subagent/subagent-executor.ts +19 -3
  62. package/extensions/subagent/subagent-prompt-runtime.ts +3 -2
  63. package/extensions/subagent/subagent-runner.ts +3 -2
  64. package/extensions/subagent/subagents-status.ts +3 -2
  65. package/extensions/subagent/text-editor.ts +3 -2
  66. package/extensions/subagent/throttle.ts +77 -0
  67. package/extensions/subagent/top-level-async.ts +3 -2
  68. package/extensions/subagent/types.ts +3 -2
  69. package/extensions/subagent/utils.ts +3 -2
  70. package/extensions/subagent/worktree.ts +3 -2
  71. package/package.json +3 -1
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  /**
4
5
  * Core execution logic for running subagents
5
6
  */
@@ -406,6 +407,7 @@ async function runSingleAttempt(
406
407
  };
407
408
 
408
409
  if (controlConfig.enabled) {
410
+ const activityIntervalMs = resolveActivityTimerInterval();
409
411
  activityTimer = setInterval(() => {
410
412
  if (processClosed || settled || detached) return;
411
413
  const now = Date.now();
@@ -413,7 +415,7 @@ async function runSingleAttempt(
413
415
  progress.durationMs = now - startTime;
414
416
  fireUpdate();
415
417
  }
416
- }, 1000);
418
+ }, activityIntervalMs);
417
419
  activityTimer.unref?.();
418
420
  }
419
421
 
@@ -575,6 +577,14 @@ async function runSingleAttempt(
575
577
  /**
576
578
  * Run a subagent synchronously (blocking until complete)
577
579
  */
580
+ /**
581
+ * Resolve activity timer interval based on concurrency level.
582
+ * Higher concurrency → longer interval to reduce render pressure.
583
+ */
584
+ export function resolveActivityTimerInterval(concurrentCount = 1): number {
585
+ return concurrentCount <= 1 ? 2000 : 3000;
586
+ }
587
+
578
588
  export async function runSync(
579
589
  runtimeCwd: string,
580
590
  agents: AgentConfig[],
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  interface TimerApi {
4
5
  setTimeout(handler: () => void, delayMs: number): unknown;
5
6
  clearTimeout(handle: unknown): void;
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  export type SubagentExecutionContext = "fresh" | "fork";
4
5
 
5
6
  interface ForkableSessionManagerStatic {
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  /**
4
5
  * Formatting utilities for display output
5
6
  */
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  export function parseFrontmatter(content: string): { frontmatter: Record<string, string>; body: string } {
4
5
  const frontmatter: Record<string, string> = {};
5
6
  const normalized = content.replace(/\r\n/g, "\n");
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  /**
4
5
  * Subagent Tool
5
6
  *
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import * as fs from "node:fs";
4
5
  import * as os from "node:os";
5
6
  import * as path from "node:path";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import * as fs from "node:fs";
4
5
 
5
6
  export interface DrainableSource {
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import type { Usage } from "./types.ts";
4
5
 
5
6
  export interface AvailableModelInfo {
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  /**
4
5
  * Subagent completion notifications.
5
6
  */
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  export interface RunnerSubagentStep {
4
5
  agent: string;
5
6
  task: string;
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import * as fs from "node:fs";
4
5
  import * as os from "node:os";
5
6
  import * as path from "node:path";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import * as fs from "node:fs";
4
5
  import { createRequire } from "node:module";
5
6
  import * as path from "node:path";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import type { ChildProcess } from "node:child_process";
4
5
 
5
6
  interface PostExitStdioGuardOptions {
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  export const PROMPT_TEMPLATE_SUBAGENT_REQUEST_EVENT = "prompt-template:subagent:request";
4
5
  export const PROMPT_TEMPLATE_SUBAGENT_STARTED_EVENT = "prompt-template:subagent:started";
5
6
  export const PROMPT_TEMPLATE_SUBAGENT_RESPONSE_EVENT = "prompt-template:subagent:response";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import type { Theme } from "@mariozechner/pi-coding-agent";
4
5
  import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
5
6
 
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  /**
4
5
  * Rendering functions for subagent results
5
6
  */
@@ -82,11 +83,24 @@ function truncLine(text: string, maxWidth: number): string {
82
83
  }
83
84
 
84
85
  const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
85
- const WIDGET_ANIMATION_MS = 80;
86
+ const WIDGET_ANIMATION_BASE_MS = 250;
87
+
88
+ /**
89
+ * Resolve widget animation interval based on running job count.
90
+ * Higher parallelism → lower refresh rate to prevent render storms.
91
+ */
92
+ export function resolveWidgetInterval(runningCount: number): number {
93
+ if (runningCount <= 2) return 250;
94
+ if (runningCount <= 4) return 400;
95
+ if (runningCount <= 6) return 600;
96
+ return 1000;
97
+ }
86
98
 
87
99
  let widgetTimer: ReturnType<typeof setInterval> | undefined;
88
100
  let latestWidgetCtx: ExtensionContext | undefined;
89
101
  let latestWidgetJobs: AsyncJobState[] = [];
102
+ let lastWidgetRenderMs = 0;
103
+ let lastWidgetContentHash = "";
90
104
 
91
105
  const resultAnimationTimers = new Map<ReturnType<typeof setInterval>, ResultAnimationContext["state"]>();
92
106
  const outputActivityCache = new Map<string, { checkedAt: number; text: string }>();
@@ -97,7 +111,7 @@ export interface ResultAnimationContext {
97
111
  }
98
112
 
99
113
  function spinnerFrame(): string {
100
- return SPINNER[Math.floor(Date.now() / WIDGET_ANIMATION_MS) % SPINNER.length]!;
114
+ return SPINNER[Math.floor(Date.now() / WIDGET_ANIMATION_BASE_MS) % SPINNER.length]!;
101
115
  }
102
116
 
103
117
  function resultIsRunning(result: AgentToolResult<Details>): boolean {
@@ -120,7 +134,7 @@ export function syncResultAnimation(result: AgentToolResult<Details>, context: R
120
134
  return;
121
135
  }
122
136
  if (context.state.subagentResultAnimationTimer) return;
123
- const timer = setInterval(() => context.invalidate(), WIDGET_ANIMATION_MS);
137
+ const timer = setInterval(() => context.invalidate(), WIDGET_ANIMATION_BASE_MS);
124
138
  timer.unref?.();
125
139
  context.state.subagentResultAnimationTimer = timer;
126
140
  resultAnimationTimers.set(timer, context.state);
@@ -352,21 +366,57 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
352
366
  return lines;
353
367
  }
354
368
 
369
+ function computeWidgetHash(jobs: AsyncJobState[]): string {
370
+ // Lightweight hash based on status and key mutable fields
371
+ return jobs.map((j) =>
372
+ `${j.asyncId}:${j.status}:${j.currentTool ?? ""}:${j.currentStep ?? ""}:${j.lastActivityAt ?? 0}:${j.activityState ?? ""}:${j.updatedAt ?? 0}`
373
+ ).join("|");
374
+ }
375
+
355
376
  function refreshAnimatedWidget(): void {
356
377
  if (!latestWidgetCtx?.hasUI || latestWidgetJobs.length === 0) return;
378
+ // Skip if called too recently (guard against overlapping timers)
379
+ const now = Date.now();
380
+ const runningCount = latestWidgetJobs.filter((j) => j.status === "running").length;
381
+ const minInterval = resolveWidgetInterval(runningCount);
382
+ if (now - lastWidgetRenderMs < minInterval * 0.8) return;
383
+ // Skip if content hasn't changed
384
+ const newHash = computeWidgetHash(latestWidgetJobs);
385
+ if (newHash === lastWidgetContentHash) return;
386
+ lastWidgetContentHash = newHash;
387
+ lastWidgetRenderMs = now;
357
388
  latestWidgetCtx.ui.setWidget(WIDGET_KEY, buildWidgetLines(latestWidgetJobs, latestWidgetCtx.ui.theme));
358
389
  latestWidgetCtx.ui.requestRender?.();
359
390
  }
360
391
 
392
+ /** Stop animation timer only — preserves jobs/ctx/hash for dedup */
393
+ function stopAnimationTimer(): void {
394
+ if (widgetTimer) {
395
+ clearInterval(widgetTimer);
396
+ widgetTimer = undefined;
397
+ }
398
+ }
399
+
361
400
  function ensureWidgetAnimation(): void {
362
401
  if (widgetTimer) return;
363
- widgetTimer = setInterval(() => {
402
+ const runningCount = latestWidgetJobs.filter((j) => j.status === "running").length;
403
+ let interval = resolveWidgetInterval(runningCount);
404
+ const tick = () => {
364
405
  if (!hasAnimatedWidgetJobs(latestWidgetJobs)) {
365
406
  stopWidgetAnimation();
366
407
  return;
367
408
  }
409
+ // Dynamically adjust interval if running count changed
410
+ const currentRunning = latestWidgetJobs.filter((j) => j.status === "running").length;
411
+ const currentInterval = resolveWidgetInterval(currentRunning);
412
+ if (currentInterval !== interval) {
413
+ stopAnimationTimer();
414
+ ensureWidgetAnimation();
415
+ return;
416
+ }
368
417
  refreshAnimatedWidget();
369
- }, WIDGET_ANIMATION_MS);
418
+ };
419
+ widgetTimer = setInterval(tick, interval);
370
420
  widgetTimer.unref?.();
371
421
  }
372
422
 
@@ -377,6 +427,7 @@ export function stopWidgetAnimation(): void {
377
427
  }
378
428
  latestWidgetCtx = undefined;
379
429
  latestWidgetJobs = [];
430
+ lastWidgetContentHash = "";
380
431
  outputActivityCache.clear();
381
432
  }
382
433
 
@@ -388,6 +439,14 @@ export function stopResultAnimations(): void {
388
439
  resultAnimationTimers.clear();
389
440
  }
390
441
 
442
+ /** @internal Test-only: reset module-level widget state */
443
+ export function _testResetWidgetState(): void {
444
+ stopWidgetAnimation();
445
+ stopResultAnimations();
446
+ lastWidgetRenderMs = 0;
447
+ lastWidgetContentHash = "";
448
+ }
449
+
391
450
  /**
392
451
  * Render the async jobs widget
393
452
  */
@@ -401,12 +460,25 @@ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void
401
460
  stopWidgetAnimation();
402
461
  return;
403
462
  }
463
+
464
+ // Content dedup: skip requestRender if widget data hasn't changed
465
+ const newHash = computeWidgetHash(jobs);
466
+ const contentChanged = newHash !== lastWidgetContentHash;
467
+ lastWidgetContentHash = newHash;
468
+
404
469
  latestWidgetCtx = ctx;
405
470
  latestWidgetJobs = [...jobs];
406
471
 
472
+ const hasAnimation = hasAnimatedWidgetJobs(jobs);
407
473
  ctx.ui.setWidget(WIDGET_KEY, buildWidgetLines(jobs, ctx.ui.theme));
408
- if (hasAnimatedWidgetJobs(jobs)) ensureWidgetAnimation();
409
- else stopWidgetAnimation();
474
+ if (contentChanged) {
475
+ ctx.ui.requestRender?.();
476
+ }
477
+ if (hasAnimation) ensureWidgetAnimation();
478
+ else {
479
+ stopAnimationTimer();
480
+ outputActivityCache.clear();
481
+ }
410
482
  }
411
483
 
412
484
  function renderSingleCompact(d: Details, r: Details["results"][number], theme: Theme): Component {
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import { randomUUID } from "node:crypto";
4
5
  import {
5
6
  type Details,
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import * as fs from "node:fs";
4
5
  import * as path from "node:path";
5
6
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import * as fs from "node:fs";
4
5
  import * as os from "node:os";
5
6
  import * as path from "node:path";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import * as fs from "node:fs";
4
5
  import * as path from "node:path";
5
6
  import type { AgentToolResult } from "@mariozechner/pi-agent-core";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  /**
4
5
  * TypeBox schemas for subagent tool parameters
5
6
  */
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import * as fs from "node:fs";
4
5
  import * as path from "node:path";
5
6
 
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  /**
4
5
  * Chain behavior, template resolution, and directory management
5
6
  */
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import * as fs from "node:fs";
4
5
  import * as path from "node:path";
5
6
 
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  /**
4
5
  * Skill resolution and caching for subagent extension
5
6
  */
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import type { AgentToolResult } from "@mariozechner/pi-agent-core";
4
5
  import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
5
6
  import type { SubagentParamsLike } from "./subagent-executor.js";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import { randomUUID } from "node:crypto";
4
5
  import * as fs from "node:fs";
5
6
  import * as path from "node:path";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import type { AgentToolResult } from "@mariozechner/pi-agent-core";
4
5
  import type { Message } from "@mariozechner/pi-ai";
5
6
  import type { SubagentParamsLike } from "./subagent-executor.ts";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import {
4
5
  type ActivityState,
5
6
  type ControlConfig,
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import { randomUUID } from "node:crypto";
4
5
  import * as fs from "node:fs";
5
6
  import * as path from "node:path";
@@ -34,6 +35,7 @@ import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBrid
34
35
  import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "./subagent-control.ts";
35
36
  import { finalizeSingleOutput, injectSingleOutputInstruction, resolveSingleOutputPath } from "./single-output.ts";
36
37
  import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "./utils.ts";
38
+ import { createThrottle, resolveThrottleInterval } from "./throttle.ts";
37
39
  import {
38
40
  buildSubagentResultIntercomPayload,
39
41
  deliverSubagentResultIntercomEvent,
@@ -966,7 +968,17 @@ function findDuplicateParallelOutputPath(input: {
966
968
  }
967
969
 
968
970
  async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
969
- return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
971
+ // Throttle onUpdate to prevent render storms during high parallelism
972
+ const throttleMs = resolveThrottleInterval(input.tasks.length);
973
+ const rawOnUpdate = input.onUpdate;
974
+ let throttledOnUpdate: ((typeof rawOnUpdate) & { flush(): void; dispose(): void }) | undefined;
975
+ if (rawOnUpdate && throttleMs > 0) {
976
+ throttledOnUpdate = createThrottle(rawOnUpdate, throttleMs);
977
+ input = { ...input, onUpdate: throttledOnUpdate };
978
+ }
979
+
980
+ try {
981
+ return await mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
970
982
  const behavior = input.behaviors[index];
971
983
  const effectiveSkills = behavior?.skills;
972
984
  const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index);
@@ -1056,6 +1068,10 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1056
1068
  }
1057
1069
  });
1058
1070
  });
1071
+ } finally {
1072
+ throttledOnUpdate?.flush();
1073
+ throttledOnUpdate?.dispose();
1074
+ }
1059
1075
  }
1060
1076
 
1061
1077
  async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps): Promise<AgentToolResult<Details>> {
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
4
5
 
5
6
  export const SUBAGENT_INHERIT_PROJECT_CONTEXT_ENV = "PI_SUBAGENT_INHERIT_PROJECT_CONTEXT";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import { spawn, spawnSync } from "node:child_process";
4
5
  import * as fs from "node:fs";
5
6
  import { createRequire } from "node:module";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import * as fs from "node:fs";
4
5
  import * as path from "node:path";
5
6
  import type { Theme } from "@mariozechner/pi-coding-agent";
@@ -1,5 +1,6 @@
1
- // Based on pi-subagents by Nico Bailon (https://github.com/nicobailon/pi-subagents)
2
- // MIT License
1
+ // SPDX-FileCopyrightText: 2025 Nico Bailon
2
+ // SPDX-License-Identifier: MIT
3
+ // Source: https://github.com/nicobailon/pi-subagents
3
4
  import { matchesKey } from "@mariozechner/pi-tui";
4
5
 
5
6
  export interface TextEditorState {