@selesai/code 0.5.20 → 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-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 +1 -1
|
@@ -5,13 +5,30 @@ import registerSubagentNotify, {
|
|
|
5
5
|
buildCompletionDetails,
|
|
6
6
|
formatGroupedCompletion,
|
|
7
7
|
formatSingleCompletion,
|
|
8
|
+
parseSubagentNotifyContent,
|
|
8
9
|
type RegisterSubagentNotifyOptions,
|
|
9
10
|
type SubagentNotifyDetails,
|
|
10
11
|
} from "../../src/runs/background/notify.ts";
|
|
11
|
-
import { SUBAGENT_ASYNC_COMPLETE_EVENT } from "../../src/shared/types.ts";
|
|
12
|
+
import { SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT } from "../../src/shared/types.ts";
|
|
13
|
+
|
|
14
|
+
function createEventBus() {
|
|
15
|
+
const emitter = new EventEmitter();
|
|
16
|
+
return {
|
|
17
|
+
on(event: string, listener: (...args: unknown[]) => void) {
|
|
18
|
+
emitter.on(event, listener);
|
|
19
|
+
return () => emitter.off(event, listener);
|
|
20
|
+
},
|
|
21
|
+
emit(event: string, ...args: unknown[]) {
|
|
22
|
+
return emitter.emit(event, ...args);
|
|
23
|
+
},
|
|
24
|
+
listenerCount(event: string) {
|
|
25
|
+
return emitter.listenerCount(event);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
12
29
|
|
|
13
30
|
function createPi(currentSessionId = "session-1", registerOptions: RegisterSubagentNotifyOptions = {}) {
|
|
14
|
-
const events =
|
|
31
|
+
const events = createEventBus();
|
|
15
32
|
const sent: Array<{ message: unknown; options: unknown }> = [];
|
|
16
33
|
const pi = {
|
|
17
34
|
events,
|
|
@@ -22,13 +39,13 @@ function createPi(currentSessionId = "session-1", registerOptions: RegisterSubag
|
|
|
22
39
|
|
|
23
40
|
// Formatting-focused tests run with batching disabled so single completions
|
|
24
41
|
// emit synchronously. Batching behavior is covered by the dedicated suite below.
|
|
25
|
-
registerSubagentNotify(pi as never, { currentSessionId }, { batchConfig: { enabled: false }, ...registerOptions });
|
|
42
|
+
const notifier = registerSubagentNotify(pi as never, { currentSessionId }, { batchConfig: { enabled: false }, ...registerOptions });
|
|
26
43
|
|
|
27
|
-
return { events, sent };
|
|
44
|
+
return { events, sent, notifier, dispose: () => notifier.dispose() };
|
|
28
45
|
}
|
|
29
46
|
|
|
30
47
|
function createBatchingPi(clock: ReturnType<typeof createFakeClock>, currentSessionId = "session-a") {
|
|
31
|
-
const events =
|
|
48
|
+
const events = createEventBus();
|
|
32
49
|
const sent: Array<{ message: unknown; options: unknown }> = [];
|
|
33
50
|
const pi = {
|
|
34
51
|
events,
|
|
@@ -36,12 +53,12 @@ function createBatchingPi(clock: ReturnType<typeof createFakeClock>, currentSess
|
|
|
36
53
|
sent.push({ message, options });
|
|
37
54
|
},
|
|
38
55
|
};
|
|
39
|
-
registerSubagentNotify(pi as never, { currentSessionId }, {
|
|
56
|
+
const notifier = registerSubagentNotify(pi as never, { currentSessionId }, {
|
|
40
57
|
batchConfig: { enabled: true, debounceMs: 150, maxWaitMs: 1000, stragglerDebounceMs: 75, stragglerMaxWaitMs: 400, stragglerWindowMs: 2000 },
|
|
41
58
|
timers: clock.api,
|
|
42
59
|
now: clock.now,
|
|
43
60
|
});
|
|
44
|
-
return { events, sent };
|
|
61
|
+
return { events, sent, notifier, dispose: () => notifier.dispose() };
|
|
45
62
|
}
|
|
46
63
|
|
|
47
64
|
interface FakeJob {
|
|
@@ -117,6 +134,62 @@ describe("registerSubagentNotify", () => {
|
|
|
117
134
|
});
|
|
118
135
|
});
|
|
119
136
|
|
|
137
|
+
it("acknowledges direct delivery only after sendMessage accepts it", async () => {
|
|
138
|
+
const { notifier, sent } = createPi("session-a");
|
|
139
|
+
assert.equal(await notifier.deliver(completionResult({ id: "direct-accepted" })), true);
|
|
140
|
+
assert.equal(sent.length, 1);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("rejects a pending batch when the notifier is disposed", async () => {
|
|
144
|
+
const clock = createFakeClock();
|
|
145
|
+
const { notifier, sent } = createBatchingPi(clock);
|
|
146
|
+
const pending = notifier.deliver(completionResult({ id: "dispose-pending" }));
|
|
147
|
+
notifier.dispose();
|
|
148
|
+
assert.equal(await pending, false);
|
|
149
|
+
clock.advance(1000);
|
|
150
|
+
assert.equal(sent.length, 0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("wakes the originating session with recovered detached foreground output", () => {
|
|
154
|
+
const { events, sent } = createPi();
|
|
155
|
+
|
|
156
|
+
events.emit(SUBAGENT_FOREGROUND_COMPLETE_EVENT, {
|
|
157
|
+
id: "foreground-run:0",
|
|
158
|
+
runId: "foreground-run",
|
|
159
|
+
source: "foreground",
|
|
160
|
+
agent: "reviewer",
|
|
161
|
+
success: true,
|
|
162
|
+
summary: "Recovered final review",
|
|
163
|
+
exitCode: 0,
|
|
164
|
+
timestamp: 123,
|
|
165
|
+
sessionId: "session-1",
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
assert.equal(sent.length, 1);
|
|
169
|
+
assert.deepEqual(sent[0], {
|
|
170
|
+
message: {
|
|
171
|
+
customType: "subagent-notify",
|
|
172
|
+
content: "Detached foreground task completed: **reviewer**\n\nRecovered final review",
|
|
173
|
+
display: true,
|
|
174
|
+
},
|
|
175
|
+
options: { triggerTurn: true },
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("does not deliver detached foreground completion to another active session", () => {
|
|
180
|
+
const { events, sent } = createPi("session-2");
|
|
181
|
+
events.emit(SUBAGENT_FOREGROUND_COMPLETE_EVENT, {
|
|
182
|
+
id: "foreground-run:0",
|
|
183
|
+
source: "foreground",
|
|
184
|
+
agent: "reviewer",
|
|
185
|
+
success: true,
|
|
186
|
+
summary: "Recovered final review",
|
|
187
|
+
timestamp: 123,
|
|
188
|
+
sessionId: "session-1",
|
|
189
|
+
});
|
|
190
|
+
assert.equal(sent.length, 0);
|
|
191
|
+
});
|
|
192
|
+
|
|
120
193
|
it("preserves non-empty completion summaries", () => {
|
|
121
194
|
const { events, sent } = createPi();
|
|
122
195
|
const summary = " Done streaming\nAll clear ";
|
|
@@ -277,9 +350,71 @@ describe("registerSubagentNotify", () => {
|
|
|
277
350
|
assert.match((sent[0]!.message as { content: string }).content, /^Background task completed: \*\*alpha\*\*/);
|
|
278
351
|
assert.doesNotMatch((sent[0]!.message as { content: string }).content, /boom/);
|
|
279
352
|
});
|
|
353
|
+
|
|
354
|
+
it("disposes queued completions without emitting and is idempotent", () => {
|
|
355
|
+
const clock = createFakeClock();
|
|
356
|
+
const { events, sent, dispose } = createBatchingPi(clock);
|
|
357
|
+
|
|
358
|
+
events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, completionResult({ id: "dispose-held-1" }));
|
|
359
|
+
assert.equal(sent.length, 0);
|
|
360
|
+
assert.equal(events.listenerCount(SUBAGENT_ASYNC_COMPLETE_EVENT), 1);
|
|
361
|
+
assert.equal(events.listenerCount(SUBAGENT_FOREGROUND_COMPLETE_EVENT), 1);
|
|
362
|
+
|
|
363
|
+
dispose();
|
|
364
|
+
dispose();
|
|
365
|
+
assert.equal(events.listenerCount(SUBAGENT_ASYNC_COMPLETE_EVENT), 0);
|
|
366
|
+
assert.equal(events.listenerCount(SUBAGENT_FOREGROUND_COMPLETE_EVENT), 0);
|
|
367
|
+
|
|
368
|
+
clock.advance(1000);
|
|
369
|
+
assert.equal(sent.length, 0);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("does not let a disposed notifier affect another runtime", () => {
|
|
373
|
+
const oldClock = createFakeClock();
|
|
374
|
+
const oldRegistration = createBatchingPi(oldClock);
|
|
375
|
+
oldRegistration.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, completionResult({ id: "old-owner-held-1" }));
|
|
376
|
+
|
|
377
|
+
const newClock = createFakeClock();
|
|
378
|
+
const newRegistration = createBatchingPi(newClock);
|
|
379
|
+
oldRegistration.dispose();
|
|
380
|
+
oldClock.advance(1000);
|
|
381
|
+
assert.equal(oldRegistration.sent.length, 0);
|
|
382
|
+
|
|
383
|
+
newRegistration.events.emit(SUBAGENT_ASYNC_COMPLETE_EVENT, completionResult({ id: "new-owner-1" }));
|
|
384
|
+
newClock.advance(150);
|
|
385
|
+
assert.equal(newRegistration.sent.length, 1);
|
|
386
|
+
newRegistration.dispose();
|
|
387
|
+
});
|
|
280
388
|
});
|
|
281
389
|
|
|
282
390
|
describe("completion formatting helpers", () => {
|
|
391
|
+
it("formats and parses a parallel handoff without folding it into the result preview", () => {
|
|
392
|
+
const content = formatSingleCompletion({
|
|
393
|
+
agent: "worker",
|
|
394
|
+
status: "completed",
|
|
395
|
+
resultPreview: "Done",
|
|
396
|
+
handoffPath: "/tmp/run/handoff.json",
|
|
397
|
+
sessionLabel: "Session file",
|
|
398
|
+
sessionValue: "/tmp/session.jsonl",
|
|
399
|
+
});
|
|
400
|
+
assert.equal(content, "Background task completed: **worker**\n\nDone\n\nParallel handoff: /tmp/run/handoff.json\n\nSession file: /tmp/session.jsonl");
|
|
401
|
+
assert.deepEqual(parseSubagentNotifyContent(content), {
|
|
402
|
+
agent: "worker",
|
|
403
|
+
status: "completed",
|
|
404
|
+
resultPreview: "Done",
|
|
405
|
+
handoffPath: "/tmp/run/handoff.json",
|
|
406
|
+
sessionLabel: "session file",
|
|
407
|
+
sessionValue: "/tmp/session.jsonl",
|
|
408
|
+
});
|
|
409
|
+
assert.equal(buildCompletionDetails({
|
|
410
|
+
id: "run",
|
|
411
|
+
agent: "worker",
|
|
412
|
+
success: true,
|
|
413
|
+
summary: "Done",
|
|
414
|
+
parallelHandoff: { path: "/tmp/run/handoff.json" },
|
|
415
|
+
}).handoffPath, "/tmp/run/handoff.json");
|
|
416
|
+
});
|
|
417
|
+
|
|
283
418
|
it("formatSingleCompletion mirrors the in-handler single message shape", () => {
|
|
284
419
|
const content = formatSingleCompletion({
|
|
285
420
|
agent: "worker",
|
|
@@ -292,6 +427,25 @@ describe("completion formatting helpers", () => {
|
|
|
292
427
|
assert.equal(content, "Background task completed: **worker** (2/3)\n\nDone\n\nSession file: /tmp/session.jsonl");
|
|
293
428
|
});
|
|
294
429
|
|
|
430
|
+
it("parses detached foreground notification content for the custom renderer", () => {
|
|
431
|
+
const content = formatSingleCompletion({
|
|
432
|
+
agent: "reviewer",
|
|
433
|
+
status: "failed",
|
|
434
|
+
source: "foreground",
|
|
435
|
+
resultPreview: "Acceptance rejected",
|
|
436
|
+
sessionLabel: "Session file",
|
|
437
|
+
sessionValue: "/tmp/reviewer.jsonl",
|
|
438
|
+
});
|
|
439
|
+
assert.deepEqual(parseSubagentNotifyContent(content), {
|
|
440
|
+
agent: "reviewer",
|
|
441
|
+
status: "failed",
|
|
442
|
+
source: "foreground",
|
|
443
|
+
resultPreview: "Acceptance rejected",
|
|
444
|
+
sessionLabel: "session file",
|
|
445
|
+
sessionValue: "/tmp/reviewer.jsonl",
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
295
449
|
it("formatGroupedCompletion lists each agent with its summary and session", () => {
|
|
296
450
|
const content = formatGroupedCompletion([
|
|
297
451
|
{ agent: "alpha", status: "completed", resultPreview: "alpha done" },
|
|
@@ -305,6 +459,13 @@ describe("completion formatting helpers", () => {
|
|
|
305
459
|
);
|
|
306
460
|
});
|
|
307
461
|
|
|
462
|
+
it("reports false when Pi rejects sendMessage synchronously", async () => {
|
|
463
|
+
const pi = { events: createEventBus(), sendMessage() { throw new Error("runtime inactive"); } };
|
|
464
|
+
const notifier = registerSubagentNotify(pi as never, { currentSessionId: "session-a" }, { batchConfig: { enabled: false } });
|
|
465
|
+
assert.equal(await notifier.deliver(completionResult({ id: "direct-rejected" })), false);
|
|
466
|
+
notifier.dispose();
|
|
467
|
+
});
|
|
468
|
+
|
|
308
469
|
it("buildCompletionDetails derives paused status from state and summary", () => {
|
|
309
470
|
assert.equal(buildCompletionDetails({ id: "x", agent: "w", success: false, state: "paused", summary: "Paused after interrupt.", timestamp: 1 }).status, "paused");
|
|
310
471
|
assert.equal(buildCompletionDetails({ id: "x", agent: "w", success: false, summary: "boom", exitCode: 1, timestamp: 1 }).status, "failed");
|
|
@@ -11,32 +11,84 @@ const oldPiScopePattern = /@mariozechner\/pi-/;
|
|
|
11
11
|
const piPackageJsonSubpathPattern = /@earendil-works\/pi-[^"']+\/package\.json/;
|
|
12
12
|
const cjsPiPackageResolutionPattern = /require(?:\.resolve)?\(\s*["']@earendil-works\/pi-/;
|
|
13
13
|
const exactVersionPattern = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
14
|
+
const hostPeerPackages = [
|
|
15
|
+
"@earendil-works/pi-agent-core",
|
|
16
|
+
"@earendil-works/pi-ai",
|
|
17
|
+
"@selesai/code",
|
|
18
|
+
"@earendil-works/pi-tui",
|
|
19
|
+
] as const;
|
|
20
|
+
const expectedHostPeerRanges = {
|
|
21
|
+
"@earendil-works/pi-agent-core": "*",
|
|
22
|
+
"@earendil-works/pi-ai": ">=0.80.0",
|
|
23
|
+
"@selesai/code": "*",
|
|
24
|
+
"@earendil-works/pi-tui": "*",
|
|
25
|
+
} satisfies Record<(typeof hostPeerPackages)[number], string>;
|
|
26
|
+
const expectedHostDevVersions = {
|
|
27
|
+
"@earendil-works/pi-agent-core": "0.81.0",
|
|
28
|
+
"@earendil-works/pi-ai": "0.81.0",
|
|
29
|
+
"@selesai/code": "0.81.0",
|
|
30
|
+
"@earendil-works/pi-tui": "0.81.0",
|
|
31
|
+
} satisfies Record<(typeof hostPeerPackages)[number], string>;
|
|
14
32
|
|
|
15
|
-
function
|
|
33
|
+
function collectSourceFiles(dir: string): string[] {
|
|
16
34
|
const files: string[] = [];
|
|
17
35
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
18
36
|
const entryPath = path.join(dir, entry.name);
|
|
19
37
|
if (entry.isDirectory()) {
|
|
20
|
-
|
|
21
|
-
} else if (entry.name.endsWith(".ts")) {
|
|
38
|
+
collectSourceFiles(entryPath).forEach((file) => files.push(file));
|
|
39
|
+
} else if (entry.name.endsWith(".ts") || entry.name.endsWith(".mjs")) {
|
|
22
40
|
files.push(entryPath);
|
|
23
41
|
}
|
|
24
42
|
}
|
|
25
43
|
return files;
|
|
26
44
|
}
|
|
27
45
|
|
|
46
|
+
test("published extension APIs use supported package entrypoints", async () => {
|
|
47
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
|
|
48
|
+
|
|
49
|
+
assert.deepEqual(packageJson.pi?.extensions, ["./index.ts"]);
|
|
50
|
+
assert.equal(packageJson.files?.includes("index.ts"), true);
|
|
51
|
+
assert.equal(
|
|
52
|
+
fs.readFileSync(path.join(projectRoot, "index.ts"), "utf-8").trim(),
|
|
53
|
+
'export { default } from "./src/extension/index.ts";',
|
|
54
|
+
);
|
|
55
|
+
assert.equal(fs.existsSync(path.join(projectRoot, "src", "api", "delegation.ts")), true);
|
|
56
|
+
assert.deepEqual(packageJson.exports, {
|
|
57
|
+
".": "./index.ts",
|
|
58
|
+
"./background-work": "./src/api/background-work.ts",
|
|
59
|
+
"./capability-ceiling": "./src/api/capability-ceiling.ts",
|
|
60
|
+
"./delegation": "./src/api/delegation.ts",
|
|
61
|
+
"./preflight": "./src/api/preflight.ts",
|
|
62
|
+
});
|
|
63
|
+
const backgroundWork = await import("pi-subagents/background-work");
|
|
64
|
+
assert.equal(backgroundWork.BACKGROUND_WORK_PROTOCOL_VERSION, 1);
|
|
65
|
+
assert.equal(backgroundWork.BACKGROUND_WORK_REGISTRY_KEY, "pi-subagents.background-work.v1");
|
|
66
|
+
const capability = await import("pi-subagents/capability-ceiling");
|
|
67
|
+
assert.equal(capability.SUBAGENT_CAPABILITY_CEILING_VERSION, 1);
|
|
68
|
+
assert.equal(capability.SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY, "pi-subagents.capability-ceiling.v1");
|
|
69
|
+
const delegation = await import("pi-subagents/delegation");
|
|
70
|
+
assert.equal(delegation.SUBAGENT_DELEGATION_PROTOCOL_VERSION, 1);
|
|
71
|
+
assert.equal(delegation.SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION, 2);
|
|
72
|
+
assert.equal(delegation.SUBAGENT_DELEGATION_REQUEST_EVENT, "prompt-template:subagent:request");
|
|
73
|
+
const preflight = await import("pi-subagents/preflight");
|
|
74
|
+
assert.equal(preflight.SUBAGENT_LAUNCH_CONTRACT_VERSION, 2);
|
|
75
|
+
assert.equal(typeof preflight.resolveSubagentLaunchContract, "function");
|
|
76
|
+
});
|
|
77
|
+
|
|
28
78
|
test("direct @earendil-works runtime imports are declared for CI installs", () => {
|
|
29
79
|
const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
|
|
30
80
|
const declared = new Set([
|
|
31
81
|
...Object.keys(packageJson.dependencies ?? {}),
|
|
32
82
|
...Object.keys(packageJson.devDependencies ?? {}),
|
|
83
|
+
...Object.keys(packageJson.peerDependencies ?? {}),
|
|
33
84
|
]);
|
|
34
85
|
const imported = new Set<string>();
|
|
35
86
|
|
|
36
|
-
for (const file of [...
|
|
87
|
+
for (const file of [...collectSourceFiles(path.join(projectRoot, "src")), ...collectSourceFiles(path.join(projectRoot, "test"))]) {
|
|
37
88
|
const source = fs.readFileSync(file, "utf-8");
|
|
38
89
|
for (const match of source.matchAll(sourceImportPattern)) {
|
|
39
|
-
|
|
90
|
+
const specifier = match[1] ?? match[2]!;
|
|
91
|
+
imported.add(specifier.split("/").slice(0, 2).join("/"));
|
|
40
92
|
}
|
|
41
93
|
}
|
|
42
94
|
|
|
@@ -54,15 +106,41 @@ test("direct dependency declarations are exact version pins", () => {
|
|
|
54
106
|
}
|
|
55
107
|
});
|
|
56
108
|
|
|
109
|
+
test("host-owned packages are optional peers with supported ranges, not production dependencies", () => {
|
|
110
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
|
|
111
|
+
|
|
112
|
+
for (const name of hostPeerPackages) {
|
|
113
|
+
assert.equal(packageJson.peerDependencies?.[name], expectedHostPeerRanges[name], `${name} should use its supported peer range`);
|
|
114
|
+
assert.equal(packageJson.dependencies?.[name], undefined, `${name} should not be a production dependency`);
|
|
115
|
+
assert.deepEqual(packageJson.peerDependenciesMeta?.[name], { optional: true }, `${name} should be an optional peer`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
test("typebox is a bundled runtime dependency", () => {
|
|
119
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
|
|
120
|
+
|
|
121
|
+
assert.equal(packageJson.dependencies?.typebox, "1.1.38");
|
|
122
|
+
assert.equal(packageJson.peerDependencies?.typebox, undefined);
|
|
123
|
+
assert.equal(packageJson.peerDependenciesMeta?.typebox, undefined);
|
|
124
|
+
assert.equal(packageJson.devDependencies?.typebox, undefined);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("host-owned development packages use the supported SDK baseline", () => {
|
|
128
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
|
|
129
|
+
|
|
130
|
+
for (const [name, version] of Object.entries(expectedHostDevVersions)) {
|
|
131
|
+
assert.equal(packageJson.devDependencies?.[name], version, `${name} should use ${version}`);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
57
135
|
test("old pi package scope is not used by source or tests", () => {
|
|
58
|
-
for (const file of [...
|
|
136
|
+
for (const file of [...collectSourceFiles(path.join(projectRoot, "src")), ...collectSourceFiles(path.join(projectRoot, "test"))]) {
|
|
59
137
|
const source = fs.readFileSync(file, "utf-8");
|
|
60
138
|
assert.equal(oldPiScopePattern.test(source), false, file);
|
|
61
139
|
}
|
|
62
140
|
});
|
|
63
141
|
|
|
64
142
|
test("Pi package resolution stays export-map safe", () => {
|
|
65
|
-
for (const file of [...
|
|
143
|
+
for (const file of [...collectSourceFiles(path.join(projectRoot, "src")), ...collectSourceFiles(path.join(projectRoot, "test"))]) {
|
|
66
144
|
const source = fs.readFileSync(file, "utf-8");
|
|
67
145
|
assert.equal(piPackageJsonSubpathPattern.test(source), false, `${file} should not resolve unexported package.json subpaths`);
|
|
68
146
|
assert.equal(cjsPiPackageResolutionPattern.test(source), false, `${file} should not use CommonJS resolution for ESM-only Pi packages`);
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { describe, it } from "node:test";
|
|
6
|
+
import {
|
|
7
|
+
formatParallelHandoffReference,
|
|
8
|
+
writeParallelHandoffGroup,
|
|
9
|
+
} from "../../src/runs/shared/parallel-handoff.ts";
|
|
10
|
+
import type { ParallelHandoffManifest } from "../../src/shared/types.ts";
|
|
11
|
+
import type { WorktreeCleanupReport, WorktreeDiff, WorktreeSetup } from "../../src/runs/shared/worktree.ts";
|
|
12
|
+
|
|
13
|
+
function setup(repoRoot: string, baseCommit: string): WorktreeSetup {
|
|
14
|
+
return { cwd: repoRoot, baseCommit, worktrees: [] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function diff(dir: string, index: number, agent: string, changed: boolean): WorktreeDiff {
|
|
18
|
+
const patchPath = path.join(dir, `task-${index}-${agent}.patch`);
|
|
19
|
+
fs.mkdirSync(path.dirname(patchPath), { recursive: true });
|
|
20
|
+
fs.writeFileSync(patchPath, changed ? "diff --git a/a b/a\n" : "", "utf-8");
|
|
21
|
+
return {
|
|
22
|
+
index,
|
|
23
|
+
agent,
|
|
24
|
+
branch: `branch-${index}`,
|
|
25
|
+
diffStat: changed ? " a | 1 +" : "",
|
|
26
|
+
filesChanged: changed ? 1 : 0,
|
|
27
|
+
insertions: changed ? 1 : 0,
|
|
28
|
+
deletions: 0,
|
|
29
|
+
patchPath,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function cleanup(state: "complete" | "partial" = "complete"): WorktreeCleanupReport {
|
|
34
|
+
return {
|
|
35
|
+
state,
|
|
36
|
+
pruned: state === "complete",
|
|
37
|
+
tasks: [{
|
|
38
|
+
index: 0,
|
|
39
|
+
path: "/tmp/worktree-0",
|
|
40
|
+
branch: "branch-0",
|
|
41
|
+
worktreeRemoved: true,
|
|
42
|
+
branchRemoved: state === "complete",
|
|
43
|
+
...(state === "partial" ? { errors: ["branch removal failed"] } : {}),
|
|
44
|
+
}],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("parallel handoff", () => {
|
|
49
|
+
it("writes and aggregates versioned worktree handoff groups", () => {
|
|
50
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-parallel-handoff-"));
|
|
51
|
+
try {
|
|
52
|
+
const manifestPath = path.join(dir, "handoff.json");
|
|
53
|
+
const first = writeParallelHandoffGroup({
|
|
54
|
+
manifestPath,
|
|
55
|
+
runId: "run-1",
|
|
56
|
+
mode: "chain",
|
|
57
|
+
source: "async",
|
|
58
|
+
cwd: "/repo",
|
|
59
|
+
stepIndex: 2,
|
|
60
|
+
flatStartIndex: 3,
|
|
61
|
+
setup: setup("/repo", "base-1"),
|
|
62
|
+
diffs: [diff(dir, 0, "worker", true)],
|
|
63
|
+
cleanup: cleanup(),
|
|
64
|
+
results: [{
|
|
65
|
+
agent: "worker",
|
|
66
|
+
status: "completed",
|
|
67
|
+
summary: "implemented",
|
|
68
|
+
outputPath: "/artifacts/output.md",
|
|
69
|
+
structuredOutput: { ok: true },
|
|
70
|
+
structuredOutputPath: "/artifacts/structured.json",
|
|
71
|
+
sessionPath: "/sessions/worker.jsonl",
|
|
72
|
+
}],
|
|
73
|
+
now: 100,
|
|
74
|
+
});
|
|
75
|
+
assert.deepEqual(first, {
|
|
76
|
+
version: 1,
|
|
77
|
+
path: manifestPath,
|
|
78
|
+
groupCount: 1,
|
|
79
|
+
childCount: 1,
|
|
80
|
+
changedPatches: 1,
|
|
81
|
+
cleanupState: "complete",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const second = writeParallelHandoffGroup({
|
|
85
|
+
manifestPath,
|
|
86
|
+
runId: "run-1",
|
|
87
|
+
mode: "chain",
|
|
88
|
+
source: "async",
|
|
89
|
+
cwd: "/repo",
|
|
90
|
+
stepIndex: 1,
|
|
91
|
+
flatStartIndex: 1,
|
|
92
|
+
setup: setup("/repo", "base-1"),
|
|
93
|
+
diffs: [diff(dir, 1, "reviewer", false)],
|
|
94
|
+
cleanup: cleanup("partial"),
|
|
95
|
+
results: [{ agent: "reviewer", status: "failed", summary: "blocked" }],
|
|
96
|
+
now: 200,
|
|
97
|
+
});
|
|
98
|
+
assert.equal(second.groupCount, 2);
|
|
99
|
+
assert.equal(second.childCount, 2);
|
|
100
|
+
assert.equal(second.changedPatches, 1);
|
|
101
|
+
assert.equal(second.cleanupState, "partial");
|
|
102
|
+
assert.match(formatParallelHandoffReference(second), /2 children, 1 changed patches, cleanup partial/);
|
|
103
|
+
|
|
104
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as ParallelHandoffManifest;
|
|
105
|
+
assert.equal(manifest.createdAt, 100);
|
|
106
|
+
assert.equal(manifest.updatedAt, 200);
|
|
107
|
+
assert.deepEqual(manifest.groups.map((group) => group.stepIndex), [1, 2]);
|
|
108
|
+
assert.equal(manifest.groups[0]!.children[0]!.index, 1);
|
|
109
|
+
assert.equal(manifest.groups[1]!.children[0]!.index, 3);
|
|
110
|
+
assert.deepEqual(manifest.groups[1]!.children[0]!.structuredOutput, { ok: true });
|
|
111
|
+
assert.equal(manifest.groups[1]!.children[0]!.patch.changed, true);
|
|
112
|
+
} finally {
|
|
113
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("records missing diff artifacts instead of throwing", () => {
|
|
118
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-parallel-handoff-missing-diff-"));
|
|
119
|
+
try {
|
|
120
|
+
const manifestPath = path.join(dir, "handoff.json");
|
|
121
|
+
const reference = writeParallelHandoffGroup({
|
|
122
|
+
manifestPath,
|
|
123
|
+
runId: "run-missing-diff",
|
|
124
|
+
mode: "parallel",
|
|
125
|
+
source: "foreground",
|
|
126
|
+
cwd: "/repo",
|
|
127
|
+
stepIndex: 0,
|
|
128
|
+
flatStartIndex: 0,
|
|
129
|
+
setup: {
|
|
130
|
+
...setup("/repo", "base-1"),
|
|
131
|
+
worktrees: [{ path: "/tmp/worktree-0", agentCwd: "/tmp/worktree-0", branch: "branch-0", index: 0, nodeModulesLinked: false, syntheticPaths: [] }],
|
|
132
|
+
},
|
|
133
|
+
diffs: [],
|
|
134
|
+
cleanup: cleanup(),
|
|
135
|
+
results: [{ agent: "bad/name agent", status: "completed", summary: "done" }],
|
|
136
|
+
});
|
|
137
|
+
assert.equal(reference.childCount, 1);
|
|
138
|
+
assert.equal(reference.changedPatches, 0);
|
|
139
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as ParallelHandoffManifest;
|
|
140
|
+
const patch = manifest.groups[0]!.children[0]!.patch;
|
|
141
|
+
assert.equal(patch.changed, false);
|
|
142
|
+
assert.match(patch.error ?? "", /diff artifact unavailable/);
|
|
143
|
+
assert.equal(path.basename(patch.path), "missing-diff-step-0-task-0-bad_name_agent.patch");
|
|
144
|
+
assert.equal(fs.existsSync(patch.path), true);
|
|
145
|
+
} finally {
|
|
146
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("rejects reusing a manifest path for another run", () => {
|
|
151
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-parallel-handoff-owner-"));
|
|
152
|
+
try {
|
|
153
|
+
const manifestPath = path.join(dir, "handoff.json");
|
|
154
|
+
const common = {
|
|
155
|
+
manifestPath,
|
|
156
|
+
mode: "parallel" as const,
|
|
157
|
+
source: "foreground" as const,
|
|
158
|
+
cwd: "/repo",
|
|
159
|
+
stepIndex: 0,
|
|
160
|
+
flatStartIndex: 0,
|
|
161
|
+
setup: setup("/repo", "base-1"),
|
|
162
|
+
diffs: [diff(dir, 0, "worker", false)],
|
|
163
|
+
cleanup: cleanup(),
|
|
164
|
+
results: [{ agent: "worker", status: "completed" as const, summary: "done" }],
|
|
165
|
+
};
|
|
166
|
+
writeParallelHandoffGroup({ ...common, runId: "run-1" });
|
|
167
|
+
assert.throws(
|
|
168
|
+
() => writeParallelHandoffGroup({ ...common, runId: "run-2" }),
|
|
169
|
+
/belongs to a different run/,
|
|
170
|
+
);
|
|
171
|
+
} finally {
|
|
172
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -54,7 +54,7 @@ describe("Path resolution for .agents and ~/.agents", () => {
|
|
|
54
54
|
assert.strictEqual(resolved?.path, path.join(userSkillsDir, "test-skill-2.md"));
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
test("should resolve project agents from both .agents and .
|
|
57
|
+
test("should resolve project agents from both .agents and .selesai/agents", () => {
|
|
58
58
|
const legacyDir = path.join(cwdDir, ".agents");
|
|
59
59
|
const agentsDir = path.join(cwdDir, ".selesai", "agents");
|
|
60
60
|
fs.mkdirSync(path.join(cwdDir, ".agents", "skills"), { recursive: true });
|