@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.
- package/LICENSE +21 -0
- package/README.md +148 -361
- package/THIRD-PARTY-NOTICES.md +35 -0
- package/extensions/subagent/__tests__/async-job-tracker.test.ts +21 -0
- package/extensions/subagent/__tests__/execution-activity.test.ts +15 -0
- package/extensions/subagent/__tests__/parallel-render-stress.test.ts +82 -0
- package/extensions/subagent/__tests__/render-dedup.test.ts +98 -0
- package/extensions/subagent/__tests__/render-widget.test.ts +28 -0
- package/extensions/subagent/__tests__/throttle.test.ts +90 -0
- package/extensions/subagent/agent-management.ts +3 -2
- package/extensions/subagent/agent-manager-chain-detail.ts +3 -2
- package/extensions/subagent/agent-manager-detail.ts +3 -2
- package/extensions/subagent/agent-manager-edit.ts +3 -2
- package/extensions/subagent/agent-manager-list.ts +3 -2
- package/extensions/subagent/agent-manager-parallel.ts +3 -2
- package/extensions/subagent/agent-manager.ts +3 -2
- package/extensions/subagent/agent-scope.ts +3 -2
- package/extensions/subagent/agent-selection.ts +3 -2
- package/extensions/subagent/agent-serializer.ts +3 -2
- package/extensions/subagent/agent-templates.ts +3 -2
- package/extensions/subagent/agents.ts +3 -2
- package/extensions/subagent/artifacts.ts +3 -2
- package/extensions/subagent/async-execution.ts +3 -2
- package/extensions/subagent/async-job-tracker.ts +28 -4
- package/extensions/subagent/async-status.ts +3 -2
- package/extensions/subagent/chain-clarify.ts +3 -2
- package/extensions/subagent/chain-execution.ts +3 -2
- package/extensions/subagent/chain-serializer.ts +3 -2
- package/extensions/subagent/completion-dedupe.ts +3 -2
- package/extensions/subagent/doctor.ts +3 -2
- package/extensions/subagent/execution.ts +13 -3
- package/extensions/subagent/file-coalescer.ts +3 -2
- package/extensions/subagent/fork-context.ts +3 -2
- package/extensions/subagent/formatters.ts +3 -2
- package/extensions/subagent/frontmatter.ts +3 -2
- package/extensions/subagent/index.ts +3 -2
- package/extensions/subagent/intercom-bridge.ts +3 -2
- package/extensions/subagent/jsonl-writer.ts +3 -2
- package/extensions/subagent/model-fallback.ts +3 -2
- package/extensions/subagent/notify.ts +3 -2
- package/extensions/subagent/parallel-utils.ts +3 -2
- package/extensions/subagent/pi-args.ts +3 -2
- package/extensions/subagent/pi-spawn.ts +3 -2
- package/extensions/subagent/post-exit-stdio-guard.ts +3 -2
- package/extensions/subagent/prompt-template-bridge.ts +3 -2
- package/extensions/subagent/render-helpers.ts +3 -2
- package/extensions/subagent/render.ts +81 -9
- package/extensions/subagent/result-intercom.ts +3 -2
- package/extensions/subagent/result-watcher.ts +3 -2
- package/extensions/subagent/run-history.ts +3 -2
- package/extensions/subagent/run-status.ts +3 -2
- package/extensions/subagent/schemas.ts +3 -2
- package/extensions/subagent/session-tokens.ts +3 -2
- package/extensions/subagent/settings.ts +3 -2
- package/extensions/subagent/single-output.ts +3 -2
- package/extensions/subagent/skills.ts +3 -2
- package/extensions/subagent/slash-bridge.ts +3 -2
- package/extensions/subagent/slash-commands.ts +3 -2
- package/extensions/subagent/slash-live-state.ts +3 -2
- package/extensions/subagent/subagent-control.ts +3 -2
- package/extensions/subagent/subagent-executor.ts +19 -3
- package/extensions/subagent/subagent-prompt-runtime.ts +3 -2
- package/extensions/subagent/subagent-runner.ts +3 -2
- package/extensions/subagent/subagents-status.ts +3 -2
- package/extensions/subagent/text-editor.ts +3 -2
- package/extensions/subagent/throttle.ts +77 -0
- package/extensions/subagent/top-level-async.ts +3 -2
- package/extensions/subagent/types.ts +3 -2
- package/extensions/subagent/utils.ts +3 -2
- package/extensions/subagent/worktree.ts +3 -2
- 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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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
|
-
//
|
|
2
|
-
// MIT
|
|
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";
|