@blackbelt-technology/pi-agent-dashboard 0.3.0 → 0.4.0
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/AGENTS.md +67 -116
- package/README.md +93 -7
- package/docs/architecture.md +408 -9
- package/package.json +6 -4
- package/packages/extension/package.json +11 -3
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +201 -0
- package/packages/extension/src/__tests__/git-info.test.ts +67 -55
- package/packages/extension/src/__tests__/openspec-poller.test.ts +101 -96
- package/packages/extension/src/__tests__/process-scanner-kill.test.ts +61 -0
- package/packages/extension/src/__tests__/provider-register-reload.test.ts +394 -0
- package/packages/extension/src/__tests__/server-auto-start.test.ts +95 -4
- package/packages/extension/src/__tests__/server-launcher.test.ts +16 -0
- package/packages/extension/src/bridge.ts +69 -2
- package/packages/extension/src/dev-build.ts +1 -1
- package/packages/extension/src/git-info.ts +9 -19
- package/packages/extension/src/pi-env.d.ts +1 -0
- package/packages/extension/src/process-scanner.ts +72 -38
- package/packages/extension/src/provider-register.ts +304 -16
- package/packages/extension/src/server-auto-start.ts +27 -1
- package/packages/extension/src/server-launcher.ts +71 -27
- package/packages/server/package.json +16 -2
- package/packages/server/src/__tests__/bootstrap-queue.test.ts +120 -0
- package/packages/server/src/__tests__/bootstrap-routes.test.ts +125 -0
- package/packages/server/src/__tests__/bootstrap-state.test.ts +119 -0
- package/packages/server/src/__tests__/browse-endpoint.test.ts +17 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +11 -0
- package/packages/server/src/__tests__/concurrent-launch.test.ts +110 -0
- package/packages/server/src/__tests__/config-api.test.ts +68 -0
- package/packages/server/src/__tests__/crash-recovery.test.ts +88 -0
- package/packages/server/src/__tests__/directory-service.test.ts +234 -8
- package/packages/server/src/__tests__/editor-registry.test.ts +28 -15
- package/packages/server/src/__tests__/extension-register-appimage.test.ts +5 -1
- package/packages/server/src/__tests__/extension-register.test.ts +3 -1
- package/packages/server/src/__tests__/find-port-holders.test.ts +94 -0
- package/packages/server/src/__tests__/force-kill-handler.test.ts +57 -8
- package/packages/server/src/__tests__/home-lock-escape-hatch.test.ts +60 -0
- package/packages/server/src/__tests__/home-lock-release.test.ts +85 -0
- package/packages/server/src/__tests__/home-lock.test.ts +308 -0
- package/packages/server/src/__tests__/is-pi-process.test.ts +36 -0
- package/packages/server/src/__tests__/node-guard.test.ts +85 -0
- package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +5 -1
- package/packages/server/src/__tests__/package-manager-wrapper.test.ts +45 -10
- package/packages/server/src/__tests__/pi-version-skew.test.ts +165 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +73 -4
- package/packages/server/src/__tests__/process-manager.test.ts +45 -18
- package/packages/server/src/__tests__/provider-probe.test.ts +287 -0
- package/packages/server/src/__tests__/provider-test-route.test.ts +149 -0
- package/packages/server/src/__tests__/restart-helper.test.ts +83 -0
- package/packages/server/src/__tests__/session-action-handler-headless-reload.test.ts +467 -0
- package/packages/server/src/__tests__/session-action-handler-reload-predicate.test.ts +73 -0
- package/packages/server/src/__tests__/session-action-handler-spawn-error.test.ts +74 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +41 -1
- package/packages/server/src/__tests__/tool-routes.test.ts +277 -0
- package/packages/server/src/__tests__/trusted-networks-config.test.ts +19 -0
- package/packages/server/src/__tests__/trusted-networks-no-oauth-roundtrip.test.ts +126 -0
- package/packages/server/src/__tests__/tunnel-cleanup.test.ts +90 -0
- package/packages/server/src/__tests__/tunnel.test.ts +13 -7
- package/packages/server/src/__tests__/wsl-tmux-probe-cache.test.ts +44 -0
- package/packages/server/src/bootstrap-queue.ts +130 -0
- package/packages/server/src/bootstrap-state.ts +131 -0
- package/packages/server/src/browse.ts +8 -3
- package/packages/server/src/browser-handlers/directory-handler.ts +23 -8
- package/packages/server/src/browser-handlers/session-action-handler.ts +213 -79
- package/packages/server/src/browser-handlers/session-action-helpers.ts +36 -0
- package/packages/server/src/cli.ts +256 -32
- package/packages/server/src/config-api.ts +16 -0
- package/packages/server/src/directory-service.ts +270 -39
- package/packages/server/src/editor-detection.ts +12 -9
- package/packages/server/src/editor-manager.ts +19 -4
- package/packages/server/src/editor-pid-registry.ts +9 -8
- package/packages/server/src/editor-registry.ts +22 -25
- package/packages/server/src/git-operations.ts +1 -1
- package/packages/server/src/headless-pid-registry.ts +7 -20
- package/packages/server/src/home-lock-release.ts +72 -0
- package/packages/server/src/home-lock.ts +389 -0
- package/packages/server/src/node-guard.ts +52 -0
- package/packages/server/src/package-manager-wrapper.ts +207 -47
- package/packages/server/src/pi-core-checker.ts +1 -1
- package/packages/server/src/pi-core-updater.ts +7 -1
- package/packages/server/src/pi-resource-scanner.ts +5 -8
- package/packages/server/src/pi-version-skew.ts +196 -0
- package/packages/server/src/preferences-store.ts +17 -3
- package/packages/server/src/process-manager.ts +403 -222
- package/packages/server/src/provider-probe.ts +234 -0
- package/packages/server/src/restart-helper.ts +130 -0
- package/packages/server/src/routes/bootstrap-routes.ts +88 -0
- package/packages/server/src/routes/openspec-routes.ts +25 -1
- package/packages/server/src/routes/pi-core-routes.ts +24 -1
- package/packages/server/src/routes/provider-auth-routes.ts +8 -8
- package/packages/server/src/routes/provider-routes.ts +43 -0
- package/packages/server/src/routes/recommended-routes.ts +10 -12
- package/packages/server/src/routes/system-routes.ts +20 -33
- package/packages/server/src/routes/tool-routes.ts +153 -0
- package/packages/server/src/server-pid.ts +5 -9
- package/packages/server/src/server.ts +211 -10
- package/packages/server/src/session-api.ts +77 -8
- package/packages/server/src/session-bootstrap.ts +17 -3
- package/packages/server/src/session-diff.ts +21 -21
- package/packages/server/src/terminal-manager.ts +61 -20
- package/packages/server/src/tunnel.ts +42 -28
- package/packages/shared/package.json +10 -3
- package/packages/shared/src/__tests__/{tool-resolver.test.ts → binary-lookup.test.ts} +32 -12
- package/packages/shared/src/__tests__/bootstrap/README.md +133 -0
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +370 -0
- package/packages/shared/src/__tests__/bootstrap/assertions.ts +136 -0
- package/packages/shared/src/__tests__/bootstrap/cube.test.ts +47 -0
- package/packages/shared/src/__tests__/bootstrap/cube.ts +66 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +83 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +89 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +33 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/d-overrides.test.ts.snap +20 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +61 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +33 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +46 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/j-path-gui-minimal.test.ts.snap +12 -0
- package/packages/shared/src/__tests__/bootstrap/families/a-electron.test.ts +156 -0
- package/packages/shared/src/__tests__/bootstrap/families/b-npm-global.test.ts +157 -0
- package/packages/shared/src/__tests__/bootstrap/families/c-dev-monorepo.test.ts +102 -0
- package/packages/shared/src/__tests__/bootstrap/families/d-overrides.test.ts +76 -0
- package/packages/shared/src/__tests__/bootstrap/families/e-stale-partial.test.ts +94 -0
- package/packages/shared/src/__tests__/bootstrap/families/f-cwd-variants.test.ts +87 -0
- package/packages/shared/src/__tests__/bootstrap/families/g-windows-specifics.test.ts +143 -0
- package/packages/shared/src/__tests__/bootstrap/families/h-home-drift.test.ts +64 -0
- package/packages/shared/src/__tests__/bootstrap/families/i-malformed-settings.test.ts +77 -0
- package/packages/shared/src/__tests__/bootstrap/families/index.ts +19 -0
- package/packages/shared/src/__tests__/bootstrap/families/j-path-gui-minimal.test.ts +61 -0
- package/packages/shared/src/__tests__/bootstrap/families/k-dashboard-absent.test.ts +50 -0
- package/packages/shared/src/__tests__/bootstrap/families/l-instance-coordination.test.ts +272 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +58 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/electron-layout.ts +84 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/index.ts +9 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/managed-install.ts +85 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/npm-global-layout.ts +122 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/pi-versions.ts +36 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/settings-json.ts +39 -0
- package/packages/shared/src/__tests__/bootstrap/harness.smoke.test.ts +220 -0
- package/packages/shared/src/__tests__/bootstrap/harness.ts +413 -0
- package/packages/shared/src/__tests__/bootstrap/scenarios-skipped.ts +125 -0
- package/packages/shared/src/__tests__/bootstrap/scenarios.ts +132 -0
- package/packages/shared/src/__tests__/bridge-register.test.ts +29 -6
- package/packages/shared/src/__tests__/config-openspec.test.ts +106 -0
- package/packages/shared/src/__tests__/config.test.ts +56 -0
- package/packages/shared/src/__tests__/detached-spawn.test.ts +243 -0
- package/packages/shared/src/__tests__/managed-paths.test.ts +60 -0
- package/packages/shared/src/__tests__/no-direct-child-process.test.ts +112 -0
- package/packages/shared/src/__tests__/no-direct-platform-branch.test.ts +174 -0
- package/packages/shared/src/__tests__/no-direct-process-kill.test.ts +105 -0
- package/packages/shared/src/__tests__/platform-commands.test.ts +108 -0
- package/packages/shared/src/__tests__/platform-exec.test.ts +103 -0
- package/packages/shared/src/__tests__/platform-git.test.ts +194 -0
- package/packages/shared/src/__tests__/platform-npm.test.ts +137 -0
- package/packages/shared/src/__tests__/platform-openspec.test.ts +92 -0
- package/packages/shared/src/__tests__/platform-paths.test.ts +284 -0
- package/packages/shared/src/__tests__/platform-process-scan.test.ts +55 -0
- package/packages/shared/src/__tests__/platform-process.test.ts +160 -0
- package/packages/shared/src/__tests__/platform-runner.test.ts +173 -0
- package/packages/shared/src/__tests__/platform-shell.test.ts +74 -0
- package/packages/shared/src/__tests__/process-identify.test.ts +113 -0
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +40 -7
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +43 -7
- package/packages/shared/src/__tests__/semaphore.test.ts +119 -0
- package/packages/shared/src/__tests__/spawn-mechanism.test.ts +131 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +239 -0
- package/packages/shared/src/__tests__/tool-registry-overrides.test.ts +137 -0
- package/packages/shared/src/__tests__/tool-registry-registry.test.ts +343 -0
- package/packages/shared/src/bootstrap-install.ts +212 -0
- package/packages/shared/src/bridge-register.ts +87 -20
- package/packages/shared/src/browser-protocol.ts +71 -1
- package/packages/shared/src/config.ts +87 -15
- package/packages/shared/src/managed-paths.ts +31 -4
- package/packages/shared/src/openspec-poller.ts +63 -46
- package/packages/shared/src/{tool-resolver.ts → platform/binary-lookup.ts} +125 -25
- package/packages/shared/src/platform/commands.ts +100 -0
- package/packages/shared/src/platform/detached-spawn.ts +305 -0
- package/packages/shared/src/platform/exec.ts +220 -0
- package/packages/shared/src/platform/git.ts +155 -0
- package/packages/shared/src/platform/index.ts +15 -0
- package/packages/shared/src/platform/npm.ts +162 -0
- package/packages/shared/src/platform/openspec.ts +91 -0
- package/packages/shared/src/platform/paths.ts +276 -0
- package/packages/shared/src/platform/process-identify.ts +126 -0
- package/packages/shared/src/platform/process-scan.ts +94 -0
- package/packages/shared/src/platform/process.ts +168 -0
- package/packages/shared/src/platform/runner.ts +369 -0
- package/packages/shared/src/platform/shell.ts +44 -0
- package/packages/shared/src/platform/spawn-mechanism.ts +124 -0
- package/packages/shared/src/platform/subprocess-adapter.ts +124 -0
- package/packages/shared/src/recommended-extensions.ts +18 -2
- package/packages/shared/src/resolve-jiti.ts +62 -3
- package/packages/shared/src/rest-api.ts +26 -0
- package/packages/shared/src/semaphore.ts +83 -0
- package/packages/shared/src/tool-registry/definitions.ts +342 -0
- package/packages/shared/src/tool-registry/index.ts +56 -0
- package/packages/shared/src/tool-registry/overrides.ts +118 -0
- package/packages/shared/src/tool-registry/registry.ts +262 -0
- package/packages/shared/src/tool-registry/strategies.ts +198 -0
- package/packages/shared/src/tool-registry/types.ts +180 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for packages/shared/src/platform/shell.ts.
|
|
3
|
+
* All four platform × env-present/absent branches are exercised via injected
|
|
4
|
+
* `platform` and `env`. No `process.env` or `process.platform` mutation.
|
|
5
|
+
* See change: consolidate-platform-handlers.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect } from "vitest";
|
|
8
|
+
import { detectShell, getTerminalEnvHints } from "../platform/shell.js";
|
|
9
|
+
|
|
10
|
+
describe("detectShell", () => {
|
|
11
|
+
it("uses COMSPEC on Windows when present", () => {
|
|
12
|
+
expect(detectShell({
|
|
13
|
+
platform: "win32",
|
|
14
|
+
env: { COMSPEC: "C:\\Windows\\System32\\cmd.exe" },
|
|
15
|
+
})).toBe("C:\\Windows\\System32\\cmd.exe");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("falls back to powershell.exe on Windows when COMSPEC missing", () => {
|
|
19
|
+
expect(detectShell({ platform: "win32", env: {} })).toBe("powershell.exe");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("uses SHELL on Linux when present", () => {
|
|
23
|
+
expect(detectShell({
|
|
24
|
+
platform: "linux",
|
|
25
|
+
env: { SHELL: "/bin/zsh" },
|
|
26
|
+
})).toBe("/bin/zsh");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("falls back to /bin/bash on Linux when SHELL missing", () => {
|
|
30
|
+
expect(detectShell({ platform: "linux", env: {} })).toBe("/bin/bash");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("uses SHELL on macOS when present", () => {
|
|
34
|
+
expect(detectShell({
|
|
35
|
+
platform: "darwin",
|
|
36
|
+
env: { SHELL: "/bin/zsh" },
|
|
37
|
+
})).toBe("/bin/zsh");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("falls back to /bin/bash on macOS when SHELL missing", () => {
|
|
41
|
+
expect(detectShell({ platform: "darwin", env: {} })).toBe("/bin/bash");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("ignores SHELL on Windows (uses COMSPEC path)", () => {
|
|
45
|
+
// Even if some Windows environment sets SHELL (e.g. Git Bash), the PTY
|
|
46
|
+
// primitive must use COMSPEC / powershell because that's what node-pty
|
|
47
|
+
// can actually spawn on win32.
|
|
48
|
+
expect(detectShell({
|
|
49
|
+
platform: "win32",
|
|
50
|
+
env: { SHELL: "/usr/bin/bash" },
|
|
51
|
+
})).toBe("powershell.exe");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("getTerminalEnvHints", () => {
|
|
56
|
+
it("adds TERM=cygwin on Windows when TERM is not set", () => {
|
|
57
|
+
expect(getTerminalEnvHints({ platform: "win32", env: {} })).toEqual({ TERM: "cygwin" });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("leaves TERM alone on Windows when already set", () => {
|
|
61
|
+
expect(getTerminalEnvHints({
|
|
62
|
+
platform: "win32",
|
|
63
|
+
env: { TERM: "xterm-256color" },
|
|
64
|
+
})).toEqual({});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("adds no hints on Linux", () => {
|
|
68
|
+
expect(getTerminalEnvHints({ platform: "linux", env: {} })).toEqual({});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("adds no hints on macOS", () => {
|
|
72
|
+
expect(getTerminalEnvHints({ platform: "darwin", env: {} })).toEqual({});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for platform/process-identify.ts.
|
|
3
|
+
*
|
|
4
|
+
* Uses an injected fake `exec` so we can simulate ps/tasklist output on
|
|
5
|
+
* any host OS. All tests pass `platform` explicitly.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi } from "vitest";
|
|
8
|
+
import {
|
|
9
|
+
findPidByMarker,
|
|
10
|
+
isProcessLikePi,
|
|
11
|
+
isPiCommandLine,
|
|
12
|
+
} from "../platform/process-identify.js";
|
|
13
|
+
|
|
14
|
+
describe("isPiCommandLine", () => {
|
|
15
|
+
it("matches pi", () => {
|
|
16
|
+
expect(isPiCommandLine("/usr/bin/pi --mode rpc")).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
it("matches node", () => {
|
|
19
|
+
expect(isPiCommandLine("node cli.js")).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
it("matches pi even with path prefixes", () => {
|
|
22
|
+
expect(isPiCommandLine("/opt/foo/pi --args")).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
it("does not match unrelated processes", () => {
|
|
25
|
+
expect(isPiCommandLine("/bin/bash")).toBe(false);
|
|
26
|
+
expect(isPiCommandLine("/usr/bin/zsh")).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
it("does not match substrings without word boundary", () => {
|
|
29
|
+
// "pip" and "typescript" must not match pi or node.
|
|
30
|
+
expect(isPiCommandLine("pip install something")).toBe(false);
|
|
31
|
+
expect(isPiCommandLine("/usr/bin/typescript-compiler")).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("findPidByMarker", () => {
|
|
36
|
+
it("Windows returns empty array without execution", () => {
|
|
37
|
+
const exec = vi.fn(() => "should not be called");
|
|
38
|
+
const result = findPidByMarker("marker", { platform: "win32", exec: exec as any });
|
|
39
|
+
expect(result).toEqual([]);
|
|
40
|
+
expect(exec).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("Linux parses ps output and filters to sentinel lines", () => {
|
|
44
|
+
const fakeOutput = [
|
|
45
|
+
"12345 sh -c tail -f /dev/null | pi --mode rpc session-abc",
|
|
46
|
+
"67890 grep session-abc",
|
|
47
|
+
"11111 sleep 2147483647 | pi --mode rpc session-abc",
|
|
48
|
+
"22222 vim notes-about-session-abc.txt",
|
|
49
|
+
].join("\n");
|
|
50
|
+
const exec = vi.fn(() => fakeOutput) as any;
|
|
51
|
+
const result = findPidByMarker("session-abc", { platform: "linux", exec });
|
|
52
|
+
expect(result).toEqual([12345, 11111]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("macOS parses ps output similarly", () => {
|
|
56
|
+
const fakeOutput = "99999 tail -f /dev/null | pi --mode rpc s1";
|
|
57
|
+
const exec = vi.fn(() => fakeOutput) as any;
|
|
58
|
+
const result = findPidByMarker("s1", { platform: "darwin", exec });
|
|
59
|
+
expect(result).toEqual([99999]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("returns empty array when no match", () => {
|
|
63
|
+
const exec = vi.fn(() => "") as any;
|
|
64
|
+
const result = findPidByMarker("nothing", { platform: "linux", exec });
|
|
65
|
+
expect(result).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("returns empty array when exec throws (process dead / permission)", () => {
|
|
69
|
+
const exec = vi.fn(() => { throw new Error("no such command"); }) as any;
|
|
70
|
+
const result = findPidByMarker("x", { platform: "linux", exec });
|
|
71
|
+
expect(result).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("excludes lines without pi headless sentinels", () => {
|
|
75
|
+
const fakeOutput = "12345 some-random-process matching-marker-only";
|
|
76
|
+
const exec = vi.fn(() => fakeOutput) as any;
|
|
77
|
+
const result = findPidByMarker("matching-marker", { platform: "linux", exec });
|
|
78
|
+
expect(result).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("isProcessLikePi", () => {
|
|
83
|
+
it("Windows returns true unconditionally", () => {
|
|
84
|
+
const exec = vi.fn(() => "should not be called");
|
|
85
|
+
expect(isProcessLikePi(1234, { platform: "win32", exec: exec as any })).toBe(true);
|
|
86
|
+
expect(exec).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("Linux matches via /proc cmdline", () => {
|
|
90
|
+
const exec = vi.fn(() => "/usr/bin/node /opt/pi-coding-agent/dist/cli.js") as any;
|
|
91
|
+
expect(isProcessLikePi(1234, { platform: "linux", exec })).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("Linux does not match non-pi", () => {
|
|
95
|
+
const exec = vi.fn(() => "/bin/bash") as any;
|
|
96
|
+
expect(isProcessLikePi(1234, { platform: "linux", exec })).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("macOS uses ps -p -o command=", () => {
|
|
100
|
+
let capturedCmd = "";
|
|
101
|
+
const exec = ((cmd: string) => {
|
|
102
|
+
capturedCmd = cmd;
|
|
103
|
+
return "node cli.js --mode rpc";
|
|
104
|
+
}) as any;
|
|
105
|
+
expect(isProcessLikePi(555, { platform: "darwin", exec })).toBe(true);
|
|
106
|
+
expect(capturedCmd).toMatch(/ps -p 555 -o command=/);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("returns false when process has exited (exec throws)", () => {
|
|
110
|
+
const exec = vi.fn(() => { throw new Error("no such process"); }) as any;
|
|
111
|
+
expect(isProcessLikePi(9999, { platform: "linux", exec })).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import {
|
|
3
|
+
BUNDLED_EXTENSION_IDS,
|
|
3
4
|
RECOMMENDED_EXTENSIONS,
|
|
4
5
|
getRecommendedExtension,
|
|
5
6
|
getRecommendedByStatus,
|
|
@@ -35,18 +36,18 @@ describe("RECOMMENDED_EXTENSIONS manifest", () => {
|
|
|
35
36
|
}
|
|
36
37
|
});
|
|
37
38
|
|
|
38
|
-
it("pi-anthropic-messages is marked required and uses
|
|
39
|
+
it("pi-anthropic-messages is marked required and uses HTTPS git URL", () => {
|
|
39
40
|
const entry = getRecommendedExtension("pi-anthropic-messages");
|
|
40
41
|
expect(entry).toBeDefined();
|
|
41
42
|
expect(entry?.status).toBe("required");
|
|
42
|
-
expect(entry?.source).toContain("
|
|
43
|
+
expect(entry?.source).toContain("https://github.com/BlackBeltTechnology/pi-anthropic-messages.git");
|
|
43
44
|
expect(entry?.autowired).toBe(true);
|
|
44
45
|
});
|
|
45
46
|
|
|
46
|
-
it("pi-flows uses
|
|
47
|
+
it("pi-flows uses HTTPS git URL and registers flow-engine tools", () => {
|
|
47
48
|
const entry = getRecommendedExtension("pi-flows");
|
|
48
49
|
expect(entry).toBeDefined();
|
|
49
|
-
expect(entry?.source).toBe("
|
|
50
|
+
expect(entry?.source).toBe("https://github.com/BlackBeltTechnology/pi-flows.git");
|
|
50
51
|
expect(entry?.toolsRegistered).toContain("subagent");
|
|
51
52
|
expect(entry?.toolsRegistered).toContain("flow_write");
|
|
52
53
|
});
|
|
@@ -65,12 +66,12 @@ describe("RECOMMENDED_EXTENSIONS manifest", () => {
|
|
|
65
66
|
);
|
|
66
67
|
});
|
|
67
68
|
|
|
68
|
-
it("git-sourced entries use the
|
|
69
|
+
it("git-sourced entries use the https://github.com/.../.git HTTPS form", () => {
|
|
69
70
|
const gitEntries = RECOMMENDED_EXTENSIONS.filter((e) =>
|
|
70
|
-
e.source.startsWith("
|
|
71
|
+
e.source.startsWith("https://github.com/"),
|
|
71
72
|
);
|
|
72
73
|
for (const entry of gitEntries) {
|
|
73
|
-
expect(entry.source).toMatch(/^
|
|
74
|
+
expect(entry.source).toMatch(/^https:\/\/github\.com\/[^/]+\/[^/]+\.git$/);
|
|
74
75
|
}
|
|
75
76
|
expect(gitEntries.map((e) => e.id).sort()).toEqual(
|
|
76
77
|
["pi-anthropic-messages", "pi-flows"].sort(),
|
|
@@ -121,3 +122,35 @@ describe("RecommendedExtension type", () => {
|
|
|
121
122
|
expect(entry.id).toBe("x");
|
|
122
123
|
});
|
|
123
124
|
});
|
|
125
|
+
|
|
126
|
+
// ── BUNDLED_EXTENSION_IDS manifest (task 2 of bundle-first-party-extensions) ──
|
|
127
|
+
|
|
128
|
+
describe("BUNDLED_EXTENSION_IDS manifest", () => {
|
|
129
|
+
it("contains exactly the v0.x initial bundled set", () => {
|
|
130
|
+
expect([...BUNDLED_EXTENSION_IDS].sort()).toEqual(
|
|
131
|
+
["pi-anthropic-messages", "pi-flows"].sort(),
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("every bundled id appears in RECOMMENDED_EXTENSIONS", () => {
|
|
136
|
+
const recommendedIds = new Set(RECOMMENDED_EXTENSIONS.map((e) => e.id));
|
|
137
|
+
for (const id of BUNDLED_EXTENSION_IDS) {
|
|
138
|
+
expect(recommendedIds.has(id)).toBe(true);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("every bundled id has a git-based source (no npm:, no local paths)", () => {
|
|
143
|
+
for (const id of BUNDLED_EXTENSION_IDS) {
|
|
144
|
+
const entry = RECOMMENDED_EXTENSIONS.find((e) => e.id === id);
|
|
145
|
+
expect(entry, `RECOMMENDED_EXTENSIONS missing entry for ${id}`).toBeDefined();
|
|
146
|
+
const source = entry!.source;
|
|
147
|
+
const isGit =
|
|
148
|
+
source.endsWith(".git") ||
|
|
149
|
+
source.startsWith("git@") ||
|
|
150
|
+
source.startsWith("git:") ||
|
|
151
|
+
/^https?:\/\/.+\/.+/.test(source);
|
|
152
|
+
expect(isGit, `${id} source is not git-based: ${source}`).toBe(true);
|
|
153
|
+
expect(source.startsWith("npm:"), `${id} must not be an npm source`).toBe(false);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -1,17 +1,53 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { buildJitiRegisterUrl, resolveJitiImport } from "../resolve-jiti.js";
|
|
3
|
+
|
|
4
|
+
describe("buildJitiRegisterUrl", () => {
|
|
5
|
+
// Pure function: given a jiti package.json path, return the file:// URL of
|
|
6
|
+
// its register hook. The URL contract is the critical invariant — Node's
|
|
7
|
+
// --import on Windows rejects raw drive-letter paths (parses "C:" as a
|
|
8
|
+
// URL scheme). See change: fix-windows-server-parity.
|
|
9
|
+
|
|
10
|
+
it("returns a file:// URL", () => {
|
|
11
|
+
const url = buildJitiRegisterUrl("/usr/lib/node_modules/@mariozechner/jiti/package.json");
|
|
12
|
+
expect(url.startsWith("file://")).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("URL is parseable by new URL() without throwing", () => {
|
|
16
|
+
const url = buildJitiRegisterUrl("/usr/lib/node_modules/@mariozechner/jiti/package.json");
|
|
17
|
+
expect(() => new URL(url)).not.toThrow();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("points at lib/jiti-register.mjs under the package dir", () => {
|
|
21
|
+
const url = buildJitiRegisterUrl("/usr/lib/node_modules/@mariozechner/jiti/package.json");
|
|
22
|
+
expect(url.endsWith("/lib/jiti-register.mjs")).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("handles Windows drive-letter paths (regression for ERR_UNSUPPORTED_ESM_URL_SCHEME)", () => {
|
|
26
|
+
// This is the exact shape that crashed pre-fix: a raw path with a
|
|
27
|
+
// drive letter was passed to `node --import` and Node parsed "B:" as
|
|
28
|
+
// a URL scheme. A file:// URL sidesteps the parser entirely.
|
|
29
|
+
const url = buildJitiRegisterUrl("B:\\Dev\\Nodejs\\global\\node_modules\\@mariozechner\\jiti\\package.json");
|
|
30
|
+
expect(url.startsWith("file:///")).toBe(true);
|
|
31
|
+
expect(() => new URL(url)).not.toThrow();
|
|
32
|
+
expect(new URL(url).protocol).toBe("file:");
|
|
33
|
+
// The drive letter survives as part of the pathname, not as a protocol
|
|
34
|
+
expect(url.toLowerCase()).toContain("/b:/");
|
|
35
|
+
expect(url.endsWith("/lib/jiti-register.mjs")).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
});
|
|
2
39
|
|
|
3
40
|
describe("resolveJitiImport", () => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
41
|
+
// Integration-lite: in vitest context (not inside pi's jiti loader),
|
|
42
|
+
// process.argv[1] points at the test runner, not pi — so peer-dep
|
|
43
|
+
// resolution fails and the function throws a helpful error. The
|
|
44
|
+
// URL-contract behavior is covered by buildJitiRegisterUrl above.
|
|
8
45
|
|
|
46
|
+
it("throws with clear error when pi-coding-agent is not resolvable", () => {
|
|
9
47
|
expect(() => resolveJitiImport()).toThrow("Cannot find pi's TypeScript loader");
|
|
10
48
|
});
|
|
11
49
|
|
|
12
|
-
it("error message mentions pi-coding-agent",
|
|
13
|
-
const { resolveJitiImport } = await import("../resolve-jiti.js");
|
|
14
|
-
|
|
50
|
+
it("error message mentions pi-coding-agent", () => {
|
|
15
51
|
expect(() => resolveJitiImport()).toThrow("pi-coding-agent");
|
|
16
52
|
});
|
|
17
53
|
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createSemaphore } from "../semaphore.js";
|
|
3
|
+
|
|
4
|
+
function defer<T = void>(): { promise: Promise<T>; resolve: (v: T) => void; reject: (e: any) => void } {
|
|
5
|
+
let resolve!: (v: T) => void;
|
|
6
|
+
let reject!: (e: any) => void;
|
|
7
|
+
const promise = new Promise<T>((res, rej) => { resolve = res; reject = rej; });
|
|
8
|
+
return { promise, resolve, reject };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("createSemaphore", () => {
|
|
12
|
+
it("throws when max < 1", () => {
|
|
13
|
+
expect(() => createSemaphore(0)).toThrow();
|
|
14
|
+
expect(() => createSemaphore(-1)).toThrow();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("runs tasks immediately up to the cap", async () => {
|
|
18
|
+
const sem = createSemaphore(2);
|
|
19
|
+
const a = defer<string>();
|
|
20
|
+
const b = defer<string>();
|
|
21
|
+
const pa = sem.run(() => a.promise);
|
|
22
|
+
const pb = sem.run(() => b.promise);
|
|
23
|
+
expect(sem.size()).toBe(2);
|
|
24
|
+
a.resolve("a");
|
|
25
|
+
b.resolve("b");
|
|
26
|
+
expect(await pa).toBe("a");
|
|
27
|
+
expect(await pb).toBe("b");
|
|
28
|
+
expect(sem.size()).toBe(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("caps concurrency: third task waits", async () => {
|
|
32
|
+
const sem = createSemaphore(2);
|
|
33
|
+
const a = defer<string>();
|
|
34
|
+
const b = defer<string>();
|
|
35
|
+
const c = defer<string>();
|
|
36
|
+
|
|
37
|
+
let cStarted = false;
|
|
38
|
+
const pa = sem.run(() => a.promise);
|
|
39
|
+
const pb = sem.run(() => b.promise);
|
|
40
|
+
const pc = sem.run(() => { cStarted = true; return c.promise; });
|
|
41
|
+
|
|
42
|
+
// Wait a microtask for queue placement
|
|
43
|
+
await Promise.resolve();
|
|
44
|
+
expect(cStarted).toBe(false);
|
|
45
|
+
expect(sem.size()).toBe(3); // active + queued
|
|
46
|
+
|
|
47
|
+
a.resolve("a");
|
|
48
|
+
await pa;
|
|
49
|
+
// c should now be started
|
|
50
|
+
await Promise.resolve();
|
|
51
|
+
expect(cStarted).toBe(true);
|
|
52
|
+
b.resolve("b");
|
|
53
|
+
c.resolve("c");
|
|
54
|
+
expect(await pb).toBe("b");
|
|
55
|
+
expect(await pc).toBe("c");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("FIFO order of queued tasks", async () => {
|
|
59
|
+
const sem = createSemaphore(1);
|
|
60
|
+
const order: string[] = [];
|
|
61
|
+
const blockers = [defer<void>(), defer<void>(), defer<void>()];
|
|
62
|
+
const ps = blockers.map((d, i) =>
|
|
63
|
+
sem.run(async () => { order.push(`start-${i}`); await d.promise; order.push(`end-${i}`); }),
|
|
64
|
+
);
|
|
65
|
+
await Promise.resolve();
|
|
66
|
+
blockers[0].resolve(); await ps[0];
|
|
67
|
+
blockers[1].resolve(); await ps[1];
|
|
68
|
+
blockers[2].resolve(); await ps[2];
|
|
69
|
+
expect(order).toEqual(["start-0", "end-0", "start-1", "end-1", "start-2", "end-2"]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("releases slot on reject so queued tasks still run", async () => {
|
|
73
|
+
const sem = createSemaphore(1);
|
|
74
|
+
const failed = sem.run(async () => { throw new Error("boom"); });
|
|
75
|
+
await expect(failed).rejects.toThrow("boom");
|
|
76
|
+
const ok = sem.run(async () => "ok");
|
|
77
|
+
expect(await ok).toBe("ok");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("setMax increases cap and drains queued tasks immediately", async () => {
|
|
81
|
+
const sem = createSemaphore(1);
|
|
82
|
+
const a = defer<void>();
|
|
83
|
+
const b = defer<void>();
|
|
84
|
+
let bStarted = false;
|
|
85
|
+
const pa = sem.run(() => a.promise);
|
|
86
|
+
const pb = sem.run(() => { bStarted = true; return b.promise; });
|
|
87
|
+
await Promise.resolve();
|
|
88
|
+
expect(bStarted).toBe(false);
|
|
89
|
+
|
|
90
|
+
sem.setMax(2);
|
|
91
|
+
await Promise.resolve();
|
|
92
|
+
expect(bStarted).toBe(true);
|
|
93
|
+
|
|
94
|
+
a.resolve(); b.resolve();
|
|
95
|
+
await pa; await pb;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("setMax shrinking does not interrupt in-flight tasks but caps new ones", async () => {
|
|
99
|
+
const sem = createSemaphore(3);
|
|
100
|
+
const a = defer<void>();
|
|
101
|
+
const b = defer<void>();
|
|
102
|
+
const pa = sem.run(() => a.promise);
|
|
103
|
+
const pb = sem.run(() => b.promise);
|
|
104
|
+
|
|
105
|
+
sem.setMax(1);
|
|
106
|
+
const c = defer<void>();
|
|
107
|
+
let cStarted = false;
|
|
108
|
+
const pc = sem.run(() => { cStarted = true; return c.promise; });
|
|
109
|
+
await Promise.resolve();
|
|
110
|
+
expect(cStarted).toBe(false);
|
|
111
|
+
|
|
112
|
+
a.resolve(); b.resolve();
|
|
113
|
+
await pa; await pb;
|
|
114
|
+
await Promise.resolve();
|
|
115
|
+
expect(cStarted).toBe(true);
|
|
116
|
+
c.resolve();
|
|
117
|
+
await pc;
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for platform/spawn-mechanism.ts pure selector + argv builders.
|
|
3
|
+
*
|
|
4
|
+
* Every test passes `platform` explicitly. Never mutates process.platform.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from "vitest";
|
|
7
|
+
import {
|
|
8
|
+
selectMechanism,
|
|
9
|
+
buildWtArgs,
|
|
10
|
+
sessionFlagsToArgv,
|
|
11
|
+
type SpawnMechanismContext,
|
|
12
|
+
} from "../platform/spawn-mechanism.js";
|
|
13
|
+
|
|
14
|
+
function ctx(overrides: Partial<SpawnMechanismContext> = {}): SpawnMechanismContext {
|
|
15
|
+
return {
|
|
16
|
+
platform: "linux",
|
|
17
|
+
userStrategy: "tmux",
|
|
18
|
+
electronMode: false,
|
|
19
|
+
available: { tmux: false, wt: false, wslTmux: false },
|
|
20
|
+
...overrides,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("selectMechanism", () => {
|
|
25
|
+
it("electron mode always returns headless", () => {
|
|
26
|
+
expect(selectMechanism(ctx({ electronMode: true, platform: "win32", available: { tmux: false, wt: true, wslTmux: true } }))).toBe("headless");
|
|
27
|
+
expect(selectMechanism(ctx({ electronMode: true, platform: "linux", available: { tmux: true, wt: false, wslTmux: false } }))).toBe("headless");
|
|
28
|
+
expect(selectMechanism(ctx({ electronMode: true, platform: "darwin", available: { tmux: true, wt: false, wslTmux: false } }))).toBe("headless");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("userStrategy headless always returns headless", () => {
|
|
32
|
+
expect(selectMechanism(ctx({ userStrategy: "headless", platform: "win32", available: { tmux: false, wt: true, wslTmux: true } }))).toBe("headless");
|
|
33
|
+
expect(selectMechanism(ctx({ userStrategy: "headless", platform: "linux", available: { tmux: true, wt: false, wslTmux: false } }))).toBe("headless");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("Linux with tmux returns tmux", () => {
|
|
37
|
+
expect(selectMechanism(ctx({ platform: "linux", available: { tmux: true, wt: false, wslTmux: false } }))).toBe("tmux");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("macOS with tmux returns tmux", () => {
|
|
41
|
+
expect(selectMechanism(ctx({ platform: "darwin", available: { tmux: true, wt: false, wslTmux: false } }))).toBe("tmux");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("Linux without tmux returns headless", () => {
|
|
45
|
+
expect(selectMechanism(ctx({ platform: "linux", available: { tmux: false, wt: false, wslTmux: false } }))).toBe("headless");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("Windows with wt returns wt", () => {
|
|
49
|
+
expect(selectMechanism(ctx({ platform: "win32", available: { tmux: false, wt: true, wslTmux: false } }))).toBe("wt");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("Windows with wt AND wsl-tmux prefers wt", () => {
|
|
53
|
+
expect(selectMechanism(ctx({ platform: "win32", available: { tmux: false, wt: true, wslTmux: true } }))).toBe("wt");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("Windows with only wsl-tmux returns wsl-tmux", () => {
|
|
57
|
+
expect(selectMechanism(ctx({ platform: "win32", available: { tmux: false, wt: false, wslTmux: true } }))).toBe("wsl-tmux");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("Windows with nothing available returns headless", () => {
|
|
61
|
+
expect(selectMechanism(ctx({ platform: "win32", available: { tmux: false, wt: false, wslTmux: false } }))).toBe("headless");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("unknown platform falls back to headless", () => {
|
|
65
|
+
expect(selectMechanism(ctx({ platform: "openbsd" as NodeJS.Platform, available: { tmux: true, wt: false, wslTmux: false } }))).toBe("headless");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("buildWtArgs", () => {
|
|
70
|
+
it("produces argv in expected order", () => {
|
|
71
|
+
const argv = buildWtArgs({
|
|
72
|
+
cwd: "C:\\proj",
|
|
73
|
+
title: "proj",
|
|
74
|
+
piArgv: ["C:\\node.exe", "cli.js", "--mode", "rpc"],
|
|
75
|
+
});
|
|
76
|
+
expect(argv).toEqual([
|
|
77
|
+
"-w", "0",
|
|
78
|
+
"new-tab",
|
|
79
|
+
"-d", "C:\\proj",
|
|
80
|
+
"--title", "proj",
|
|
81
|
+
"--",
|
|
82
|
+
"C:\\node.exe", "cli.js", "--mode", "rpc",
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("preserves cwd with spaces as a single argv element", () => {
|
|
87
|
+
const argv = buildWtArgs({
|
|
88
|
+
cwd: "C:\\Users\\Bob's Project (2)",
|
|
89
|
+
title: "x",
|
|
90
|
+
piArgv: ["pi"],
|
|
91
|
+
});
|
|
92
|
+
expect(argv).toContain("C:\\Users\\Bob's Project (2)");
|
|
93
|
+
expect(argv.filter(a => a.includes("Bob"))).toHaveLength(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("places piArgv after -- sentinel with --fork intact", () => {
|
|
97
|
+
const argv = buildWtArgs({
|
|
98
|
+
cwd: "C:\\proj",
|
|
99
|
+
title: "proj",
|
|
100
|
+
piArgv: ["node.exe", "cli.js", "--fork", "C:\\x\\session.jsonl"],
|
|
101
|
+
});
|
|
102
|
+
const sentinelIdx = argv.indexOf("--");
|
|
103
|
+
expect(sentinelIdx).toBeGreaterThan(0);
|
|
104
|
+
expect(argv.slice(sentinelIdx + 1)).toEqual(["node.exe", "cli.js", "--fork", "C:\\x\\session.jsonl"]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("never includes -p profile flag", () => {
|
|
108
|
+
const argv = buildWtArgs({ cwd: "C:\\x", title: "y", piArgv: ["pi"] });
|
|
109
|
+
expect(argv).not.toContain("-p");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("sessionFlagsToArgv", () => {
|
|
114
|
+
it("returns --session file for continue mode", () => {
|
|
115
|
+
expect(sessionFlagsToArgv({ sessionFile: "/s/abc.jsonl", mode: "continue" })).toEqual(["--session", "/s/abc.jsonl"]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("returns --fork file for fork mode", () => {
|
|
119
|
+
expect(sessionFlagsToArgv({ sessionFile: "C:\\s\\abc.jsonl", mode: "fork" })).toEqual(["--fork", "C:\\s\\abc.jsonl"]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("returns empty array with no file", () => {
|
|
123
|
+
expect(sessionFlagsToArgv({})).toEqual([]);
|
|
124
|
+
expect(sessionFlagsToArgv({ mode: "continue" })).toEqual([]);
|
|
125
|
+
expect(sessionFlagsToArgv({ mode: "fork" })).toEqual([]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("returns empty array with file but no mode", () => {
|
|
129
|
+
expect(sessionFlagsToArgv({ sessionFile: "/s/x.jsonl" })).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
});
|