@selesai/code 0.3.0 → 0.3.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/dist/core/diagnostics.d.ts +1 -0
- package/dist/core/diagnostics.d.ts.map +1 -1
- package/dist/core/diagnostics.js.map +1 -1
- package/dist/core/package-manager.d.ts +10 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +93 -8
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-manager.test.d.ts +2 -0
- package/dist/core/package-manager.test.d.ts.map +1 -0
- package/dist/core/package-manager.test.js +20 -0
- package/dist/core/package-manager.test.js.map +1 -0
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +10 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/extensions/caveman/caveman-instructions.cjs +39 -0
- package/dist/extensions/caveman/index.js +116 -0
- package/dist/extensions/caveman/package.json +8 -0
- package/dist/extensions/caveman/test/extension.test.js +185 -0
- package/dist/extensions/caveman/test/helpers.test.js +67 -0
- package/dist/extensions/pi-intercom/broker/broker.ts +2 -3
- package/dist/extensions/pi-intercom/broker/paths.ts +10 -5
- package/dist/extensions/pi-intercom/broker/spawn.ts +2 -3
- package/dist/extensions/pi-intercom/config.ts +2 -2
- package/dist/extensions/pi-intercom/index.ts +2 -2
- package/dist/extensions/pi-intercom/package.json +2 -2
- package/dist/extensions/pi-intercom/ui/compose.ts +3 -3
- package/dist/extensions/pi-intercom/ui/inline-message.ts +3 -3
- package/dist/extensions/pi-intercom/ui/session-list.ts +3 -3
- package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +2 -1
- package/dist/extensions/pi-powerline-footer/index.ts +9 -16
- package/dist/extensions/pi-powerline-footer/welcome.ts +23 -16
- package/dist/extensions/pi-powerline-footer/working-vibes.ts +2 -9
- package/dist/extensions/pi-rewind-hook/index.ts +1 -1
- package/dist/extensions/pi-rewind-hook/package.json +1 -1
- package/dist/extensions/pi-subagents/README.md +224 -52
- package/dist/extensions/pi-subagents/install.mjs +93 -0
- package/dist/extensions/pi-subagents/package.json +4 -5
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +76 -13
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +179 -2
- package/dist/extensions/pi-subagents/src/agents/agent-memory.ts +254 -0
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +11 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +193 -19
- package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +27 -2
- package/dist/extensions/pi-subagents/src/extension/config.ts +27 -4
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +1 -7
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -2
- package/dist/extensions/pi-subagents/src/extension/index.ts +73 -71
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +369 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +54 -10
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +200 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +21 -253
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +519 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +195 -105
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +88 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +67 -10
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +34 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +166 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +156 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +515 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +161 -44
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +1 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +3 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +167 -6
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +514 -0
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +28 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +873 -204
- package/dist/extensions/pi-subagents/src/runs/background/wait.ts +353 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +123 -27
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +174 -70
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +569 -81
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +45 -22
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +172 -21
- package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +89 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +50 -1
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +35 -4
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +63 -45
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +125 -5
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +74 -0
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +52 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +28 -5
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +16 -1
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +15 -2
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +212 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +133 -22
- package/dist/extensions/pi-subagents/src/shared/settings.ts +3 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +197 -4
- package/dist/extensions/pi-subagents/src/shared/utils.ts +100 -15
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +330 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +135 -4
- package/dist/extensions/pi-subagents/src/tui/render.ts +32 -12
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2772 -0
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +1107 -0
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +463 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +274 -0
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1535 -0
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +242 -0
- package/dist/extensions/pi-subagents/test/integration/doctor-executor.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +252 -0
- package/dist/extensions/pi-subagents/test/integration/foreground-result-size.test.ts +188 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +1306 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +1033 -0
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +583 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +691 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +892 -0
- package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1824 -0
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1284 -0
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +127 -0
- package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +37 -0
- package/dist/extensions/pi-subagents/test/support/helpers.ts +189 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +335 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +129 -0
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +184 -0
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +285 -0
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +15 -0
- package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +108 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +421 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +343 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +1199 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +556 -0
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +420 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +579 -0
- package/dist/extensions/pi-subagents/test/unit/agent-scope.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +221 -0
- package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +98 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +206 -0
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +229 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +225 -0
- package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +101 -0
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +228 -0
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +152 -0
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +161 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +217 -0
- package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +415 -0
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +126 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +271 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/jsonl-writer.test.ts +119 -0
- package/dist/extensions/pi-subagents/test/unit/live-async-resume.test.ts +170 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +346 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +87 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +145 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +162 -0
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +470 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +329 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +319 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +70 -0
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +113 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +803 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +238 -0
- package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +118 -0
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +408 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +257 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +226 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +294 -0
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +147 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +997 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +478 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +562 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +438 -0
- package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +54 -0
- package/dist/extensions/pi-subagents/test/unit/slash-chain-groups.test.ts +464 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +255 -0
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +19 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +245 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +517 -0
- package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +237 -0
- package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +60 -0
- package/dist/extensions/pi-subagents/test/unit/ts-loader.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +202 -0
- package/dist/extensions/pi-subagents/test/unit/types-fork-preamble.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/wait.test.ts +398 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +479 -0
- package/dist/extensions/pi-web-agent/src/changelog-notice.ts +5 -2
- package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +6 -4
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +19 -5
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/skills/caveman/SKILL.md +49 -0
- package/dist/skills/handoff-text/SKILL.md +13 -0
- package/docs/docs.json +4 -0
- package/docs/extensions.md +2 -0
- package/docs/settings.md +15 -0
- package/docs/shared-host-extensions.md +109 -0
- package/package.json +1 -1
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for parallel execution.
|
|
3
|
+
*
|
|
4
|
+
* Tests the mapConcurrent utility and parallel agent spawning via runSync.
|
|
5
|
+
* The top-level parallel mode (params.tasks) lives in index.ts and uses
|
|
6
|
+
* mapConcurrent + runSync — we test both pieces here.
|
|
7
|
+
*
|
|
8
|
+
* mapConcurrent tests always run. runSync tests require pi packages.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, before, after, beforeEach, afterEach } from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import * as fs from "node:fs";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import type { MockPi } from "../support/helpers.ts";
|
|
16
|
+
import {
|
|
17
|
+
createEventBus,
|
|
18
|
+
createMockPi,
|
|
19
|
+
createTempDir,
|
|
20
|
+
makeAgent,
|
|
21
|
+
makeAgentConfigs,
|
|
22
|
+
makeMinimalCtx,
|
|
23
|
+
removeTempDir,
|
|
24
|
+
tryImport,
|
|
25
|
+
} from "../support/helpers.ts";
|
|
26
|
+
|
|
27
|
+
// Top-level await: try importing pi-dependent modules
|
|
28
|
+
const utils = await tryImport<any>("./src/shared/utils.ts");
|
|
29
|
+
const execution = await tryImport<any>("./src/runs/foreground/execution.ts");
|
|
30
|
+
const executorMod = await tryImport<any>("./src/runs/foreground/subagent-executor.ts");
|
|
31
|
+
const piAvailable = !!(execution && utils);
|
|
32
|
+
|
|
33
|
+
const runSync = execution?.runSync;
|
|
34
|
+
const mapConcurrent = utils?.mapConcurrent;
|
|
35
|
+
const createSubagentExecutor = executorMod?.createSubagentExecutor;
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// mapConcurrent — always runs (pure logic, no pi deps beyond utils.ts)
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
describe("mapConcurrent", { skip: !mapConcurrent ? "utils not importable" : undefined }, () => {
|
|
42
|
+
it("processes all items", async () => {
|
|
43
|
+
const items = [1, 2, 3, 4, 5];
|
|
44
|
+
const results = await mapConcurrent(items, 2, async (item: number) => item * 2);
|
|
45
|
+
assert.deepEqual(results, [2, 4, 6, 8, 10]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("preserves order regardless of completion time", async () => {
|
|
49
|
+
const items = [80, 10, 40]; // delays in ms
|
|
50
|
+
const results = await mapConcurrent(items, 3, async (ms: number, i: number) => {
|
|
51
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
52
|
+
return i;
|
|
53
|
+
});
|
|
54
|
+
assert.deepEqual(results, [0, 1, 2], "results should be in original order");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("respects concurrency limit", async () => {
|
|
58
|
+
let running = 0;
|
|
59
|
+
let maxRunning = 0;
|
|
60
|
+
const items = [1, 2, 3, 4, 5, 6];
|
|
61
|
+
|
|
62
|
+
await mapConcurrent(items, 2, async () => {
|
|
63
|
+
running++;
|
|
64
|
+
maxRunning = Math.max(maxRunning, running);
|
|
65
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
66
|
+
running--;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
assert.ok(maxRunning <= 2, `max concurrent should be ≤ 2, got ${maxRunning}`);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("handles empty array", async () => {
|
|
73
|
+
const results = await mapConcurrent([], 4, async (item: unknown) => item);
|
|
74
|
+
assert.deepEqual(results, []);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("propagates errors", async () => {
|
|
78
|
+
await assert.rejects(
|
|
79
|
+
() =>
|
|
80
|
+
mapConcurrent([1, 2, 3], 2, async (item: number) => {
|
|
81
|
+
if (item === 2) throw new Error("boom");
|
|
82
|
+
return item;
|
|
83
|
+
}),
|
|
84
|
+
/boom/,
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Parallel agent execution via runSync
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
describe("parallel agent execution", { skip: !piAvailable ? "pi packages not available" : undefined }, () => {
|
|
94
|
+
let tempDir: string;
|
|
95
|
+
let mockPi: MockPi;
|
|
96
|
+
|
|
97
|
+
before(() => {
|
|
98
|
+
mockPi = createMockPi();
|
|
99
|
+
mockPi.install();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
after(() => {
|
|
103
|
+
mockPi.uninstall();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
tempDir = createTempDir();
|
|
108
|
+
mockPi.reset();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
afterEach(() => {
|
|
112
|
+
removeTempDir(tempDir);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
function makeExecutor(agents = [makeAgent("echo")]) {
|
|
116
|
+
return createSubagentExecutor({
|
|
117
|
+
pi: { events: createEventBus(), getSessionName: () => undefined },
|
|
118
|
+
state: { baseCwd: tempDir, currentSessionId: null, asyncJobs: new Map(), foregroundControls: new Map(), lastForegroundControlId: null },
|
|
119
|
+
config: {},
|
|
120
|
+
asyncByDefault: false,
|
|
121
|
+
tempArtifactsDir: tempDir,
|
|
122
|
+
getSubagentSessionRoot: () => tempDir,
|
|
123
|
+
expandTilde: (value: string) => value,
|
|
124
|
+
discoverAgents: () => ({ agents }),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readLastCallArgs(): string[] {
|
|
129
|
+
const callFile = fs.readdirSync(mockPi.dir).find((name) => name.startsWith("call-"));
|
|
130
|
+
assert.ok(callFile, "expected a recorded mock pi call");
|
|
131
|
+
return JSON.parse(fs.readFileSync(path.join(mockPi.dir, callFile), "utf-8")).args as string[];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
it("runs multiple agents concurrently via mapConcurrent + runSync", async () => {
|
|
135
|
+
mockPi.onCall({ output: "Done" });
|
|
136
|
+
const agents = makeAgentConfigs(["agent-a", "agent-b", "agent-c"]);
|
|
137
|
+
const tasks = ["Task A", "Task B", "Task C"];
|
|
138
|
+
|
|
139
|
+
const results = await mapConcurrent(
|
|
140
|
+
tasks.map((task, i) => ({ agent: agents[i].name, task, index: i })),
|
|
141
|
+
3,
|
|
142
|
+
async ({ agent, task, index }: any) => {
|
|
143
|
+
return runSync(tempDir, agents, agent, task, { index });
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
assert.equal(results.length, 3);
|
|
148
|
+
assert.ok(results.every((r: any) => r.exitCode === 0));
|
|
149
|
+
assert.equal(results[0].agent, "agent-a");
|
|
150
|
+
assert.equal(results[1].agent, "agent-b");
|
|
151
|
+
assert.equal(results[2].agent, "agent-c");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("all agents get independent results", async () => {
|
|
155
|
+
mockPi.onCall({ output: "Result" });
|
|
156
|
+
const agents = makeAgentConfigs(["a", "b"]);
|
|
157
|
+
|
|
158
|
+
const results = await mapConcurrent(
|
|
159
|
+
[
|
|
160
|
+
{ agent: "a", task: "Task A" },
|
|
161
|
+
{ agent: "b", task: "Task B" },
|
|
162
|
+
],
|
|
163
|
+
2,
|
|
164
|
+
async ({ agent, task }: any, i: number) => runSync(tempDir, agents, agent, task, { index: i }),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
assert.equal(results.length, 2);
|
|
168
|
+
assert.equal(results[0].agent, "a");
|
|
169
|
+
assert.equal(results[1].agent, "b");
|
|
170
|
+
const ok = results.filter((r: any) => r.exitCode === 0).length;
|
|
171
|
+
assert.equal(ok, 2);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("treats parallel action aliases with tasks as top-level parallel execution", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
175
|
+
for (const action of ["parallel", "PARALLEL", "tasks"]) {
|
|
176
|
+
mockPi.reset();
|
|
177
|
+
mockPi.onCall({ output: `${action} alias finished` });
|
|
178
|
+
const executor = makeExecutor();
|
|
179
|
+
|
|
180
|
+
const result = await executor.execute(
|
|
181
|
+
`parallel-alias-${action}`,
|
|
182
|
+
{ action, tasks: [{ agent: "echo", task: `Run ${action}` }] },
|
|
183
|
+
new AbortController().signal,
|
|
184
|
+
undefined,
|
|
185
|
+
makeMinimalCtx(tempDir),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
assert.equal(result.isError, undefined);
|
|
189
|
+
assert.equal(result.details?.mode, "parallel");
|
|
190
|
+
assert.match(result.content[0]?.text ?? "", new RegExp(`${action} alias finished`));
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("top-level parallel output saves use per-task output paths", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
195
|
+
mockPi.onCall({ output: "Saved report" });
|
|
196
|
+
const executor = makeExecutor();
|
|
197
|
+
|
|
198
|
+
const result = await executor.execute(
|
|
199
|
+
"parallel-output",
|
|
200
|
+
{ tasks: [{ agent: "echo", task: "Write report", output: "parallel-output.md" }] },
|
|
201
|
+
new AbortController().signal,
|
|
202
|
+
undefined,
|
|
203
|
+
makeMinimalCtx(tempDir),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const runId = result.details?.runId;
|
|
207
|
+
assert.ok(runId, "expected run id in details");
|
|
208
|
+
const outputPath = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", runId, "parallel-output.md");
|
|
209
|
+
assert.equal(result.isError, undefined);
|
|
210
|
+
assert.equal(fs.readFileSync(outputPath, "utf-8"), "Saved report");
|
|
211
|
+
assert.equal(result.details?.results?.[0]?.savedOutputPath, outputPath);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("top-level parallel preserves completed siblings and marks timed-out children", { skip: !createSubagentExecutor ? "executor not importable" : process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
|
|
215
|
+
mockPi.onCall({ matchArgIncludes: "Slow review", delay: 10000 });
|
|
216
|
+
mockPi.onCall({ matchArgIncludes: "Fast review", output: "fast done" });
|
|
217
|
+
const executor = makeExecutor();
|
|
218
|
+
|
|
219
|
+
const start = Date.now();
|
|
220
|
+
const result = await executor.execute(
|
|
221
|
+
"parallel-timeout",
|
|
222
|
+
{
|
|
223
|
+
tasks: [
|
|
224
|
+
{ agent: "echo", task: "Slow review" },
|
|
225
|
+
{ agent: "echo", task: "Fast review" },
|
|
226
|
+
],
|
|
227
|
+
concurrency: 2,
|
|
228
|
+
maxRuntimeMs: 300,
|
|
229
|
+
},
|
|
230
|
+
new AbortController().signal,
|
|
231
|
+
undefined,
|
|
232
|
+
makeMinimalCtx(tempDir),
|
|
233
|
+
);
|
|
234
|
+
const elapsed = Date.now() - start;
|
|
235
|
+
|
|
236
|
+
assert.ok(elapsed < 5000, `should time out early, took ${elapsed}ms`);
|
|
237
|
+
assert.equal(result.isError, undefined);
|
|
238
|
+
assert.equal(result.details?.results?.length, 2);
|
|
239
|
+
assert.equal(result.details?.results?.[0]?.timedOut, true);
|
|
240
|
+
assert.equal(result.details?.results?.[0]?.error, "Subagent timed out after 300ms.");
|
|
241
|
+
assert.equal(result.details?.results?.[1]?.exitCode, 0);
|
|
242
|
+
assert.equal(result.details?.results?.[1]?.finalOutput, "fast done");
|
|
243
|
+
assert.match(result.content[0]?.text ?? "", /1\/2 succeeded/);
|
|
244
|
+
assert.match(result.content[0]?.text ?? "", /TIMED OUT: Subagent timed out after 300ms\./);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("top-level parallel file-only output aggregates concise file references", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
248
|
+
mockPi.onCall({ output: "Parallel full report\nwith details" });
|
|
249
|
+
const executor = makeExecutor();
|
|
250
|
+
|
|
251
|
+
const result = await executor.execute(
|
|
252
|
+
"parallel-file-only-output",
|
|
253
|
+
{ tasks: [{ agent: "echo", task: "Write report", output: "parallel-file-only.md", outputMode: "file-only" }] },
|
|
254
|
+
new AbortController().signal,
|
|
255
|
+
undefined,
|
|
256
|
+
makeMinimalCtx(tempDir),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const runId = result.details?.runId;
|
|
260
|
+
assert.ok(runId, "expected run id in details");
|
|
261
|
+
const outputPath = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", runId, "parallel-file-only.md");
|
|
262
|
+
const text = result.content[0]?.text ?? "";
|
|
263
|
+
assert.equal(result.isError, undefined);
|
|
264
|
+
assert.match(text, /Output saved to:/);
|
|
265
|
+
assert.match(text, /2 lines/);
|
|
266
|
+
assert.doesNotMatch(text, /Parallel full report/);
|
|
267
|
+
assert.match(result.details?.results?.[0]?.finalOutput ?? "", /Output saved to:/);
|
|
268
|
+
assert.doesNotMatch(result.details?.results?.[0]?.finalOutput ?? "", /Parallel full report/);
|
|
269
|
+
assert.equal(fs.readFileSync(outputPath, "utf-8"), "Parallel full report\nwith details");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("rejects top-level parallel file-only output without an output path", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
273
|
+
const executor = makeExecutor();
|
|
274
|
+
|
|
275
|
+
const result = await executor.execute(
|
|
276
|
+
"parallel-file-only-missing-output",
|
|
277
|
+
{ tasks: [{ agent: "echo", task: "Write report", outputMode: "file-only" }] },
|
|
278
|
+
new AbortController().signal,
|
|
279
|
+
undefined,
|
|
280
|
+
makeMinimalCtx(tempDir),
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
assert.equal(result.isError, true);
|
|
284
|
+
assert.match(result.content[0]?.text ?? "", /outputMode: "file-only"/);
|
|
285
|
+
assert.equal(mockPi.callCount(), 0);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("rejects duplicate top-level parallel output paths", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
289
|
+
const executor = makeExecutor();
|
|
290
|
+
|
|
291
|
+
const result = await executor.execute(
|
|
292
|
+
"parallel-duplicate-output",
|
|
293
|
+
{
|
|
294
|
+
tasks: [
|
|
295
|
+
{ agent: "echo", task: "Write A", output: "same.md" },
|
|
296
|
+
{ agent: "echo", task: "Write B", output: "same.md" },
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
new AbortController().signal,
|
|
300
|
+
undefined,
|
|
301
|
+
makeMinimalCtx(tempDir),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
assert.equal(result.isError, true);
|
|
305
|
+
assert.match(result.content[0]?.text ?? "", /same path/);
|
|
306
|
+
assert.equal(mockPi.callCount(), 0);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("treats string false as disabled output in top-level parallel runs", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
310
|
+
mockPi.onCall({ output: "Review done" });
|
|
311
|
+
const executor = makeExecutor();
|
|
312
|
+
|
|
313
|
+
const result = await executor.execute(
|
|
314
|
+
"parallel-string-false-output",
|
|
315
|
+
{
|
|
316
|
+
tasks: [
|
|
317
|
+
{ agent: "echo", task: "Review A", output: "false" },
|
|
318
|
+
{ agent: "echo", task: "Review B", output: "false" },
|
|
319
|
+
],
|
|
320
|
+
},
|
|
321
|
+
new AbortController().signal,
|
|
322
|
+
undefined,
|
|
323
|
+
makeMinimalCtx(tempDir),
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
assert.equal(result.isError, undefined);
|
|
327
|
+
assert.equal(mockPi.callCount(), 2);
|
|
328
|
+
assert.equal(fs.existsSync(path.join(tempDir, "false")), false);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("top-level parallel reads are injected once with chain-style prefix", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
332
|
+
mockPi.onCall({ output: "Read done" });
|
|
333
|
+
const executor = makeExecutor();
|
|
334
|
+
|
|
335
|
+
await executor.execute(
|
|
336
|
+
"parallel-reads",
|
|
337
|
+
{ tasks: [{ agent: "echo", task: "Inspect", reads: ["a.md", "b.md"] }] },
|
|
338
|
+
new AbortController().signal,
|
|
339
|
+
undefined,
|
|
340
|
+
makeMinimalCtx(tempDir),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
const args = readLastCallArgs();
|
|
344
|
+
const taskArg = args.at(-1) ?? "";
|
|
345
|
+
assert.ok(taskArg.startsWith(`Task: [Read from: ${path.join(tempDir, "a.md")}, ${path.join(tempDir, "b.md")}]
|
|
346
|
+
|
|
347
|
+
Inspect
|
|
348
|
+
|
|
349
|
+
## Acceptance Contract`));
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("top-level parallel defaultProgress uses isolated run storage", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
353
|
+
mockPi.onCall({ output: "Progress done" });
|
|
354
|
+
const executor = makeExecutor([makeAgent("echo", { defaultProgress: true })]);
|
|
355
|
+
const parentSessionFile = path.join(tempDir, "parent-session.jsonl");
|
|
356
|
+
|
|
357
|
+
const result = await executor.execute(
|
|
358
|
+
"parallel-progress",
|
|
359
|
+
{ tasks: [{ agent: "echo", task: "Track work" }] },
|
|
360
|
+
new AbortController().signal,
|
|
361
|
+
undefined,
|
|
362
|
+
{
|
|
363
|
+
...makeMinimalCtx(tempDir),
|
|
364
|
+
sessionManager: {
|
|
365
|
+
getSessionId: () => "session-123",
|
|
366
|
+
getSessionFile: () => parentSessionFile,
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
);
|
|
370
|
+
const runId = result.details?.runId;
|
|
371
|
+
assert.ok(runId, "expected run id in details");
|
|
372
|
+
const expectedProgressPath = path.join(tempDir, ".pi-subagents", "artifacts", "progress", runId, "progress.md");
|
|
373
|
+
|
|
374
|
+
const args = readLastCallArgs();
|
|
375
|
+
const taskArg = args.at(-1) ?? "";
|
|
376
|
+
assert.ok(taskArg.includes(`Update progress at: ${expectedProgressPath}`), taskArg);
|
|
377
|
+
assert.equal(fs.existsSync(expectedProgressPath), true);
|
|
378
|
+
assert.equal(fs.existsSync(path.join(tempDir, "progress.md")), false);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("top-level parallel suppresses progress when the task is review-only", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
382
|
+
mockPi.onCall({ output: "Review done" });
|
|
383
|
+
const executor = makeExecutor([makeAgent("reviewer", { defaultProgress: true })]);
|
|
384
|
+
|
|
385
|
+
await executor.execute(
|
|
386
|
+
"parallel-read-only-progress",
|
|
387
|
+
{ tasks: [{ agent: "reviewer", task: "Review-only. Do not edit files. Return findings." }] },
|
|
388
|
+
new AbortController().signal,
|
|
389
|
+
undefined,
|
|
390
|
+
makeMinimalCtx(tempDir),
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const taskArg = readLastCallArgs().at(-1) ?? "";
|
|
394
|
+
assert.doesNotMatch(taskArg, /progress\.md/);
|
|
395
|
+
assert.equal(fs.existsSync(path.join(tempDir, "progress.md")), false);
|
|
396
|
+
});
|
|
397
|
+
});
|