@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
@@ -0,0 +1,35 @@
1
+ # Third-Party Notices
2
+
3
+ ## pi-subagents
4
+
5
+ This software includes source code from the **pi-subagents** project.
6
+
7
+ - **Author**: Nico Bailon
8
+ - **Repository**: https://github.com/nicobailon/pi-subagents
9
+ - **License**: MIT
10
+
11
+ ```
12
+ MIT License
13
+
14
+ Copyright (c) 2025 Nico Bailon
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ of this software and associated documentation files (the "Software"), to deal
18
+ in the Software without restriction, including without limitation the rights
19
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ copies of the Software, and to permit persons to whom the Software is
21
+ furnished to do so, subject to the following conditions:
22
+
23
+ The above copyright notice and this permission notice shall be included in all
24
+ copies or substantial portions of the Software.
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32
+ SOFTWARE.
33
+ ```
34
+
35
+ The pi-subagents source code is located in `extensions/subagent/`.
@@ -0,0 +1,21 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { resolvePollInterval } from "../async-job-tracker.ts";
3
+
4
+ describe("resolvePollInterval", () => {
5
+ it("returns 1000ms as default (no running jobs)", () => {
6
+ expect(resolvePollInterval(0)).toBe(1000);
7
+ expect(resolvePollInterval(1)).toBe(1000);
8
+ expect(resolvePollInterval(2)).toBe(1000);
9
+ });
10
+
11
+ it("returns 1500ms for 3-4 running jobs", () => {
12
+ expect(resolvePollInterval(3)).toBe(1500);
13
+ expect(resolvePollInterval(4)).toBe(1500);
14
+ });
15
+
16
+ it("returns 2000ms for 5+ running jobs", () => {
17
+ expect(resolvePollInterval(5)).toBe(2000);
18
+ expect(resolvePollInterval(8)).toBe(2000);
19
+ expect(resolvePollInterval(16)).toBe(2000);
20
+ });
21
+ });
@@ -0,0 +1,15 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { resolveActivityTimerInterval } from "../execution.ts";
3
+
4
+ describe("resolveActivityTimerInterval", () => {
5
+ it("returns 2000ms as default (single subprocess)", () => {
6
+ expect(resolveActivityTimerInterval()).toBe(2000);
7
+ expect(resolveActivityTimerInterval(1)).toBe(2000);
8
+ });
9
+
10
+ it("returns 3000ms for parallel scenarios (2+ concurrent)", () => {
11
+ expect(resolveActivityTimerInterval(2)).toBe(3000);
12
+ expect(resolveActivityTimerInterval(4)).toBe(3000);
13
+ expect(resolveActivityTimerInterval(8)).toBe(3000);
14
+ });
15
+ });
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "bun:test";
2
+ import { createThrottle, resolveThrottleInterval } from "../throttle.ts";
3
+ import { resolveWidgetInterval } from "../render.ts";
4
+ import { resolveActivityTimerInterval } from "../execution.ts";
5
+ import { resolvePollInterval } from "../async-job-tracker.ts";
6
+
7
+ /**
8
+ * Integration stress test: simulates 8 parallel subagents firing events
9
+ * and validates that total render frequency stays within bounds.
10
+ */
11
+ describe("parallel render stress", () => {
12
+ beforeEach(() => { vi.useFakeTimers(); });
13
+ afterEach(() => { vi.useRealTimers(); });
14
+
15
+ it("limits onUpdate to ≤5 triggers/s with 8 parallel tasks", () => {
16
+ const interval = resolveThrottleInterval(8);
17
+ expect(interval).toBe(500);
18
+
19
+ const fn = vi.fn();
20
+ const throttled = createThrottle(fn, interval);
21
+
22
+ // Simulate 100 rapid fireUpdate calls in 1ms intervals (like 8 processes sending events)
23
+ for (let i = 0; i < 100; i++) {
24
+ throttled({ index: i });
25
+ vi.advanceTimersByTime(1);
26
+ }
27
+
28
+ // First call executes immediately, then at most 1 call per 500ms
29
+ // Over 100ms simulated time: 1 (immediate) + ~0 (no 500ms passed yet) = 1 call
30
+ expect(fn.mock.calls.length).toBeGreaterThanOrEqual(1);
31
+ expect(fn.mock.calls.length).toBeLessThanOrEqual(2);
32
+
33
+ throttled.dispose();
34
+ });
35
+
36
+ it("allows at most 2 calls in 500ms for 8 tasks (500ms throttle)", () => {
37
+ const interval = resolveThrottleInterval(8);
38
+ const fn = vi.fn();
39
+ const throttled = createThrottle(fn, interval);
40
+
41
+ // Immediate first call
42
+ throttled("a");
43
+ expect(fn).toHaveBeenCalledTimes(1);
44
+
45
+ // Fire many more
46
+ for (let i = 0; i < 50; i++) throttled(`b-${i}`);
47
+
48
+ // Advance 500ms — trailing fires
49
+ vi.advanceTimersByTime(500);
50
+ expect(fn).toHaveBeenCalledTimes(2); // immediate + 1 trailing
51
+
52
+ throttled.dispose();
53
+ });
54
+
55
+ it("widget animation interval adapts to 1000ms at 7+ running jobs", () => {
56
+ expect(resolveWidgetInterval(7)).toBe(1000);
57
+ expect(resolveWidgetInterval(8)).toBe(1000);
58
+ });
59
+
60
+ it("activity timer adapts to 3000ms for parallel scenarios", () => {
61
+ expect(resolveActivityTimerInterval(8)).toBe(3000);
62
+ });
63
+
64
+ it("poll interval adapts to 2000ms at 5+ running jobs", () => {
65
+ expect(resolvePollInterval(5)).toBe(2000);
66
+ expect(resolvePollInterval(8)).toBe(2000);
67
+ });
68
+
69
+ it("total theoretical render rate with 8 parallel subagents is ≤ 8/s", () => {
70
+ const widgetRate = 1000 / resolveWidgetInterval(8); // renders/s
71
+ const pollRate = 1000 / resolvePollInterval(8); // polls/s
72
+ const throttleRate = 1000 / resolveThrottleInterval(8); // onUpdate/s
73
+ const activityRate = 1000 / resolveActivityTimerInterval(8); // activity checks/s
74
+
75
+ // Widget animation + poll + onUpdate throttle + activity are the main render sources
76
+ // Note: not all fire requestRender every time
77
+ const totalMaxRate = widgetRate + pollRate + throttleRate + activityRate;
78
+
79
+ // With all optimizations, should be well under 8/s total
80
+ expect(totalMaxRate).toBeLessThanOrEqual(8);
81
+ });
82
+ });
@@ -0,0 +1,98 @@
1
+ import { describe, it, expect, vi, beforeEach } from "bun:test";
2
+
3
+ describe("render dedup", () => {
4
+ beforeEach(async () => {
5
+ const { _testResetWidgetState } = await import("../render.ts");
6
+ _testResetWidgetState();
7
+ });
8
+
9
+ it("should not call requestRender when widget data is identical", async () => {
10
+ const { renderWidget, stopWidgetAnimation } = await import("../render.ts");
11
+
12
+ const setWidget = vi.fn();
13
+ const requestRender = vi.fn();
14
+ const theme = {
15
+ fg: (_color: string, text: string) => text,
16
+ bold: (text: string) => text,
17
+ };
18
+ const ctx = {
19
+ hasUI: true,
20
+ ui: { setWidget, requestRender, theme },
21
+ } as any;
22
+
23
+ const jobs = [
24
+ { asyncId: "test-1", asyncDir: "/tmp/test", status: "complete" as const, updatedAt: 1000 },
25
+ ];
26
+
27
+ // First render — should call requestRender (new content)
28
+ renderWidget(ctx, jobs);
29
+ expect(requestRender).toHaveBeenCalledTimes(1);
30
+
31
+ // Second render with EXACTLY same data — should NOT call requestRender
32
+ renderWidget(ctx, jobs);
33
+ expect(requestRender).toHaveBeenCalledTimes(1); // Still 1
34
+
35
+ stopWidgetAnimation();
36
+ });
37
+
38
+ it("should call requestRender when widget data changes", async () => {
39
+ const { renderWidget, stopWidgetAnimation } = await import("../render.ts");
40
+
41
+ const setWidget = vi.fn();
42
+ const requestRender = vi.fn();
43
+ const theme = {
44
+ fg: (_color: string, text: string) => text,
45
+ bold: (text: string) => text,
46
+ };
47
+ const ctx = {
48
+ hasUI: true,
49
+ ui: { setWidget, requestRender, theme },
50
+ } as any;
51
+
52
+ // First render
53
+ const jobs1 = [
54
+ { asyncId: "test-1", asyncDir: "/tmp/test", status: "running" as const, updatedAt: 1000 },
55
+ ];
56
+ renderWidget(ctx, jobs1);
57
+ expect(requestRender).toHaveBeenCalledTimes(1);
58
+
59
+ // Second render with changed updatedAt — should call requestRender
60
+ const jobs2 = [
61
+ { asyncId: "test-1", asyncDir: "/tmp/test", status: "running" as const, updatedAt: 2000 },
62
+ ];
63
+ renderWidget(ctx, jobs2);
64
+ expect(requestRender).toHaveBeenCalledTimes(2);
65
+
66
+ stopWidgetAnimation();
67
+ });
68
+
69
+ it("should call requestRender on status transitions", async () => {
70
+ const { renderWidget, stopWidgetAnimation } = await import("../render.ts");
71
+
72
+ const setWidget = vi.fn();
73
+ const requestRender = vi.fn();
74
+ const theme = {
75
+ fg: (_color: string, text: string) => text,
76
+ bold: (text: string) => text,
77
+ };
78
+ const ctx = {
79
+ hasUI: true,
80
+ ui: { setWidget, requestRender, theme },
81
+ } as any;
82
+
83
+ const jobs1 = [
84
+ { asyncId: "test-1", asyncDir: "/tmp/test", status: "running" as const, updatedAt: 1000 },
85
+ ];
86
+ renderWidget(ctx, jobs1);
87
+ expect(requestRender).toHaveBeenCalledTimes(1);
88
+
89
+ // Status changes to complete — should trigger requestRender
90
+ const jobs2 = [
91
+ { asyncId: "test-1", asyncDir: "/tmp/test", status: "complete" as const, updatedAt: 2000 },
92
+ ];
93
+ renderWidget(ctx, jobs2);
94
+ expect(requestRender).toHaveBeenCalledTimes(2);
95
+
96
+ stopWidgetAnimation();
97
+ });
98
+ });
@@ -0,0 +1,28 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "bun:test";
2
+ import {
3
+ resolveWidgetInterval,
4
+ } from "../render.ts";
5
+
6
+ describe("resolveWidgetInterval", () => {
7
+ it("returns 250ms for 1-2 running jobs", () => {
8
+ expect(resolveWidgetInterval(0)).toBe(250);
9
+ expect(resolveWidgetInterval(1)).toBe(250);
10
+ expect(resolveWidgetInterval(2)).toBe(250);
11
+ });
12
+
13
+ it("returns 400ms for 3-4 running jobs", () => {
14
+ expect(resolveWidgetInterval(3)).toBe(400);
15
+ expect(resolveWidgetInterval(4)).toBe(400);
16
+ });
17
+
18
+ it("returns 600ms for 5-6 running jobs", () => {
19
+ expect(resolveWidgetInterval(5)).toBe(600);
20
+ expect(resolveWidgetInterval(6)).toBe(600);
21
+ });
22
+
23
+ it("returns 1000ms for 7+ running jobs", () => {
24
+ expect(resolveWidgetInterval(7)).toBe(1000);
25
+ expect(resolveWidgetInterval(8)).toBe(1000);
26
+ expect(resolveWidgetInterval(16)).toBe(1000);
27
+ });
28
+ });
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "bun:test";
2
+ import { createThrottle, resolveThrottleInterval } from "../throttle.ts";
3
+
4
+ describe("createThrottle", () => {
5
+ beforeEach(() => { vi.useFakeTimers(); });
6
+ afterEach(() => { vi.useRealTimers(); });
7
+
8
+ it("executes the first call immediately", () => {
9
+ const fn = vi.fn();
10
+ const throttled = createThrottle(fn, 200);
11
+ throttled("a");
12
+ expect(fn).toHaveBeenCalledTimes(1);
13
+ expect(fn).toHaveBeenCalledWith("a");
14
+ });
15
+
16
+ it("drops calls within the interval, keeping only the latest args", () => {
17
+ const fn = vi.fn();
18
+ const throttled = createThrottle(fn, 200);
19
+ throttled("a");
20
+ throttled("b");
21
+ throttled("c");
22
+ expect(fn).toHaveBeenCalledTimes(1);
23
+ expect(fn).toHaveBeenCalledWith("a");
24
+
25
+ vi.advanceTimersByTime(200);
26
+ expect(fn).toHaveBeenCalledTimes(2);
27
+ expect(fn).toHaveBeenCalledWith("c");
28
+ });
29
+
30
+ it("does not fire trailing call if no pending args", () => {
31
+ const fn = vi.fn();
32
+ const throttled = createThrottle(fn, 200);
33
+ throttled("a");
34
+ expect(fn).toHaveBeenCalledTimes(1);
35
+
36
+ vi.advanceTimersByTime(200);
37
+ // No new call after "a", so no trailing fire
38
+ expect(fn).toHaveBeenCalledTimes(1);
39
+ });
40
+
41
+ it("flush() immediately executes the latest pending args", () => {
42
+ const fn = vi.fn();
43
+ const throttled = createThrottle(fn, 200);
44
+ throttled("a");
45
+ throttled("b");
46
+ throttled.flush();
47
+ expect(fn).toHaveBeenCalledTimes(2);
48
+ expect(fn).toHaveBeenCalledWith("b");
49
+ });
50
+
51
+ it("flush() does nothing if no pending call", () => {
52
+ const fn = vi.fn();
53
+ const throttled = createThrottle(fn, 200);
54
+ throttled.flush();
55
+ expect(fn).toHaveBeenCalledTimes(0);
56
+ });
57
+
58
+ it("dispose() clears timers and prevents future trailing calls", () => {
59
+ const fn = vi.fn();
60
+ const throttled = createThrottle(fn, 200);
61
+ throttled("a");
62
+ throttled("b");
63
+ throttled.dispose();
64
+ vi.advanceTimersByTime(500);
65
+ expect(fn).toHaveBeenCalledTimes(1);
66
+ expect(fn).toHaveBeenCalledWith("a");
67
+ });
68
+ });
69
+
70
+ describe("resolveThrottleInterval", () => {
71
+ it("returns 0 for 1 task (no throttle)", () => {
72
+ expect(resolveThrottleInterval(1)).toBe(0);
73
+ });
74
+
75
+ it("returns 200ms for 2-4 tasks", () => {
76
+ expect(resolveThrottleInterval(2)).toBe(200);
77
+ expect(resolveThrottleInterval(3)).toBe(200);
78
+ expect(resolveThrottleInterval(4)).toBe(200);
79
+ });
80
+
81
+ it("returns 500ms for 5-8 tasks", () => {
82
+ expect(resolveThrottleInterval(5)).toBe(500);
83
+ expect(resolveThrottleInterval(8)).toBe(500);
84
+ });
85
+
86
+ it("returns 1000ms for 9+ tasks", () => {
87
+ expect(resolveThrottleInterval(9)).toBe(1000);
88
+ expect(resolveThrottleInterval(16)).toBe(1000);
89
+ });
90
+ });
@@ -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
  import type { Theme } from "@mariozechner/pi-coding-agent";
