@selesai/code 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +4 -15
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -45
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +8 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/diagnostics.d.ts +1 -0
- package/dist/core/diagnostics.d.ts.map +1 -1
- package/dist/core/diagnostics.js.map +1 -1
- package/dist/core/model-registry.d.ts +4 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +101 -51
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/package-manager.d.ts +10 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +93 -8
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-manager.test.d.ts +2 -0
- package/dist/core/package-manager.test.d.ts.map +1 -0
- package/dist/core/package-manager.test.js +20 -0
- package/dist/core/package-manager.test.js.map +1 -0
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +10 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/defaults/models.json +64 -113
- package/dist/extensions/caveman/caveman-instructions.cjs +39 -0
- package/dist/extensions/caveman/index.js +116 -0
- package/dist/extensions/caveman/package.json +8 -0
- package/dist/extensions/caveman/test/extension.test.js +185 -0
- package/dist/extensions/caveman/test/helpers.test.js +67 -0
- package/dist/extensions/copy-turn.ts +2 -2
- package/dist/extensions/pi-intercom/broker/broker.ts +2 -3
- package/dist/extensions/pi-intercom/broker/paths.ts +10 -5
- package/dist/extensions/pi-intercom/broker/spawn.ts +2 -3
- package/dist/extensions/pi-intercom/config.ts +2 -2
- package/dist/extensions/pi-intercom/index.ts +2 -2
- package/dist/extensions/pi-intercom/package.json +2 -2
- package/dist/extensions/pi-intercom/ui/compose.ts +3 -3
- package/dist/extensions/pi-intercom/ui/inline-message.ts +3 -3
- package/dist/extensions/pi-intercom/ui/session-list.ts +3 -3
- package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +2 -2
- package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +2 -1
- package/dist/extensions/pi-powerline-footer/index.ts +40 -47
- package/dist/extensions/pi-powerline-footer/package.json +2 -2
- package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +10 -10
- package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +1 -1
- package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +2 -2
- package/dist/extensions/pi-powerline-footer/theme.ts +6 -6
- package/dist/extensions/pi-powerline-footer/tps.ts +1 -1
- package/dist/extensions/pi-powerline-footer/types.ts +7 -7
- package/dist/extensions/pi-powerline-footer/welcome.ts +23 -16
- package/dist/extensions/pi-powerline-footer/working-vibes.ts +52 -59
- package/dist/extensions/pi-rewind-hook/index.ts +1 -1
- package/dist/extensions/pi-rewind-hook/package.json +1 -1
- package/dist/extensions/pi-subagents/README.md +224 -52
- package/dist/extensions/pi-subagents/agents/architect.md +188 -0
- package/dist/extensions/pi-subagents/agents/builder.md +120 -0
- package/dist/extensions/pi-subagents/agents/commentator.md +131 -0
- package/dist/extensions/pi-subagents/agents/explorer.md +51 -0
- package/dist/extensions/pi-subagents/agents/recapper.md +21 -0
- package/dist/extensions/pi-subagents/install.mjs +93 -0
- package/dist/extensions/pi-subagents/package.json +4 -5
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +76 -13
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +179 -2
- package/dist/extensions/pi-subagents/src/agents/agent-memory.ts +254 -0
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +11 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +193 -19
- package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +27 -2
- package/dist/extensions/pi-subagents/src/extension/config.ts +27 -4
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +1 -7
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -2
- package/dist/extensions/pi-subagents/src/extension/index.ts +73 -71
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +369 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +54 -10
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +200 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +21 -253
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +519 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +195 -105
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +88 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +67 -10
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +34 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +166 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +156 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +515 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +161 -44
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +1 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +3 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +167 -6
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +514 -0
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +28 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +873 -204
- package/dist/extensions/pi-subagents/src/runs/background/wait.ts +353 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +123 -27
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +174 -70
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +569 -81
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +45 -22
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +172 -21
- package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +89 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +50 -1
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +35 -4
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +63 -45
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +125 -5
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +74 -0
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +52 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +28 -5
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +16 -1
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +15 -2
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +212 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +133 -22
- package/dist/extensions/pi-subagents/src/shared/settings.ts +3 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +197 -4
- package/dist/extensions/pi-subagents/src/shared/utils.ts +108 -17
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +330 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +135 -4
- package/dist/extensions/pi-subagents/src/tui/render.ts +32 -12
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2772 -0
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +1107 -0
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +463 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +274 -0
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1535 -0
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +242 -0
- package/dist/extensions/pi-subagents/test/integration/doctor-executor.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +252 -0
- package/dist/extensions/pi-subagents/test/integration/foreground-result-size.test.ts +188 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +1306 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +1033 -0
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +583 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +691 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +892 -0
- package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1824 -0
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1284 -0
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +127 -0
- package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +37 -0
- package/dist/extensions/pi-subagents/test/support/helpers.ts +189 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +335 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +129 -0
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +184 -0
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +285 -0
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +15 -0
- package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +108 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +421 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +343 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +1199 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +556 -0
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +420 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +579 -0
- package/dist/extensions/pi-subagents/test/unit/agent-scope.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +221 -0
- package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +98 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +206 -0
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +229 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +225 -0
- package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +101 -0
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +228 -0
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +152 -0
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +161 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +217 -0
- package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +415 -0
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +126 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +271 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/jsonl-writer.test.ts +119 -0
- package/dist/extensions/pi-subagents/test/unit/live-async-resume.test.ts +170 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +346 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +87 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +145 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +162 -0
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +470 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +329 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +319 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +70 -0
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +113 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +803 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +238 -0
- package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +118 -0
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +408 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +257 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +226 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +294 -0
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +147 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +997 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +478 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +562 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +438 -0
- package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +54 -0
- package/dist/extensions/pi-subagents/test/unit/slash-chain-groups.test.ts +464 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +255 -0
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +19 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +245 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +517 -0
- package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +237 -0
- package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +60 -0
- package/dist/extensions/pi-subagents/test/unit/ts-loader.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +202 -0
- package/dist/extensions/pi-subagents/test/unit/types-fork-preamble.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/wait.test.ts +398 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +479 -0
- package/dist/extensions/pi-web-agent/package.json +2 -2
- package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +2 -2
- package/dist/extensions/pi-web-agent/src/changelog-notice.ts +5 -2
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +1 -1
- package/dist/extensions/pi-web-agent/src/extension.ts +1 -1
- package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +6 -4
- package/dist/extensions/question/helpers.ts +3 -3
- package/dist/extensions/question/index.ts +2 -2
- package/dist/extensions/question/navigation.ts +2 -2
- package/dist/extensions/question/package.json +2 -2
- package/dist/extensions/question/question-list.ts +2 -2
- package/dist/extensions/question/tests/question-list.test.ts +2 -2
- package/dist/extensions/question/tui-adapter.ts +2 -2
- package/dist/extensions/question/types.ts +2 -2
- package/dist/extensions/question/ui-protocol.ts +2 -2
- package/dist/extensions/rtk.ts +2 -2
- package/dist/extensions/tokenin-onboarding.ts +2 -2
- package/dist/extensions/undo.ts +1 -1
- package/dist/extensions/web-agent-onboarding.ts +3 -3
- package/dist/extensions/workflow/adapter.ts +16 -8
- package/dist/extensions/workflow/extension.ts +1 -1
- package/dist/extensions/workflow/package.json +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +2 -0
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +19 -5
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/skills/caveman/SKILL.md +49 -0
- package/dist/skills/handoff-text/SKILL.md +13 -0
- package/dist/utils/thinking-tags.d.ts +7 -0
- package/dist/utils/thinking-tags.d.ts.map +1 -0
- package/dist/utils/thinking-tags.js +63 -0
- package/dist/utils/thinking-tags.js.map +1 -0
- package/docs/docs.json +4 -0
- package/docs/extensions.md +2 -0
- package/docs/settings.md +15 -0
- package/docs/shared-host-extensions.md +109 -0
- package/package.json +2 -2
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import { describe, it } from "node:test";
|
|
7
|
+
import {
|
|
8
|
+
cleanupWorktrees,
|
|
9
|
+
createWorktrees,
|
|
10
|
+
diffWorktrees,
|
|
11
|
+
findWorktreeTaskCwdConflict,
|
|
12
|
+
formatWorktreeDiffSummary,
|
|
13
|
+
resolveExpectedWorktreeAgentCwd,
|
|
14
|
+
type WorktreeSetup,
|
|
15
|
+
} from "../../src/runs/shared/worktree.ts";
|
|
16
|
+
|
|
17
|
+
function git(cwd: string, args: string[]): string {
|
|
18
|
+
const result = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf-8" });
|
|
19
|
+
if (result.status !== 0) {
|
|
20
|
+
const message = result.stderr.trim() || result.stdout.trim() || `git ${args.join(" ")} failed`;
|
|
21
|
+
throw new Error(message);
|
|
22
|
+
}
|
|
23
|
+
return result.stdout.trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createRepo(prefix: string): string {
|
|
27
|
+
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
28
|
+
git(repoDir, ["init"]);
|
|
29
|
+
git(repoDir, ["config", "user.email", "tests@example.com"]);
|
|
30
|
+
git(repoDir, ["config", "user.name", "Worktree Tests"]);
|
|
31
|
+
fs.writeFileSync(path.join(repoDir, ".gitignore"), "node_modules/\n", "utf-8");
|
|
32
|
+
fs.writeFileSync(path.join(repoDir, "tracked.txt"), "initial\n", "utf-8");
|
|
33
|
+
git(repoDir, ["add", "-A"]);
|
|
34
|
+
git(repoDir, ["commit", "-m", "initial commit"]);
|
|
35
|
+
return repoDir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function cleanupRepo(repoDir: string): void {
|
|
39
|
+
try { fs.rmSync(repoDir, { recursive: true, force: true }); } catch {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createHookScript(_repoDir: string, fileName: string, source: string): string {
|
|
43
|
+
const hooksDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-worktree-hook-script-"));
|
|
44
|
+
const hookPath = path.join(hooksDir, fileName);
|
|
45
|
+
fs.writeFileSync(hookPath, `#!/usr/bin/env node\n${source}\n`, "utf-8");
|
|
46
|
+
fs.chmodSync(hookPath, 0o755);
|
|
47
|
+
return hookPath;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const hookScriptSkip = process.platform === "win32"
|
|
51
|
+
? "Hook script execution differs on Windows CI environments."
|
|
52
|
+
: undefined;
|
|
53
|
+
|
|
54
|
+
describe("worktree", () => {
|
|
55
|
+
it("createWorktrees returns expected structure", () => {
|
|
56
|
+
const repoDir = createRepo("pi-worktree-structure-");
|
|
57
|
+
let setup: WorktreeSetup | undefined;
|
|
58
|
+
try {
|
|
59
|
+
setup = createWorktrees(repoDir, "structure", 2);
|
|
60
|
+
assert.equal(setup.worktrees.length, 2);
|
|
61
|
+
assert.equal(setup.cwd, git(repoDir, ["rev-parse", "--show-toplevel"]));
|
|
62
|
+
for (let i = 0; i < setup.worktrees.length; i++) {
|
|
63
|
+
const worktree = setup.worktrees[i]!;
|
|
64
|
+
assert.equal(worktree.branch, `pi-parallel-structure-${i}`);
|
|
65
|
+
assert.equal(worktree.index, i);
|
|
66
|
+
assert.equal(worktree.agentCwd, worktree.path);
|
|
67
|
+
assert.equal(worktree.nodeModulesLinked, false);
|
|
68
|
+
assert.deepEqual(worktree.syntheticPaths, []);
|
|
69
|
+
assert.ok(fs.existsSync(worktree.path), `worktree path missing: ${worktree.path}`);
|
|
70
|
+
}
|
|
71
|
+
} finally {
|
|
72
|
+
if (setup) cleanupWorktrees(setup);
|
|
73
|
+
cleanupRepo(repoDir);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("createWorktrees maps subdirectory cwd to each agentCwd", () => {
|
|
78
|
+
const repoDir = createRepo("pi-worktree-subdir-");
|
|
79
|
+
const nestedDir = path.join(repoDir, "packages", "app");
|
|
80
|
+
fs.mkdirSync(nestedDir, { recursive: true });
|
|
81
|
+
fs.writeFileSync(path.join(nestedDir, "index.ts"), "export const value = 1;\n", "utf-8");
|
|
82
|
+
git(repoDir, ["add", "-A"]);
|
|
83
|
+
git(repoDir, ["commit", "-m", "add nested dir"]);
|
|
84
|
+
|
|
85
|
+
let setup: WorktreeSetup | undefined;
|
|
86
|
+
try {
|
|
87
|
+
setup = createWorktrees(nestedDir, "subdir", 1);
|
|
88
|
+
assert.equal(setup.worktrees[0]!.agentCwd, path.join(setup.worktrees[0]!.path, "packages", "app"));
|
|
89
|
+
} finally {
|
|
90
|
+
if (setup) cleanupWorktrees(setup);
|
|
91
|
+
cleanupRepo(repoDir);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("previews expected worktree agent cwd for repository subdirectories", () => {
|
|
96
|
+
const repoDir = createRepo("pi-worktree-preview-");
|
|
97
|
+
const nestedDir = path.join(repoDir, "packages", "app");
|
|
98
|
+
fs.mkdirSync(nestedDir, { recursive: true });
|
|
99
|
+
fs.writeFileSync(path.join(nestedDir, "index.ts"), "export const value = 1;\n", "utf-8");
|
|
100
|
+
git(repoDir, ["add", "-A"]);
|
|
101
|
+
git(repoDir, ["commit", "-m", "add nested dir"]);
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
assert.equal(
|
|
105
|
+
resolveExpectedWorktreeAgentCwd(nestedDir, "preview", 2),
|
|
106
|
+
path.join(os.tmpdir(), "pi-worktree-preview-2", "packages", "app"),
|
|
107
|
+
);
|
|
108
|
+
} finally {
|
|
109
|
+
cleanupRepo(repoDir);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("creates worktrees under a configured base directory", () => {
|
|
114
|
+
const repoDir = createRepo("pi-worktree-base-dir-");
|
|
115
|
+
const baseDir = path.join(os.tmpdir(), `pi-worktree-base-${Date.now().toString(36)}`, "nested");
|
|
116
|
+
let setup: WorktreeSetup | undefined;
|
|
117
|
+
try {
|
|
118
|
+
setup = createWorktrees(repoDir, "base-dir", 1, { baseDir });
|
|
119
|
+
assert.equal(setup.worktrees[0]!.path, path.join(baseDir, "pi-worktree-base-dir-0"));
|
|
120
|
+
assert.ok(fs.existsSync(baseDir), "configured base directory should be created");
|
|
121
|
+
} finally {
|
|
122
|
+
if (setup) cleanupWorktrees(setup);
|
|
123
|
+
cleanupRepo(repoDir);
|
|
124
|
+
fs.rmSync(path.dirname(baseDir), { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("uses SELESAI_SUBAGENTS_WORKTREE_DIR when no base directory is configured", () => {
|
|
129
|
+
const repoDir = createRepo("pi-worktree-env-base-dir-");
|
|
130
|
+
const previous = process.env.SELESAI_SUBAGENTS_WORKTREE_DIR;
|
|
131
|
+
const baseDir = path.join(os.tmpdir(), `pi-worktree-env-base-${Date.now().toString(36)}`);
|
|
132
|
+
let setup: WorktreeSetup | undefined;
|
|
133
|
+
try {
|
|
134
|
+
process.env.SELESAI_SUBAGENTS_WORKTREE_DIR = baseDir;
|
|
135
|
+
setup = createWorktrees(repoDir, "env-base-dir", 1);
|
|
136
|
+
assert.equal(setup.worktrees[0]!.path, path.join(baseDir, "pi-worktree-env-base-dir-0"));
|
|
137
|
+
} finally {
|
|
138
|
+
if (setup) cleanupWorktrees(setup);
|
|
139
|
+
if (previous === undefined) {
|
|
140
|
+
delete process.env.SELESAI_SUBAGENTS_WORKTREE_DIR;
|
|
141
|
+
} else {
|
|
142
|
+
process.env.SELESAI_SUBAGENTS_WORKTREE_DIR = previous;
|
|
143
|
+
}
|
|
144
|
+
cleanupRepo(repoDir);
|
|
145
|
+
fs.rmSync(baseDir, { recursive: true, force: true });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("createWorktrees rejects dirty repositories", () => {
|
|
150
|
+
const repoDir = createRepo("pi-worktree-dirty-");
|
|
151
|
+
try {
|
|
152
|
+
fs.writeFileSync(path.join(repoDir, "tracked.txt"), "dirty\n", "utf-8");
|
|
153
|
+
assert.throws(
|
|
154
|
+
() => createWorktrees(repoDir, "dirty", 1),
|
|
155
|
+
/worktree isolation requires a clean git working tree/i,
|
|
156
|
+
);
|
|
157
|
+
} finally {
|
|
158
|
+
cleanupRepo(repoDir);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("findWorktreeTaskCwdConflict allows omitted or matching task cwd values", () => {
|
|
163
|
+
const sharedCwd = path.join("/tmp", "repo");
|
|
164
|
+
assert.equal(
|
|
165
|
+
findWorktreeTaskCwdConflict(
|
|
166
|
+
[
|
|
167
|
+
{ agent: "worker-a" },
|
|
168
|
+
{ agent: "worker-b", cwd: sharedCwd },
|
|
169
|
+
],
|
|
170
|
+
sharedCwd,
|
|
171
|
+
),
|
|
172
|
+
undefined,
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("findWorktreeTaskCwdConflict treats relative task cwd values as relative to the shared cwd", () => {
|
|
177
|
+
const sharedCwd = path.join("/tmp", "repo");
|
|
178
|
+
assert.equal(
|
|
179
|
+
findWorktreeTaskCwdConflict(
|
|
180
|
+
[{ agent: "worker-a", cwd: "." }],
|
|
181
|
+
sharedCwd,
|
|
182
|
+
),
|
|
183
|
+
undefined,
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("findWorktreeTaskCwdConflict returns the first conflicting task cwd", () => {
|
|
188
|
+
const sharedCwd = path.join("/tmp", "repo");
|
|
189
|
+
const conflict = findWorktreeTaskCwdConflict(
|
|
190
|
+
[
|
|
191
|
+
{ agent: "worker-a", cwd: sharedCwd },
|
|
192
|
+
{ agent: "worker-b", cwd: path.join(sharedCwd, "packages", "app") },
|
|
193
|
+
],
|
|
194
|
+
sharedCwd,
|
|
195
|
+
);
|
|
196
|
+
assert.deepEqual(conflict, {
|
|
197
|
+
index: 1,
|
|
198
|
+
agent: "worker-b",
|
|
199
|
+
cwd: path.join(sharedCwd, "packages", "app"),
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("diffWorktrees captures committed, modified, and new files without staging the node_modules symlink", () => {
|
|
204
|
+
const repoDir = createRepo("pi-worktree-diff-");
|
|
205
|
+
const nodeModulesDir = path.join(repoDir, "node_modules");
|
|
206
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
207
|
+
fs.writeFileSync(path.join(nodeModulesDir, "fixture.txt"), "fixture\n", "utf-8");
|
|
208
|
+
|
|
209
|
+
let setup: WorktreeSetup | undefined;
|
|
210
|
+
try {
|
|
211
|
+
setup = createWorktrees(repoDir, "diff", 1);
|
|
212
|
+
const worktree = setup.worktrees[0]!;
|
|
213
|
+
fs.writeFileSync(path.join(worktree.path, "committed.ts"), "export const committed = true;\n", "utf-8");
|
|
214
|
+
git(worktree.path, ["add", "committed.ts"]);
|
|
215
|
+
git(worktree.path, ["commit", "-m", "committed change"]);
|
|
216
|
+
fs.writeFileSync(path.join(worktree.path, "tracked.txt"), "modified\n", "utf-8");
|
|
217
|
+
fs.writeFileSync(path.join(worktree.path, "new-file.ts"), "export const added = true;\n", "utf-8");
|
|
218
|
+
|
|
219
|
+
const diffsDir = path.join(repoDir, "artifacts", "worktree-diffs");
|
|
220
|
+
const diffs = diffWorktrees(setup, ["agent-a"], diffsDir);
|
|
221
|
+
assert.equal(diffs.length, 1);
|
|
222
|
+
assert.equal(diffs[0]!.agent, "agent-a");
|
|
223
|
+
assert.equal(diffs[0]!.filesChanged, 3, `expected 3 files, got ${diffs[0]!.filesChanged}`);
|
|
224
|
+
assert.ok(diffs[0]!.insertions > 0, "expected insertions > 0");
|
|
225
|
+
assert.ok(fs.existsSync(diffs[0]!.patchPath), "expected patch file to exist");
|
|
226
|
+
|
|
227
|
+
const patch = fs.readFileSync(diffs[0]!.patchPath, "utf-8");
|
|
228
|
+
assert.match(patch, /committed\.ts/);
|
|
229
|
+
assert.match(patch, /tracked\.txt/);
|
|
230
|
+
assert.match(patch, /new-file\.ts/);
|
|
231
|
+
assert.doesNotMatch(patch, /diff --git a\/node_modules b\/node_modules/);
|
|
232
|
+
|
|
233
|
+
const summary = formatWorktreeDiffSummary(diffs);
|
|
234
|
+
assert.match(summary, /=== Worktree Changes ===/);
|
|
235
|
+
assert.match(summary, /Full patches:/);
|
|
236
|
+
} finally {
|
|
237
|
+
if (setup) cleanupWorktrees(setup);
|
|
238
|
+
cleanupRepo(repoDir);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("cleanupWorktrees removes worktrees and branches", () => {
|
|
243
|
+
const repoDir = createRepo("pi-worktree-cleanup-");
|
|
244
|
+
let setup: WorktreeSetup | undefined;
|
|
245
|
+
try {
|
|
246
|
+
setup = createWorktrees(repoDir, "cleanup", 2);
|
|
247
|
+
const worktreePaths = setup.worktrees.map((worktree) => worktree.path);
|
|
248
|
+
const branches = setup.worktrees.map((worktree) => worktree.branch);
|
|
249
|
+
cleanupWorktrees(setup);
|
|
250
|
+
setup = undefined;
|
|
251
|
+
|
|
252
|
+
for (const worktreePath of worktreePaths) {
|
|
253
|
+
assert.equal(fs.existsSync(worktreePath), false, `worktree path still exists: ${worktreePath}`);
|
|
254
|
+
}
|
|
255
|
+
for (const branch of branches) {
|
|
256
|
+
const branchResult = git(repoDir, ["branch", "--list", branch]);
|
|
257
|
+
assert.equal(branchResult.trim(), "", `branch still exists: ${branch}`);
|
|
258
|
+
}
|
|
259
|
+
} finally {
|
|
260
|
+
if (setup) cleanupWorktrees(setup);
|
|
261
|
+
cleanupRepo(repoDir);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("createWorktrees creates node_modules symlink when node_modules exists", {
|
|
266
|
+
skip: process.platform === "win32" ? "Symlink behavior differs on Windows CI environments." : undefined,
|
|
267
|
+
}, () => {
|
|
268
|
+
const repoDir = createRepo("pi-worktree-node-modules-");
|
|
269
|
+
const nodeModulesDir = path.join(repoDir, "node_modules");
|
|
270
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
271
|
+
fs.writeFileSync(path.join(nodeModulesDir, "fixture.txt"), "fixture\n", "utf-8");
|
|
272
|
+
|
|
273
|
+
let setup: WorktreeSetup | undefined;
|
|
274
|
+
try {
|
|
275
|
+
setup = createWorktrees(repoDir, "node-modules", 1);
|
|
276
|
+
const symlinkPath = path.join(setup.worktrees[0]!.path, "node_modules");
|
|
277
|
+
assert.equal(setup.worktrees[0]!.nodeModulesLinked, true);
|
|
278
|
+
assert.deepEqual(setup.worktrees[0]!.syntheticPaths, ["node_modules"]);
|
|
279
|
+
assert.ok(fs.existsSync(symlinkPath), "node_modules link should exist");
|
|
280
|
+
assert.equal(fs.lstatSync(symlinkPath).isSymbolicLink(), true, "node_modules should be a symlink");
|
|
281
|
+
assert.equal(fs.realpathSync(symlinkPath), fs.realpathSync(nodeModulesDir));
|
|
282
|
+
} finally {
|
|
283
|
+
if (setup) cleanupWorktrees(setup);
|
|
284
|
+
cleanupRepo(repoDir);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("diffWorktrees preserves a tracked node_modules symlink", {
|
|
289
|
+
skip: process.platform === "win32" ? "Symlink behavior differs on Windows CI environments." : undefined,
|
|
290
|
+
}, () => {
|
|
291
|
+
const repoDir = createRepo("pi-worktree-tracked-node-modules-");
|
|
292
|
+
const vendorDir = path.join(repoDir, "vendor-modules");
|
|
293
|
+
fs.mkdirSync(vendorDir, { recursive: true });
|
|
294
|
+
fs.writeFileSync(path.join(vendorDir, "fixture.txt"), "fixture\n", "utf-8");
|
|
295
|
+
fs.symlinkSync("vendor-modules", path.join(repoDir, "node_modules"));
|
|
296
|
+
git(repoDir, ["add", "vendor-modules", "-f", "node_modules"]);
|
|
297
|
+
git(repoDir, ["commit", "-m", "track node_modules symlink"]);
|
|
298
|
+
|
|
299
|
+
let setup: WorktreeSetup | undefined;
|
|
300
|
+
try {
|
|
301
|
+
setup = createWorktrees(repoDir, "tracked-node-modules", 1);
|
|
302
|
+
assert.equal(setup.worktrees[0]!.nodeModulesLinked, false);
|
|
303
|
+
assert.deepEqual(setup.worktrees[0]!.syntheticPaths, []);
|
|
304
|
+
fs.writeFileSync(path.join(setup.worktrees[0]!.path, "tracked.txt"), "modified\n", "utf-8");
|
|
305
|
+
|
|
306
|
+
const diffsDir = path.join(repoDir, "artifacts", "tracked-node-modules-diffs");
|
|
307
|
+
const diffs = diffWorktrees(setup, ["agent-a"], diffsDir);
|
|
308
|
+
const patch = fs.readFileSync(diffs[0]!.patchPath, "utf-8");
|
|
309
|
+
assert.doesNotMatch(patch, /diff --git a\/node_modules b\/node_modules/);
|
|
310
|
+
assert.equal(fs.lstatSync(path.join(setup.worktrees[0]!.path, "node_modules")).isSymbolicLink(), true);
|
|
311
|
+
} finally {
|
|
312
|
+
if (setup) cleanupWorktrees(setup);
|
|
313
|
+
cleanupRepo(repoDir);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("runs a repo-relative worktree setup hook and records synthetic paths", { skip: hookScriptSkip }, () => {
|
|
318
|
+
const repoDir = createRepo("pi-worktree-hook-relative-");
|
|
319
|
+
const hookPath = createHookScript(repoDir, "setup-hook.mjs", `
|
|
320
|
+
import * as fs from "node:fs";
|
|
321
|
+
import * as path from "node:path";
|
|
322
|
+
const payload = JSON.parse(fs.readFileSync(0, "utf-8"));
|
|
323
|
+
fs.mkdirSync(path.join(payload.worktreePath, ".venv"), { recursive: true });
|
|
324
|
+
fs.writeFileSync(path.join(payload.worktreePath, ".venv", "pyvenv.cfg"), "home=/tmp\\n", "utf-8");
|
|
325
|
+
process.stdout.write(JSON.stringify({ syntheticPaths: [".venv"] }));
|
|
326
|
+
`);
|
|
327
|
+
|
|
328
|
+
let setup: WorktreeSetup | undefined;
|
|
329
|
+
try {
|
|
330
|
+
setup = createWorktrees(repoDir, "hook-relative", 1, {
|
|
331
|
+
setupHook: { hookPath: path.relative(repoDir, hookPath) },
|
|
332
|
+
});
|
|
333
|
+
assert.ok(setup.worktrees[0]!.syntheticPaths.includes(".venv"));
|
|
334
|
+
} finally {
|
|
335
|
+
if (setup) cleanupWorktrees(setup);
|
|
336
|
+
cleanupRepo(repoDir);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("runs an absolute worktree setup hook path", { skip: hookScriptSkip }, () => {
|
|
341
|
+
const repoDir = createRepo("pi-worktree-hook-absolute-");
|
|
342
|
+
const hookPath = createHookScript(repoDir, "setup-hook.mjs", `
|
|
343
|
+
import * as fs from "node:fs";
|
|
344
|
+
JSON.parse(fs.readFileSync(0, "utf-8"));
|
|
345
|
+
process.stdout.write(JSON.stringify({ syntheticPaths: [] }));
|
|
346
|
+
`);
|
|
347
|
+
|
|
348
|
+
let setup: WorktreeSetup | undefined;
|
|
349
|
+
try {
|
|
350
|
+
setup = createWorktrees(repoDir, "hook-absolute", 1, {
|
|
351
|
+
setupHook: { hookPath },
|
|
352
|
+
});
|
|
353
|
+
assert.equal(setup.worktrees.length, 1);
|
|
354
|
+
} finally {
|
|
355
|
+
if (setup) cleanupWorktrees(setup);
|
|
356
|
+
cleanupRepo(repoDir);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("rejects bare command names for worktree setup hooks", () => {
|
|
361
|
+
const repoDir = createRepo("pi-worktree-hook-bare-");
|
|
362
|
+
try {
|
|
363
|
+
assert.throws(
|
|
364
|
+
() => createWorktrees(repoDir, "hook-bare", 1, { setupHook: { hookPath: "node" } }),
|
|
365
|
+
/worktree setup hook must be an absolute path or a repo-relative path/i,
|
|
366
|
+
);
|
|
367
|
+
} finally {
|
|
368
|
+
cleanupRepo(repoDir);
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("rejects tracked synthetic paths from hook output", { skip: hookScriptSkip }, () => {
|
|
373
|
+
const repoDir = createRepo("pi-worktree-hook-tracked-");
|
|
374
|
+
const hookPath = createHookScript(repoDir, "tracked-hook.mjs", `
|
|
375
|
+
import * as fs from "node:fs";
|
|
376
|
+
JSON.parse(fs.readFileSync(0, "utf-8"));
|
|
377
|
+
process.stdout.write(JSON.stringify({ syntheticPaths: ["tracked.txt"] }));
|
|
378
|
+
`);
|
|
379
|
+
const runId = `hook-tracked-${Date.now().toString(36)}`;
|
|
380
|
+
try {
|
|
381
|
+
assert.throws(
|
|
382
|
+
() => createWorktrees(repoDir, runId, 1, { setupHook: { hookPath: path.relative(repoDir, hookPath) } }),
|
|
383
|
+
/cannot mark tracked paths as synthetic/i,
|
|
384
|
+
);
|
|
385
|
+
} finally {
|
|
386
|
+
cleanupRepo(repoDir);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("rejects absolute synthetic paths from hook output", { skip: hookScriptSkip }, () => {
|
|
391
|
+
const repoDir = createRepo("pi-worktree-hook-absolute-synthetic-");
|
|
392
|
+
const hookPath = createHookScript(repoDir, "absolute-path-hook.mjs", `
|
|
393
|
+
import * as fs from "node:fs";
|
|
394
|
+
const payload = JSON.parse(fs.readFileSync(0, "utf-8"));
|
|
395
|
+
process.stdout.write(JSON.stringify({ syntheticPaths: [payload.worktreePath + "/.venv"] }));
|
|
396
|
+
`);
|
|
397
|
+
const runId = `hook-absolute-synthetic-${Date.now().toString(36)}`;
|
|
398
|
+
try {
|
|
399
|
+
assert.throws(
|
|
400
|
+
() => createWorktrees(repoDir, runId, 1, { setupHook: { hookPath: path.relative(repoDir, hookPath) } }),
|
|
401
|
+
/synthetic path must be relative/i,
|
|
402
|
+
);
|
|
403
|
+
} finally {
|
|
404
|
+
cleanupRepo(repoDir);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("excludes hook-created synthetic files from captured patch output", { skip: hookScriptSkip }, () => {
|
|
409
|
+
const repoDir = createRepo("pi-worktree-hook-diff-");
|
|
410
|
+
const hookPath = createHookScript(repoDir, "setup-copy-hook.mjs", `
|
|
411
|
+
import * as fs from "node:fs";
|
|
412
|
+
import * as path from "node:path";
|
|
413
|
+
const payload = JSON.parse(fs.readFileSync(0, "utf-8"));
|
|
414
|
+
fs.writeFileSync(path.join(payload.worktreePath, ".env.local"), "TOKEN=secret\\n", "utf-8");
|
|
415
|
+
process.stdout.write(JSON.stringify({ syntheticPaths: [".env.local"] }));
|
|
416
|
+
`);
|
|
417
|
+
|
|
418
|
+
let setup: WorktreeSetup | undefined;
|
|
419
|
+
try {
|
|
420
|
+
setup = createWorktrees(repoDir, "hook-diff", 1, {
|
|
421
|
+
setupHook: { hookPath: path.relative(repoDir, hookPath) },
|
|
422
|
+
});
|
|
423
|
+
fs.writeFileSync(path.join(setup.worktrees[0]!.path, "tracked.txt"), "modified-by-agent\n", "utf-8");
|
|
424
|
+
const diffs = diffWorktrees(setup, ["agent-a"], path.join(repoDir, "artifacts", "hook-diff"));
|
|
425
|
+
const patch = fs.readFileSync(diffs[0]!.patchPath, "utf-8");
|
|
426
|
+
assert.match(patch, /tracked\.txt/);
|
|
427
|
+
assert.doesNotMatch(patch, /\.env\.local/);
|
|
428
|
+
} finally {
|
|
429
|
+
if (setup) cleanupWorktrees(setup);
|
|
430
|
+
cleanupRepo(repoDir);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it("cleans up created worktrees when a later hook setup fails", { skip: hookScriptSkip }, () => {
|
|
435
|
+
const repoDir = createRepo("pi-worktree-hook-cleanup-");
|
|
436
|
+
const runId = `hook-cleanup-${Date.now().toString(36)}`;
|
|
437
|
+
const hookPath = createHookScript(repoDir, "flaky-hook.mjs", `
|
|
438
|
+
import * as fs from "node:fs";
|
|
439
|
+
const payload = JSON.parse(fs.readFileSync(0, "utf-8"));
|
|
440
|
+
if (payload.index === 1) {
|
|
441
|
+
console.error("intentional failure");
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
process.stdout.write(JSON.stringify({ syntheticPaths: [] }));
|
|
445
|
+
`);
|
|
446
|
+
try {
|
|
447
|
+
assert.throws(
|
|
448
|
+
() => createWorktrees(repoDir, runId, 2, { setupHook: { hookPath: path.relative(repoDir, hookPath) } }),
|
|
449
|
+
/worktree setup hook failed with exit code 1/i,
|
|
450
|
+
);
|
|
451
|
+
const branchList = git(repoDir, ["branch", "--list", `pi-parallel-${runId}-*`]);
|
|
452
|
+
assert.equal(branchList.trim(), "", "temporary branches should be cleaned up after setup failure");
|
|
453
|
+
} finally {
|
|
454
|
+
cleanupRepo(repoDir);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("fails when the hook exceeds the configured timeout", { skip: hookScriptSkip }, () => {
|
|
459
|
+
const repoDir = createRepo("pi-worktree-hook-timeout-");
|
|
460
|
+
const hookPath = createHookScript(repoDir, "slow-hook.mjs", `
|
|
461
|
+
import * as fs from "node:fs";
|
|
462
|
+
JSON.parse(fs.readFileSync(0, "utf-8"));
|
|
463
|
+
setTimeout(() => {
|
|
464
|
+
process.stdout.write(JSON.stringify({ syntheticPaths: [] }));
|
|
465
|
+
}, 1000);
|
|
466
|
+
`);
|
|
467
|
+
const runId = `hook-timeout-${Date.now().toString(36)}`;
|
|
468
|
+
try {
|
|
469
|
+
assert.throws(
|
|
470
|
+
() => createWorktrees(repoDir, runId, 1, {
|
|
471
|
+
setupHook: { hookPath: path.relative(repoDir, hookPath), timeoutMs: 50 },
|
|
472
|
+
}),
|
|
473
|
+
/timed out/i,
|
|
474
|
+
);
|
|
475
|
+
} finally {
|
|
476
|
+
cleanupRepo(repoDir);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// minimal settings.json read. Ceiling: if selesai moves web search config elsewhere, this
|
|
5
5
|
// helper needs updating; upgrade path = upstream pi-web-agent adopting a settings hook.
|
|
6
6
|
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
-
import { getSettingsPath } from '@
|
|
7
|
+
import { getSettingsPath } from '@selesai/code';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Read the Brave API key from selesai settings.json (webAgent.braveApiKey).
|
|
@@ -22,4 +22,4 @@ export function readBraveKeyFromSettings(settingsPath: string = getSettingsPath(
|
|
|
22
22
|
} catch {
|
|
23
23
|
return undefined;
|
|
24
24
|
}
|
|
25
|
-
}
|
|
25
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { getAgentDir } from '@selesai/code';
|
|
4
5
|
|
|
5
6
|
export type ChangelogEntry = {
|
|
6
7
|
version: string;
|
|
@@ -64,8 +65,10 @@ function defaultPackageRoot() {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
function defaultStatePath() {
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
// ponytail: route through host getAgentDir() so the config dir respects the
|
|
69
|
+
// active CONFIG_DIR_NAME (e.g. .selesai). Ceiling: state layout assumed fixed;
|
|
70
|
+
// upgrade path = upstream pi-web-agent accepting a config-dir resolver.
|
|
71
|
+
return path.join(getAgentDir(), 'extensions', 'pi-web-agent', 'state.json');
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
async function readPackageVersion(packageRoot: string): Promise<string | undefined> {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
DynamicBorder,
|
|
11
11
|
getSettingsListTheme,
|
|
12
12
|
type ExtensionAPI
|
|
13
|
-
} from '@
|
|
13
|
+
} from '@selesai/code';
|
|
14
14
|
import { Container, SelectList, SettingsList, Text, type SelectItem, type SettingItem } from '@earendil-works/pi-tui';
|
|
15
15
|
import {
|
|
16
16
|
DEFAULT_PRESENTATION_CONFIG,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI } from '@
|
|
1
|
+
import type { ExtensionAPI } from '@selesai/code';
|
|
2
2
|
import { DEFAULT_BACKEND_CONFIG, type BackendConfig } from './backends/config.js';
|
|
3
3
|
import { Type } from 'typebox';
|
|
4
4
|
import { registerWebAgentConfigCommands } from './commands/web-agent-config.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { getAgentDir } from '@
|
|
3
|
+
import { CONFIG_DIR_NAME, getAgentDir } from '@selesai/code';
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_BACKEND_CONFIG,
|
|
6
6
|
extractBackendConfigOverride,
|
|
@@ -46,15 +46,17 @@ export function getPresentationConfigPaths(options: PresentationConfigStoreOptio
|
|
|
46
46
|
// CONFIG_DIR_NAME (e.g. .selesai) instead of upstream's hardcoded .pi. Ceiling:
|
|
47
47
|
// if selesai changes the extensions config layout, update here; upgrade path =
|
|
48
48
|
// upstream pi-web-agent accepting a config-dir resolver.
|
|
49
|
-
const homeDir = options.homeDir ?? process.env.USERPROFILE ?? process.env.HOME ?? '';
|
|
50
49
|
const projectDir = options.projectDir ?? process.cwd();
|
|
51
50
|
const agentDir = getAgentDir();
|
|
52
|
-
// agentDir is already <homeDir>/<CONFIG_DIR_NAME>/agent; derive the home-relative config dir.
|
|
53
51
|
const globalConfigRoot = path.join(agentDir, 'extensions', 'pi-web-agent', 'config.json');
|
|
54
52
|
|
|
55
53
|
return {
|
|
56
54
|
globalPath: globalConfigRoot,
|
|
57
|
-
|
|
55
|
+
// ponytail: project config dir is CONFIG_DIR_NAME (e.g. .selesai), not the
|
|
56
|
+
// upstream hardcoded .pi. Ceiling: assumes the project-local extension
|
|
57
|
+
// config layout matches the global one; upgrade path = upstream accepting a
|
|
58
|
+
// config-dir resolver for project paths too.
|
|
59
|
+
projectPath: path.join(projectDir, CONFIG_DIR_NAME, 'extensions', 'pi-web-agent', 'config.json')
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Pure helper functions for the question extension.
|
|
2
2
|
|
|
3
|
-
import type { MarkdownTheme } from "@
|
|
3
|
+
import type { MarkdownTheme } from "@selesai/code";
|
|
4
4
|
|
|
5
5
|
import type { QuestionOption, QuestionResponse, RawOption } from "./types.ts";
|
|
6
6
|
|
|
@@ -44,7 +44,7 @@ export function safeMarkdownTheme(): MarkdownTheme | undefined {
|
|
|
44
44
|
if (_getMarkdownTheme === undefined) {
|
|
45
45
|
// Dynamic require avoids breaking module load when package is absent.
|
|
46
46
|
_getMarkdownTheme = (globalThis as any).require
|
|
47
|
-
? (globalThis as any).require("@
|
|
47
|
+
? (globalThis as any).require("@selesai/code").getMarkdownTheme
|
|
48
48
|
: null;
|
|
49
49
|
}
|
|
50
50
|
if (!_getMarkdownTheme) return undefined;
|
|
@@ -99,4 +99,4 @@ export function parseDialogSelections(input: string): string[] {
|
|
|
99
99
|
|
|
100
100
|
export function isCancelled(value: unknown): value is null | undefined {
|
|
101
101
|
return value === null || value === undefined;
|
|
102
|
-
}
|
|
102
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Owns: QuestionComponent (mode-switching + editor lifecycle + box borders),
|
|
3
3
|
// tool registration, execute() orchestration via UIProtocol.
|
|
4
4
|
|
|
5
|
-
import type { ExtensionAPI, KeybindingsManager, Theme } from "@
|
|
5
|
+
import type { ExtensionAPI, KeybindingsManager, Theme } from "@selesai/code";
|
|
6
6
|
import {
|
|
7
7
|
Container,
|
|
8
8
|
type Component,
|
|
@@ -668,4 +668,4 @@ export default function questionExtension(pi: ExtensionAPI) {
|
|
|
668
668
|
return new Text(text, 0, 0);
|
|
669
669
|
},
|
|
670
670
|
});
|
|
671
|
-
}
|
|
671
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Vim-style navigation aliases (ctrl+j/k don't collide with fuzzy-search input).
|
|
2
2
|
|
|
3
|
-
import type { KeybindingsManager } from "@
|
|
3
|
+
import type { KeybindingsManager } from "@selesai/code";
|
|
4
4
|
import { matchesKey } from "@earendil-works/pi-tui";
|
|
5
5
|
|
|
6
6
|
import { VIM_DOWN, VIM_UP } from "./constants.ts";
|
|
@@ -11,4 +11,4 @@ export function matchesUp(data: string, keybindings: KeybindingsManager): boolea
|
|
|
11
11
|
|
|
12
12
|
export function matchesDown(data: string, keybindings: KeybindingsManager): boolean {
|
|
13
13
|
return keybindings.matches(data, "tui.select.down") || matchesKey(data, VIM_DOWN);
|
|
14
|
-
}
|
|
14
|
+
}
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
"extensions": ["./index.ts"]
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
11
|
-
"@
|
|
11
|
+
"@selesai/code": "*",
|
|
12
12
|
"@earendil-works/pi-tui": "*",
|
|
13
13
|
"typebox": "*"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"test": "node --experimental-strip-types --test tests/**/*.test.ts"
|
|
17
17
|
}
|
|
18
|
-
}
|
|
18
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Single-vs-multi is a SelectionMode flag, not a separate class.
|
|
3
3
|
// Fuzzy search + split-pane preview are single-select-only (matching original behavior).
|
|
4
4
|
|
|
5
|
-
import type { KeybindingsManager, Theme } from "@
|
|
5
|
+
import type { KeybindingsManager, Theme } from "@selesai/code";
|
|
6
6
|
import {
|
|
7
7
|
type Component,
|
|
8
8
|
fuzzyFilter,
|
|
@@ -495,4 +495,4 @@ export class QuestionList implements Component {
|
|
|
495
495
|
return `${left}${separator}${right}`;
|
|
496
496
|
});
|
|
497
497
|
}
|
|
498
|
-
}
|
|
498
|
+
}
|
|
@@ -5,7 +5,7 @@ import { Key, matchesKey } from "@earendil-works/pi-tui";
|
|
|
5
5
|
|
|
6
6
|
import { QuestionList } from "../question-list.ts";
|
|
7
7
|
import { MultiSelect, SingleSelect } from "../selection-mode.ts";
|
|
8
|
-
import type { KeybindingsManager, Theme } from "@
|
|
8
|
+
import type { KeybindingsManager, Theme } from "@selesai/code";
|
|
9
9
|
import type { ResolvedShortcut } from "../types.ts";
|
|
10
10
|
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
@@ -227,4 +227,4 @@ test("onSubmit passes commentEnabled=true when comment is enabled", () => {
|
|
|
227
227
|
list.handleInput(CTRL_G); // enable comment
|
|
228
228
|
list.handleInput(ENTER); // confirm
|
|
229
229
|
assert.deepEqual(submitted, [["a"], true]);
|
|
230
|
-
});
|
|
230
|
+
});
|