@selesai/code 0.5.19 → 0.5.21
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/extensions/handoff-new.test.ts +8 -9
- package/dist/extensions/handoff-new.ts +6 -8
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +31 -0
- package/dist/extensions/pi-intercom/broker/spawn.ts +5 -3
- package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
- package/dist/extensions/pi-subagents/README.md +602 -220
- package/dist/extensions/pi-subagents/banner.png +0 -0
- package/dist/extensions/pi-subagents/index.ts +1 -0
- package/dist/extensions/pi-subagents/install.mjs +3 -3
- package/dist/extensions/pi-subagents/package-lock.json +3405 -0
- package/dist/extensions/pi-subagents/package.json +26 -14
- package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
- package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
- package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
- package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
- package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
- package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
- package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
- package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
- package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
- package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
- package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
- package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
- package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
- package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
- package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
- package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
- package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
- package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
- package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
- package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
- package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
- package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
- package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
- package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
- package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
- package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
- package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
- package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
- package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
- package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
- package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
- package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
- package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
- package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
- package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
- package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
- package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
- package/dist/extensions/question/batch.ts +39 -0
- package/dist/extensions/question/dialog-adapter.ts +23 -1
- package/dist/extensions/question/index.ts +237 -7
- package/dist/extensions/question/schemas.ts +28 -15
- package/dist/extensions/question/tests/batch.test.ts +26 -0
- package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
- package/dist/extensions/question/tui-adapter.ts +2 -3
- package/dist/extensions/question/types.ts +37 -0
- package/dist/extensions/question/ui-protocol.ts +3 -3
- package/dist/skills/batch-grill-me/SKILL.md +3 -1
- package/package.json +2 -1
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
import { describe, it, before, after, beforeEach, afterEach } from "node:test";
|
|
12
12
|
import assert from "node:assert/strict";
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
13
14
|
import * as fs from "node:fs";
|
|
15
|
+
import * as os from "node:os";
|
|
14
16
|
import * as path from "node:path";
|
|
15
17
|
import type { MockPi } from "../support/helpers.ts";
|
|
16
18
|
import {
|
|
@@ -112,11 +114,15 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
112
114
|
removeTempDir(tempDir);
|
|
113
115
|
});
|
|
114
116
|
|
|
115
|
-
function makeExecutor(
|
|
117
|
+
function makeExecutor(
|
|
118
|
+
agents = [makeAgent("echo")],
|
|
119
|
+
state = { baseCwd: tempDir, currentSessionId: null, asyncJobs: new Map(), foregroundControls: new Map(), lastForegroundControlId: null },
|
|
120
|
+
config: Record<string, unknown> = {},
|
|
121
|
+
) {
|
|
116
122
|
return createSubagentExecutor({
|
|
117
123
|
pi: { events: createEventBus(), getSessionName: () => undefined },
|
|
118
|
-
state
|
|
119
|
-
config
|
|
124
|
+
state,
|
|
125
|
+
config,
|
|
120
126
|
asyncByDefault: false,
|
|
121
127
|
tempArtifactsDir: tempDir,
|
|
122
128
|
getSubagentSessionRoot: () => tempDir,
|
|
@@ -125,6 +131,12 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
125
131
|
});
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
function git(args: string[]): string {
|
|
135
|
+
const result = spawnSync("git", args, { cwd: tempDir, encoding: "utf-8" });
|
|
136
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
137
|
+
return result.stdout.trim();
|
|
138
|
+
}
|
|
139
|
+
|
|
128
140
|
function readLastCallArgs(): string[] {
|
|
129
141
|
const callFile = fs.readdirSync(mockPi.dir).find((name) => name.startsWith("call-"));
|
|
130
142
|
assert.ok(callFile, "expected a recorded mock pi call");
|
|
@@ -171,6 +183,124 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
171
183
|
assert.equal(ok, 2);
|
|
172
184
|
});
|
|
173
185
|
|
|
186
|
+
it("tracks every concurrently running foreground child", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
187
|
+
for (let index = 0; index < 3; index++) mockPi.onCall({ output: `Review ${index} complete`, delay: 250 });
|
|
188
|
+
const agents = [makeAgent("reviewer")];
|
|
189
|
+
const state = {
|
|
190
|
+
baseCwd: tempDir,
|
|
191
|
+
currentSessionId: null,
|
|
192
|
+
asyncJobs: new Map(),
|
|
193
|
+
foregroundControls: new Map(),
|
|
194
|
+
lastForegroundControlId: null,
|
|
195
|
+
};
|
|
196
|
+
const executor = makeExecutor(agents, state);
|
|
197
|
+
const executionPromise = executor.execute(
|
|
198
|
+
"parallel-foreground-fleet",
|
|
199
|
+
{
|
|
200
|
+
tasks: [
|
|
201
|
+
{ agent: "reviewer", task: "Review correctness" },
|
|
202
|
+
{ agent: "reviewer", task: "Review quality" },
|
|
203
|
+
{ agent: "reviewer", task: "Review tests" },
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
new AbortController().signal,
|
|
207
|
+
undefined,
|
|
208
|
+
makeMinimalCtx(tempDir),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
for (let attempt = 0; attempt < 40; attempt++) {
|
|
212
|
+
const active = [...state.foregroundControls.values()][0]?.activeChildren;
|
|
213
|
+
if (active?.size === 3) break;
|
|
214
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
215
|
+
}
|
|
216
|
+
const control = [...state.foregroundControls.values()][0];
|
|
217
|
+
assert.ok(control);
|
|
218
|
+
assert.deepEqual([...control.activeChildren.values()].map((child) => child.description), [
|
|
219
|
+
"Review correctness",
|
|
220
|
+
"Review quality",
|
|
221
|
+
"Review tests",
|
|
222
|
+
]);
|
|
223
|
+
assert.equal(control.activeChildren.size, 3);
|
|
224
|
+
|
|
225
|
+
const result = await executionPromise;
|
|
226
|
+
assert.equal(result.isError, undefined);
|
|
227
|
+
assert.equal(state.foregroundControls.size, 0);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("publishes a durable handoff before cleaning foreground parallel worktrees", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
|
|
231
|
+
git(["init"]);
|
|
232
|
+
git(["config", "user.email", "test@example.com"]);
|
|
233
|
+
git(["config", "user.name", "Test User"]);
|
|
234
|
+
fs.writeFileSync(path.join(tempDir, "tracked.txt"), "base\n", "utf-8");
|
|
235
|
+
git(["add", "tracked.txt"]);
|
|
236
|
+
git(["commit", "-m", "initial"]);
|
|
237
|
+
mockPi.onCall({ output: "Worktree task complete" });
|
|
238
|
+
const executor = makeExecutor();
|
|
239
|
+
const result = await executor.execute(
|
|
240
|
+
"foreground-worktree-handoff",
|
|
241
|
+
{ tasks: [{ agent: "echo", task: "Work in isolation" }], worktree: true },
|
|
242
|
+
new AbortController().signal,
|
|
243
|
+
undefined,
|
|
244
|
+
makeMinimalCtx(tempDir),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
assert.equal(result.isError, undefined);
|
|
248
|
+
assert.equal(result.details?.parallelHandoff?.version, 1);
|
|
249
|
+
assert.equal(result.details?.parallelHandoff?.cleanupState, "complete");
|
|
250
|
+
assert.match(result.content[0]?.text ?? "", /Parallel handoff:/);
|
|
251
|
+
const handoffPath = result.details!.parallelHandoff!.path;
|
|
252
|
+
const handoff = JSON.parse(fs.readFileSync(handoffPath, "utf-8")) as {
|
|
253
|
+
groups: Array<{ children: Array<{ agent: string; summary: string; patch: { path: string } }>; cleanup: { state: string; tasks: Array<{ path: string; worktreeRemoved: boolean; branchRemoved: boolean }> } }>;
|
|
254
|
+
};
|
|
255
|
+
assert.equal(handoff.groups[0]!.children[0]!.agent, "echo");
|
|
256
|
+
assert.equal(handoff.groups[0]!.children[0]!.summary, "Worktree task complete");
|
|
257
|
+
assert.equal(fs.existsSync(handoff.groups[0]!.children[0]!.patch.path), true);
|
|
258
|
+
assert.ok(result.details?.runId);
|
|
259
|
+
assert.ok(handoff.groups[0]!.children[0]!.patch.path.includes(`${path.sep}worktree-diffs${path.sep}${result.details.runId}${path.sep}`));
|
|
260
|
+
assert.equal(handoff.groups[0]!.cleanup.state, "complete");
|
|
261
|
+
assert.equal(handoff.groups[0]!.cleanup.tasks[0]!.worktreeRemoved, true);
|
|
262
|
+
assert.equal(handoff.groups[0]!.cleanup.tasks[0]!.branchRemoved, true);
|
|
263
|
+
assert.equal(fs.existsSync(handoff.groups[0]!.cleanup.tasks[0]!.path), false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("keeps worktree parallel runs successful when handoff manifest writing fails", { skip: !createSubagentExecutor || process.platform === "win32" ? "executor unavailable or worktree paths differ on Windows" : undefined }, async () => {
|
|
267
|
+
git(["init"]);
|
|
268
|
+
git(["config", "user.email", "test@example.com"]);
|
|
269
|
+
git(["config", "user.name", "Test User"]);
|
|
270
|
+
fs.writeFileSync(path.join(tempDir, "tracked.txt"), "base\n", "utf-8");
|
|
271
|
+
git(["add", "tracked.txt"]);
|
|
272
|
+
git(["commit", "-m", "initial"]);
|
|
273
|
+
const sessionDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-session-"));
|
|
274
|
+
try {
|
|
275
|
+
const artifactsDir = path.join(sessionDir, "subagent-artifacts");
|
|
276
|
+
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
277
|
+
fs.writeFileSync(path.join(artifactsDir, "handoffs"), "not a directory", "utf-8");
|
|
278
|
+
mockPi.onCall({ output: "Worktree task complete" });
|
|
279
|
+
const executor = makeExecutor([makeAgent("echo")], undefined, { artifactDir: "session" });
|
|
280
|
+
const ctx = {
|
|
281
|
+
...makeMinimalCtx(tempDir),
|
|
282
|
+
sessionManager: {
|
|
283
|
+
getSessionId: () => "session-123",
|
|
284
|
+
getSessionFile: () => path.join(sessionDir, "session.jsonl"),
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
const result = await executor.execute(
|
|
288
|
+
"foreground-worktree-handoff-collision",
|
|
289
|
+
{ tasks: [{ agent: "echo", task: "Work in isolation" }], worktree: true },
|
|
290
|
+
new AbortController().signal,
|
|
291
|
+
undefined,
|
|
292
|
+
ctx,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
assert.equal(result.isError, undefined);
|
|
296
|
+
assert.equal(result.details?.parallelHandoff, undefined);
|
|
297
|
+
assert.match(result.content[0]?.text ?? "", /Parallel handoff unavailable:/);
|
|
298
|
+
assert.doesNotMatch(git(["worktree", "list", "--porcelain"]), /pi-parallel-/);
|
|
299
|
+
} finally {
|
|
300
|
+
fs.rmSync(sessionDir, { recursive: true, force: true });
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
174
304
|
it("treats parallel action aliases with tasks as top-level parallel execution", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
175
305
|
for (const action of ["parallel", "PARALLEL", "tasks"]) {
|
|
176
306
|
mockPi.reset();
|
|
@@ -191,6 +321,42 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
191
321
|
}
|
|
192
322
|
});
|
|
193
323
|
|
|
324
|
+
it("does not apply agent acceptance defaults to top-level parallel tasks", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
325
|
+
mockPi.onCall({ output: "parallel response without explicit acceptance" });
|
|
326
|
+
const executor = makeExecutor([
|
|
327
|
+
makeAgent("echo", { defaultAcceptance: { level: "none", reason: "single launches only" } }),
|
|
328
|
+
]);
|
|
329
|
+
|
|
330
|
+
const result = await executor.execute(
|
|
331
|
+
"parallel-agent-acceptance-default",
|
|
332
|
+
{ tasks: [{ agent: "echo", task: "Return a concise answer" }] },
|
|
333
|
+
new AbortController().signal,
|
|
334
|
+
undefined,
|
|
335
|
+
makeMinimalCtx(tempDir),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
assert.equal(result.isError, undefined);
|
|
339
|
+
assert.equal(result.details?.results?.[0]?.acceptance?.explicit, false);
|
|
340
|
+
assert.equal(result.details?.results?.[0]?.acceptance?.effectiveAcceptance.level, "attested");
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("applies agent acceptance roles to inferred parallel acceptance", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
344
|
+
mockPi.onCall({ output: "exploration complete" });
|
|
345
|
+
const executor = makeExecutor([
|
|
346
|
+
makeAgent("worker", { acceptanceRole: "read-only" }),
|
|
347
|
+
]);
|
|
348
|
+
|
|
349
|
+
const result = await executor.execute(
|
|
350
|
+
"parallel-agent-acceptance-role",
|
|
351
|
+
{ tasks: [{ agent: "worker", task: "Explore the authentication flow" }] },
|
|
352
|
+
new AbortController().signal,
|
|
353
|
+
undefined,
|
|
354
|
+
makeMinimalCtx(tempDir),
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
assert.equal(result.details?.results?.[0]?.acceptance?.effectiveAcceptance.level, "attested");
|
|
358
|
+
});
|
|
359
|
+
|
|
194
360
|
it("top-level parallel output saves use per-task output paths", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
195
361
|
mockPi.onCall({ output: "Saved report" });
|
|
196
362
|
const executor = makeExecutor();
|
|
@@ -211,6 +377,32 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
211
377
|
assert.equal(result.details?.results?.[0]?.savedOutputPath, outputPath);
|
|
212
378
|
});
|
|
213
379
|
|
|
380
|
+
it("top-level parallel tasks support outputSchema", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
381
|
+
mockPi.onCall({ matchArgIncludes: "First structured", output: "first", structuredOutput: { ok: true, item: "first" } });
|
|
382
|
+
mockPi.onCall({ matchArgIncludes: "Second structured", output: "second", structuredOutput: { ok: true, item: "second" } });
|
|
383
|
+
const executor = makeExecutor();
|
|
384
|
+
|
|
385
|
+
const result = await executor.execute(
|
|
386
|
+
"parallel-schema",
|
|
387
|
+
{
|
|
388
|
+
tasks: [
|
|
389
|
+
{ agent: "echo", task: "First structured", outputSchema: { type: "object", required: ["ok", "item"], properties: { ok: { type: "boolean" }, item: { type: "string" } } } },
|
|
390
|
+
{ agent: "echo", task: "Second structured", outputSchema: { type: "object", required: ["ok", "item"], properties: { ok: { type: "boolean" }, item: { type: "string" } } } },
|
|
391
|
+
],
|
|
392
|
+
acceptance: false,
|
|
393
|
+
},
|
|
394
|
+
new AbortController().signal,
|
|
395
|
+
undefined,
|
|
396
|
+
makeMinimalCtx(tempDir),
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
assert.equal(result.isError, undefined);
|
|
400
|
+
assert.deepEqual(result.details?.results?.map((item: { structuredOutput?: unknown }) => item.structuredOutput), [
|
|
401
|
+
{ ok: true, item: "first" },
|
|
402
|
+
{ ok: true, item: "second" },
|
|
403
|
+
]);
|
|
404
|
+
});
|
|
405
|
+
|
|
214
406
|
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
407
|
mockPi.onCall({ matchArgIncludes: "Slow review", delay: 10000 });
|
|
216
408
|
mockPi.onCall({ matchArgIncludes: "Fast review", output: "fast done" });
|
|
@@ -306,6 +498,38 @@ describe("parallel agent execution", { skip: !piAvailable ? "pi packages not ava
|
|
|
306
498
|
assert.equal(mockPi.callCount(), 0);
|
|
307
499
|
});
|
|
308
500
|
|
|
501
|
+
for (const outputOverride of [undefined, true] as const) {
|
|
502
|
+
it(`isolates foreground inherited output (${outputOverride === true ? "output:true" : "omitted output"})`, { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
503
|
+
mockPi.onCall({ matchArgIncludes: "Write A", output: "first foreground report" });
|
|
504
|
+
mockPi.onCall({ matchArgIncludes: "Write B", output: "second foreground report" });
|
|
505
|
+
const executor = makeExecutor([makeAgent("echo", { output: "context.md" })]);
|
|
506
|
+
const tasks = [
|
|
507
|
+
{ agent: "echo", task: "Write A", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
|
|
508
|
+
{ agent: "echo", task: "Write B", ...(outputOverride !== undefined ? { output: outputOverride } : {}) },
|
|
509
|
+
];
|
|
510
|
+
|
|
511
|
+
const result = await executor.execute(
|
|
512
|
+
`parallel-inherited-output-${outputOverride === true ? "true" : "omitted"}`,
|
|
513
|
+
{ tasks },
|
|
514
|
+
new AbortController().signal,
|
|
515
|
+
undefined,
|
|
516
|
+
makeMinimalCtx(tempDir),
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
const runId = result.details?.runId;
|
|
520
|
+
assert.ok(runId, "expected run id in details");
|
|
521
|
+
const outputDir = path.join(tempDir, ".pi-subagents", "artifacts", "outputs", runId);
|
|
522
|
+
const firstOutputPath = path.join(outputDir, "parallel-0", "0-echo", "context.md");
|
|
523
|
+
const secondOutputPath = path.join(outputDir, "parallel-0", "1-echo", "context.md");
|
|
524
|
+
assert.equal(result.isError, undefined);
|
|
525
|
+
assert.equal(mockPi.callCount(), 2);
|
|
526
|
+
assert.equal(fs.readFileSync(firstOutputPath, "utf-8"), "first foreground report");
|
|
527
|
+
assert.equal(fs.readFileSync(secondOutputPath, "utf-8"), "second foreground report");
|
|
528
|
+
assert.equal(result.details?.results?.[0]?.savedOutputPath, firstOutputPath);
|
|
529
|
+
assert.equal(result.details?.results?.[1]?.savedOutputPath, secondOutputPath);
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
309
533
|
it("treats string false as disabled output in top-level parallel runs", { skip: !createSubagentExecutor ? "executor not importable" : undefined }, async () => {
|
|
310
534
|
mockPi.onCall({ output: "Review done" });
|
|
311
535
|
const executor = makeExecutor();
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
|
+
import { keyText } from "@selesai/code";
|
|
3
4
|
|
|
4
5
|
type RenderSubagentResult = (
|
|
5
6
|
result: {
|
|
6
7
|
content: Array<{ type: "text"; text: string }>;
|
|
8
|
+
isError?: boolean;
|
|
7
9
|
details?: {
|
|
8
10
|
mode: "single" | "parallel" | "chain" | "management";
|
|
9
|
-
context?: "fresh" | "fork";
|
|
11
|
+
context?: "fresh" | "fork" | "mixed";
|
|
10
12
|
results: unknown[];
|
|
11
13
|
};
|
|
12
14
|
},
|
|
@@ -26,9 +28,25 @@ const theme = {
|
|
|
26
28
|
fg: (_name: string, text: string) => text,
|
|
27
29
|
bold: (text: string) => text,
|
|
28
30
|
};
|
|
31
|
+
const expandKey = keyText("app.tools.expand");
|
|
32
|
+
const expandHint = `Press ${expandKey} for full output`;
|
|
29
33
|
|
|
30
34
|
const emptyUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
31
35
|
|
|
36
|
+
function nestedChild(id: string, state: "running" | "complete" | "failed" = "running") {
|
|
37
|
+
return {
|
|
38
|
+
id,
|
|
39
|
+
parentRunId: "root",
|
|
40
|
+
parentStepIndex: 0,
|
|
41
|
+
depth: 1,
|
|
42
|
+
path: [{ runId: "root", stepIndex: 0, agent: "owner" }],
|
|
43
|
+
state,
|
|
44
|
+
agent: id,
|
|
45
|
+
lastUpdate: 1_700_000_002_000,
|
|
46
|
+
...(state === "running" ? { currentTool: "bash" } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
function firstGrapheme(text: string): string {
|
|
33
51
|
return Array.from(text.trimStart())[0] ?? "";
|
|
34
52
|
}
|
|
@@ -50,6 +68,148 @@ function withTerminalWidth<T>(columns: number, fn: () => T): T {
|
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
describe("renderSubagentResult fork indicator", () => {
|
|
71
|
+
it("renders result-owned nested children for foreground single, parallel, and chain runs", () => {
|
|
72
|
+
const cases = [
|
|
73
|
+
{
|
|
74
|
+
mode: "single" as const,
|
|
75
|
+
results: [{ agent: "single", task: "run", exitCode: 0, messages: [], usage: emptyUsage, children: [nestedChild("single-child")] }],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
mode: "parallel" as const,
|
|
79
|
+
totalSteps: 2,
|
|
80
|
+
results: [
|
|
81
|
+
{ agent: "parallel-a", task: "run", exitCode: 0, messages: [], usage: emptyUsage, progress: { status: "running", index: 0, agent: "parallel-a", toolCount: 0, tokens: 0, durationMs: 0 }, children: [nestedChild("parallel-child-a")] },
|
|
82
|
+
{ agent: "parallel-b", task: "run", exitCode: 0, messages: [], usage: emptyUsage, progress: { status: "running", index: 1, agent: "parallel-b", toolCount: 0, tokens: 0, durationMs: 0 }, children: [nestedChild("parallel-child-b")] },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
mode: "chain" as const,
|
|
87
|
+
chainAgents: ["chain-a", "chain-b"],
|
|
88
|
+
results: [
|
|
89
|
+
{ agent: "chain-a", task: "run", exitCode: 0, messages: [], usage: emptyUsage, progress: { status: "running", index: 0, agent: "chain-a", toolCount: 0, tokens: 0, durationMs: 0 }, children: [nestedChild("chain-child-a")] },
|
|
90
|
+
{ agent: "chain-b", task: "run", exitCode: 0, messages: [], usage: emptyUsage, progress: { status: "running", index: 1, agent: "chain-b", toolCount: 0, tokens: 0, durationMs: 0 }, children: [nestedChild("chain-child-b")] },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
for (const details of cases) {
|
|
96
|
+
const widget = renderSubagentResult!({ content: [{ type: "text", text: "running" }], details }, { expanded: true }, theme);
|
|
97
|
+
const text = widget.render(160).join("\n");
|
|
98
|
+
for (const result of details.results) {
|
|
99
|
+
assert.match(text, new RegExp(`${result.children[0].id} · running`));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("renders result-owned nested children for terminal foreground results", () => {
|
|
105
|
+
const widget = renderSubagentResult!({
|
|
106
|
+
content: [{ type: "text", text: "failed" }],
|
|
107
|
+
details: {
|
|
108
|
+
mode: "parallel",
|
|
109
|
+
totalSteps: 1,
|
|
110
|
+
results: [{
|
|
111
|
+
agent: "owner",
|
|
112
|
+
task: "run",
|
|
113
|
+
exitCode: 1,
|
|
114
|
+
error: "failed",
|
|
115
|
+
messages: [],
|
|
116
|
+
usage: emptyUsage,
|
|
117
|
+
children: [nestedChild("terminal-child", "failed")],
|
|
118
|
+
}],
|
|
119
|
+
},
|
|
120
|
+
}, { expanded: true }, theme);
|
|
121
|
+
|
|
122
|
+
assert.match(widget.render(160).join("\n"), /terminal-child · failed/);
|
|
123
|
+
|
|
124
|
+
const compact = renderSubagentResult!({
|
|
125
|
+
content: [{ type: "text", text: "done" }],
|
|
126
|
+
details: {
|
|
127
|
+
mode: "single",
|
|
128
|
+
results: [{
|
|
129
|
+
agent: "owner",
|
|
130
|
+
task: "run",
|
|
131
|
+
exitCode: 0,
|
|
132
|
+
messages: [],
|
|
133
|
+
usage: emptyUsage,
|
|
134
|
+
children: [nestedChild("compact-terminal-child", "complete")],
|
|
135
|
+
}],
|
|
136
|
+
},
|
|
137
|
+
}, { expanded: false }, theme).render(160).join("\n");
|
|
138
|
+
assert.match(compact, /↳ \[\d{2}:\d{2}:\d{2}\] \+1 nested run \(1 complete\)/);
|
|
139
|
+
assert.doesNotMatch(compact, /compact-terminal-child · complete/);
|
|
140
|
+
});
|
|
141
|
+
it("collapses multiline structured management output to a first-line summary", () => {
|
|
142
|
+
const output = `\n${"Managed agents: ".padEnd(220, "x")}\n- reviewer\n- writer`;
|
|
143
|
+
const widget = renderSubagentResult!({
|
|
144
|
+
content: [{ type: "text", text: output }],
|
|
145
|
+
details: { mode: "management", context: "fork", results: [] },
|
|
146
|
+
}, { expanded: false }, theme);
|
|
147
|
+
|
|
148
|
+
const lines = widget.render(120).map((line) => line.trimEnd());
|
|
149
|
+
assert.match(lines[0]!, /^\[fork\] Managed agents:/);
|
|
150
|
+
assert.match(lines[0]!, /…$/);
|
|
151
|
+
const hintLineIndex = lines.findIndex((line) => line.includes(expandHint) || (expandKey === "" && line.includes("Press ") && line.includes(" for full output")));
|
|
152
|
+
assert.ok(hintLineIndex > 0);
|
|
153
|
+
assert.doesNotMatch(lines[0]!, /reviewer/);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("keeps multiline structured zero-result errors visible", () => {
|
|
157
|
+
const widget = renderSubagentResult!({
|
|
158
|
+
content: [{ type: "text", text: "Error: management failed\nfirst diagnostic\nsecond diagnostic" }],
|
|
159
|
+
isError: true,
|
|
160
|
+
details: { mode: "management", results: [] },
|
|
161
|
+
}, { expanded: false }, theme);
|
|
162
|
+
|
|
163
|
+
const text = widget.render(120).join("\n");
|
|
164
|
+
assert.match(text, /Error: management failed/);
|
|
165
|
+
assert.match(text, /first diagnostic/);
|
|
166
|
+
assert.match(text, /second diagnostic/);
|
|
167
|
+
assert.ok(!text.includes(expandHint));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("keeps full multiline structured output when expanded", () => {
|
|
171
|
+
const output = "Managed agents:\n- reviewer\n- writer";
|
|
172
|
+
const widget = renderSubagentResult!({
|
|
173
|
+
content: [{ type: "text", text: output }],
|
|
174
|
+
details: { mode: "management", results: [] },
|
|
175
|
+
}, { expanded: true }, theme);
|
|
176
|
+
|
|
177
|
+
const text = widget.render(120).join("\n");
|
|
178
|
+
assert.match(text, /Managed agents:/);
|
|
179
|
+
assert.match(text, /- reviewer/);
|
|
180
|
+
assert.match(text, /- writer/);
|
|
181
|
+
assert.ok(!text.includes(expandHint));
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("collapses multiline structured single output using the same contract", () => {
|
|
185
|
+
const widget = renderSubagentResult!({
|
|
186
|
+
content: [{ type: "text", text: "Run status:\nState: running\nTranscript: available" }],
|
|
187
|
+
details: { mode: "single", results: [] },
|
|
188
|
+
}, { expanded: false }, theme);
|
|
189
|
+
|
|
190
|
+
const text = widget.render(120).join("\n");
|
|
191
|
+
assert.match(text, /^Run status:/);
|
|
192
|
+
assert.match(text, /3 lines/);
|
|
193
|
+
assert.ok(text.includes(expandHint) || (expandKey === "" && text.includes("Press ") && text.includes(" for full output")));
|
|
194
|
+
assert.doesNotMatch(text, /State: running/);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("preserves unstructured multiline and structured single-line output", () => {
|
|
198
|
+
const unstructured = renderSubagentResult!({
|
|
199
|
+
content: [{ type: "text", text: "Error:\nfirst detail\nsecond detail" }],
|
|
200
|
+
}, { expanded: false }, theme).render(120).join("\n");
|
|
201
|
+
assert.match(unstructured, /first detail/);
|
|
202
|
+
assert.match(unstructured, /second detail/);
|
|
203
|
+
assert.ok(!unstructured.includes(expandHint));
|
|
204
|
+
|
|
205
|
+
const singleLine = renderSubagentResult!({
|
|
206
|
+
content: [{ type: "text", text: "No active async run transcript is available." }],
|
|
207
|
+
details: { mode: "single", results: [] },
|
|
208
|
+
}, { expanded: false }, theme).render(120).map((line) => line.trimEnd()).join("\n");
|
|
209
|
+
assert.equal(singleLine, "No active async run transcript is available.");
|
|
210
|
+
assert.ok(!singleLine.includes(expandHint));
|
|
211
|
+
});
|
|
212
|
+
|
|
53
213
|
it("shows [fork] when details are empty but context is fork", () => {
|
|
54
214
|
const widget = renderSubagentResult!({
|
|
55
215
|
content: [{ type: "text", text: "Async: reviewer [abc123]" }],
|
|
@@ -60,24 +220,92 @@ describe("renderSubagentResult fork indicator", () => {
|
|
|
60
220
|
assert.match(text, /\[fork\]/);
|
|
61
221
|
});
|
|
62
222
|
|
|
63
|
-
it("shows
|
|
223
|
+
it("shows nested foreground children with timestamps on running single results", () => {
|
|
64
224
|
const widget = renderSubagentResult!({
|
|
65
|
-
content: [{ type: "text", text: "
|
|
225
|
+
content: [{ type: "text", text: "running" }],
|
|
66
226
|
details: {
|
|
67
227
|
mode: "single",
|
|
68
|
-
context: "fork",
|
|
69
228
|
results: [{
|
|
70
|
-
agent: "
|
|
71
|
-
task: "
|
|
229
|
+
agent: "execute",
|
|
230
|
+
task: "run plan",
|
|
72
231
|
exitCode: 0,
|
|
73
232
|
messages: [],
|
|
74
233
|
usage: emptyUsage,
|
|
234
|
+
progress: { status: "running", index: 0, agent: "execute", toolCount: 1, tokens: 10, durationMs: 1_000, lastActivityAt: 1_700_000_001_000 },
|
|
235
|
+
children: [{
|
|
236
|
+
id: "impl-1",
|
|
237
|
+
parentRunId: "root",
|
|
238
|
+
parentStepIndex: 0,
|
|
239
|
+
depth: 1,
|
|
240
|
+
path: [{ runId: "root", stepIndex: 0, agent: "execute" }],
|
|
241
|
+
state: "running",
|
|
242
|
+
agent: "implement",
|
|
243
|
+
lastUpdate: 1_700_000_002_000,
|
|
244
|
+
currentTool: "bash",
|
|
245
|
+
}],
|
|
75
246
|
}],
|
|
76
247
|
},
|
|
77
248
|
}, { expanded: false }, theme);
|
|
78
249
|
|
|
79
250
|
const text = widget.render(120).join("\n");
|
|
80
|
-
assert.match(text,
|
|
251
|
+
assert.match(text, /↳ \[\d{2}:\d{2}:\d{2}\] \+1 nested run \(1 running\)/);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("shows [fresh] and [fork] on single-result headers", () => {
|
|
255
|
+
for (const context of ["fresh", "fork"] as const) {
|
|
256
|
+
const widget = renderSubagentResult!({
|
|
257
|
+
content: [{ type: "text", text: "done" }],
|
|
258
|
+
details: {
|
|
259
|
+
mode: "single",
|
|
260
|
+
context,
|
|
261
|
+
results: [{
|
|
262
|
+
agent: "reviewer",
|
|
263
|
+
task: "review",
|
|
264
|
+
context,
|
|
265
|
+
exitCode: 0,
|
|
266
|
+
messages: [],
|
|
267
|
+
usage: emptyUsage,
|
|
268
|
+
}],
|
|
269
|
+
},
|
|
270
|
+
}, { expanded: false }, theme);
|
|
271
|
+
|
|
272
|
+
const text = widget.render(120).join("\n");
|
|
273
|
+
assert.match(text, new RegExp(`\\[${context}\\]`));
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("shows [mixed] on mixed runs and per-child context badges", () => {
|
|
278
|
+
const compact = renderSubagentResult!({
|
|
279
|
+
content: [{ type: "text", text: "done" }],
|
|
280
|
+
details: {
|
|
281
|
+
mode: "parallel",
|
|
282
|
+
context: "mixed",
|
|
283
|
+
results: [
|
|
284
|
+
{ agent: "scout", task: "scan", context: "fresh", exitCode: 0, messages: [], usage: emptyUsage },
|
|
285
|
+
{ agent: "worker", task: "fix", context: "fork", exitCode: 0, messages: [], usage: emptyUsage },
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
}, { expanded: false }, theme).render(160).join("\n");
|
|
289
|
+
|
|
290
|
+
assert.match(compact, /parallel \[mixed\]/);
|
|
291
|
+
assert.match(compact, /scout \[fresh\]/);
|
|
292
|
+
assert.match(compact, /worker \[fork\]/);
|
|
293
|
+
|
|
294
|
+
const expanded = renderSubagentResult!({
|
|
295
|
+
content: [{ type: "text", text: "done" }],
|
|
296
|
+
details: {
|
|
297
|
+
mode: "parallel",
|
|
298
|
+
context: "mixed",
|
|
299
|
+
results: [
|
|
300
|
+
{ agent: "scout", task: "scan", context: "fresh", exitCode: 0, messages: [], usage: emptyUsage },
|
|
301
|
+
{ agent: "worker", task: "fix", context: "fork", exitCode: 0, messages: [], usage: emptyUsage },
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
}, { expanded: true }, theme).render(160).join("\n");
|
|
305
|
+
|
|
306
|
+
assert.match(expanded, /parallel \[mixed\]/);
|
|
307
|
+
assert.match(expanded, /scout \[fresh\]/);
|
|
308
|
+
assert.match(expanded, /worker \[fork\]/);
|
|
81
309
|
});
|
|
82
310
|
|
|
83
311
|
it("uses compacted tool-call summaries when messages were stripped", () => {
|
|
@@ -333,6 +333,33 @@ describe("subagent async widget rendering", () => {
|
|
|
333
333
|
assert.match(text, /Agent 3\/3: reviewer · complete · 1\.5k token/);
|
|
334
334
|
});
|
|
335
335
|
|
|
336
|
+
it("shows async job and step context badges", () => {
|
|
337
|
+
const now = Date.now();
|
|
338
|
+
const text = buildWidgetLines([
|
|
339
|
+
{
|
|
340
|
+
asyncId: "run-1",
|
|
341
|
+
asyncDir: "/tmp/1",
|
|
342
|
+
status: "running",
|
|
343
|
+
mode: "parallel",
|
|
344
|
+
context: "mixed",
|
|
345
|
+
agents: ["scout", "worker"],
|
|
346
|
+
activeParallelGroup: true,
|
|
347
|
+
runningSteps: 2,
|
|
348
|
+
completedSteps: 0,
|
|
349
|
+
stepsTotal: 2,
|
|
350
|
+
updatedAt: now,
|
|
351
|
+
steps: [
|
|
352
|
+
{ agent: "scout", context: "fresh", status: "running", lastActivityAt: now },
|
|
353
|
+
{ agent: "worker", context: "fork", status: "running", currentTool: "bash", currentToolStartedAt: now - 1000 },
|
|
354
|
+
],
|
|
355
|
+
},
|
|
356
|
+
], theme, 160).join("\n");
|
|
357
|
+
|
|
358
|
+
assert.match(text, /parallel \[mixed\]/);
|
|
359
|
+
assert.match(text, /Agent 1\/2: scout \[fresh\] · running/);
|
|
360
|
+
assert.match(text, /Agent 2\/2: worker \[fork\] · running/);
|
|
361
|
+
});
|
|
362
|
+
|
|
336
363
|
it("shows model and thinking for active async widget rows", () => {
|
|
337
364
|
const lines = buildWidgetLines([
|
|
338
365
|
{
|