4
5
  import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
5
6
  import type { ChainConfig, ChainStepConfig } from "./agents.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 type { Theme } from "@mariozechner/pi-coding-agent";
4
5
  import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
5
6
  import type { AgentConfig } from "./agents.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 type { Theme } from "@mariozechner/pi-coding-agent";
4
5
  import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
5
6
  import { defaultSystemPromptMode, type AgentConfig, type BuiltinAgentOverrideBase } from "./agents.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 type { Theme } from "@mariozechner/pi-coding-agent";
4
5
  import type { AgentSource } from "./agents.ts";
5
6
  import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
@@ -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 { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
5
6
  import type { TextEditorState } from "./text-editor.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 * 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 type { AgentScope } from "./agents.ts";
4
5
 
5
6
  export function resolveExecutionAgentScope(scope: unknown): AgentScope {
@@ -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 { AgentScope, AgentConfig } from "./agents.ts";
4
5
 
5
6
  export function mergeAgentsForScope(
@@ -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 type { AgentConfig } from "./agents.ts";
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 { AgentConfig } from "./agents.ts";
4
5
 
5
6
  export interface AgentTemplate {
@@ -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
  * Agent discovery and configuration
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
  /**
4
5
  * Async execution logic for 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 type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
4
5
  import * as fs from "node:fs";
5
6
  import * as path from "node:path";
@@ -15,6 +16,16 @@ import {
15
16
  } from "./types.ts";
16
17
  import { readStatus } from "./utils.ts";
17
18
 
19
+ /**
20
+ * Resolve poll interval based on number of running jobs.
21
+ * Higher parallelism → longer interval to reduce I/O and render pressure.
22
+ */
23
+ export function resolvePollInterval(runningCount: number): number {
24
+ if (runningCount <= 2) return 1000;
25
+ if (runningCount <= 4) return 1500;
26
+ return 2000;
27
+ }
28
+
18
29
  interface AsyncJobTrackerOptions {
19
30
  completionRetentionMs?: number;
20
31
  pollIntervalMs?: number;
@@ -102,7 +113,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
102
113
 
103
114
  const ensurePoller = () => {
104
115
  if (state.poller) return;
105
- state.poller = setInterval(() => {
116
+ const pollOnce = () => {
106
117
  if (state.asyncJobs.size === 0) {
107
118
  if (state.lastUiContext?.hasUI) rerenderWidget(state.lastUiContext, []);
108
119
  if (state.poller) {
@@ -150,7 +161,20 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
150
161
  }
151
162
 
152
163
  if (state.lastUiContext?.hasUI) rerenderWidget(state.lastUiContext);
153
- }, pollIntervalMs);
164
+
165
+ // Dynamically adjust poll interval based on running count
166
+ const runningCount = Array.from(state.asyncJobs.values()).filter((j) => j.status === "running").length;
167
+ const adaptiveInterval = resolvePollInterval(runningCount);
168
+ if (state.poller) {
169
+ clearInterval(state.poller);
170
+ state.poller = setInterval(pollOnce, adaptiveInterval);
171
+ state.poller.unref?.();
172
+ }
173
+ };
174
+
175
+ const runningCount = Array.from(state.asyncJobs.values()).filter((j) => j.status === "running").length;
176
+ const initialInterval = resolvePollInterval(runningCount);
177
+ state.poller = setInterval(pollOnce, initialInterval);
154
178
  state.poller.unref?.();
155
179
  };
156
180
 
@@ -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 { formatDuration, formatTokens, shortenPath } from "./formatters.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
  /**
4
5
  * Chain Clarification TUI Component
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 execution logic for 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 type { ChainConfig, ChainStepConfig } from "./agents.ts";
4
5
  import { parseFrontmatter } from "./frontmatter.ts";
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
  interface CompletionDataLike {
4
5
  id?: unknown;
5
6
  agent?: unknown;
@@ -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 { discoverAgentsAll, type AgentSource } from "./agents.ts";