@blackbelt-technology/pi-agent-dashboard 0.3.0 → 0.4.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/AGENTS.md +87 -114
- package/README.md +408 -430
- package/docs/architecture.md +465 -12
- package/package.json +10 -5
- package/packages/extension/package.json +14 -4
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +40 -8
- package/packages/extension/src/__tests__/bridge-entry-id-pi-070.test.ts +174 -0
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +201 -0
- package/packages/extension/src/__tests__/event-forwarder.test.ts +30 -0
- package/packages/extension/src/__tests__/fork-entryid-timing.test.ts +64 -76
- package/packages/extension/src/__tests__/git-info.test.ts +67 -55
- package/packages/extension/src/__tests__/multiselect-list.test.ts +137 -0
- package/packages/extension/src/__tests__/no-session-replacement-calls.test.ts +99 -0
- 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/ask-user-tool.ts +5 -4
- package/packages/extension/src/bridge.ts +171 -17
- package/packages/extension/src/dev-build.ts +1 -1
- package/packages/extension/src/git-info.ts +9 -19
- package/packages/extension/src/multiselect-list.ts +146 -0
- package/packages/extension/src/multiselect-polyfill.ts +43 -0
- 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 +83 -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__/fixtures/fork-jsonl-roundtrip.jsonl +8 -0
- package/packages/server/src/__tests__/force-kill-handler.test.ts +57 -8
- package/packages/server/src/__tests__/fork-jsonl-roundtrip.test.ts +49 -0
- 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 +237 -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 +111 -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 +310 -39
- 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 +207 -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 +141 -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__/no-hardcoded-node-modules-paths.test.ts +176 -0
- package/packages/shared/src/__tests__/no-raw-node-import.test.ts +146 -0
- package/packages/shared/src/__tests__/node-spawn.test.ts +210 -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__/resolve-tool-cli.test.ts +105 -0
- 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__/state-replay-entry-id.test.ts +69 -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 +16 -0
- package/packages/shared/src/platform/node-spawn.ts +154 -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/protocol.ts +23 -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/state-replay.ts +9 -0
- package/packages/shared/src/tool-registry/definitions.ts +434 -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,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the pi version-skew detection module.
|
|
3
|
+
*
|
|
4
|
+
* See change: unified-bootstrap-install \u00a79.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import {
|
|
11
|
+
parseVersion,
|
|
12
|
+
compareVersions,
|
|
13
|
+
isBelow,
|
|
14
|
+
isAbove,
|
|
15
|
+
readPiCompatibility,
|
|
16
|
+
readCurrentPiVersion,
|
|
17
|
+
computeCompatibility,
|
|
18
|
+
_resetVersionSkewCache,
|
|
19
|
+
} from "../pi-version-skew.js";
|
|
20
|
+
import type { ToolRegistry, Resolution } from "@blackbelt-technology/pi-dashboard-shared/tool-registry/index.js";
|
|
21
|
+
|
|
22
|
+
describe("pi-version-skew", () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
_resetVersionSkewCache();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("parseVersion", () => {
|
|
28
|
+
it("parses simple x.y.z", () => {
|
|
29
|
+
expect(parseVersion("1.2.3")).toEqual([1, 2, 3]);
|
|
30
|
+
});
|
|
31
|
+
it("parses with v prefix", () => {
|
|
32
|
+
expect(parseVersion("v0.6.7")).toEqual([0, 6, 7]);
|
|
33
|
+
});
|
|
34
|
+
it("ignores pre-release suffix", () => {
|
|
35
|
+
expect(parseVersion("0.6.7-beta.1")).toEqual([0, 6, 7]);
|
|
36
|
+
});
|
|
37
|
+
it("ignores build metadata", () => {
|
|
38
|
+
expect(parseVersion("0.6.7+abc")).toEqual([0, 6, 7]);
|
|
39
|
+
});
|
|
40
|
+
it("returns null for non-numeric", () => {
|
|
41
|
+
expect(parseVersion("latest")).toBeNull();
|
|
42
|
+
expect(parseVersion("")).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("compareVersions", () => {
|
|
47
|
+
it("equal versions", () => {
|
|
48
|
+
expect(compareVersions("0.6.7", "0.6.7")).toBe(0);
|
|
49
|
+
});
|
|
50
|
+
it("lower major", () => {
|
|
51
|
+
expect(compareVersions("0.9.9", "1.0.0")).toBe(-1);
|
|
52
|
+
});
|
|
53
|
+
it("higher major", () => {
|
|
54
|
+
expect(compareVersions("2.0.0", "1.9.9")).toBe(1);
|
|
55
|
+
});
|
|
56
|
+
it("lower minor", () => {
|
|
57
|
+
expect(compareVersions("0.5.7", "0.6.0")).toBe(-1);
|
|
58
|
+
});
|
|
59
|
+
it("lower patch", () => {
|
|
60
|
+
expect(compareVersions("0.6.6", "0.6.7")).toBe(-1);
|
|
61
|
+
});
|
|
62
|
+
it("unparseable sorts as equal (conservative)", () => {
|
|
63
|
+
expect(compareVersions("latest", "0.6.7")).toBe(0);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("isBelow / isAbove", () => {
|
|
68
|
+
it("isBelow", () => {
|
|
69
|
+
expect(isBelow("0.5.0", "0.6.7")).toBe(true);
|
|
70
|
+
expect(isBelow("0.6.7", "0.6.7")).toBe(false);
|
|
71
|
+
expect(isBelow("0.7.0", "0.6.7")).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
it("isAbove with .x wildcard", () => {
|
|
74
|
+
expect(isAbove("0.10.0", "0.9.x")).toBe(true);
|
|
75
|
+
expect(isAbove("0.9.5", "0.9.x")).toBe(false);
|
|
76
|
+
expect(isAbove("0.9.99998", "0.9.x")).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
it("isAbove with concrete version", () => {
|
|
79
|
+
expect(isAbove("1.0.1", "1.0.0")).toBe(true);
|
|
80
|
+
expect(isAbove("1.0.0", "1.0.0")).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("readPiCompatibility", () => {
|
|
85
|
+
let tmpDir: string;
|
|
86
|
+
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-skew-"));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("reads the field from a well-formed package.json", () => {
|
|
92
|
+
const pkg = path.join(tmpDir, "package.json");
|
|
93
|
+
fs.writeFileSync(
|
|
94
|
+
pkg,
|
|
95
|
+
JSON.stringify({ piCompatibility: { minimum: "1.0.0", recommended: "1.2.0", maximum: "2.x" } }),
|
|
96
|
+
);
|
|
97
|
+
expect(readPiCompatibility(pkg)).toEqual({
|
|
98
|
+
minimum: "1.0.0",
|
|
99
|
+
recommended: "1.2.0",
|
|
100
|
+
maximum: "2.x",
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("tolerates null maximum", () => {
|
|
105
|
+
const pkg = path.join(tmpDir, "package.json");
|
|
106
|
+
fs.writeFileSync(
|
|
107
|
+
pkg,
|
|
108
|
+
JSON.stringify({ piCompatibility: { minimum: "1.0.0", recommended: "1.2.0", maximum: null } }),
|
|
109
|
+
);
|
|
110
|
+
expect(readPiCompatibility(pkg).maximum).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("falls back to defaults when field is missing", () => {
|
|
114
|
+
const pkg = path.join(tmpDir, "package.json");
|
|
115
|
+
fs.writeFileSync(pkg, JSON.stringify({ name: "something" }));
|
|
116
|
+
expect(readPiCompatibility(pkg)).toEqual({
|
|
117
|
+
minimum: "0.6.7",
|
|
118
|
+
recommended: "0.6.7",
|
|
119
|
+
maximum: null,
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("falls back to defaults when file is unreadable", () => {
|
|
124
|
+
expect(readPiCompatibility("/does/not/exist")).toEqual({
|
|
125
|
+
minimum: "0.6.7",
|
|
126
|
+
recommended: "0.6.7",
|
|
127
|
+
maximum: null,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("computeCompatibility", () => {
|
|
133
|
+
const range = { minimum: "0.6.7", recommended: "0.6.7", maximum: null };
|
|
134
|
+
|
|
135
|
+
it("returns range unchanged when pi is not yet installed", () => {
|
|
136
|
+
expect(computeCompatibility(range, undefined)).toEqual({ ...range, current: undefined });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("flags upgradeRecommended when below minimum", () => {
|
|
140
|
+
const out = computeCompatibility(range, "0.5.0");
|
|
141
|
+
expect(out.current).toBe("0.5.0");
|
|
142
|
+
expect(out.upgradeRecommended).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("flags upgradeRecommended when below recommended (but >= minimum)", () => {
|
|
146
|
+
const out = computeCompatibility(
|
|
147
|
+
{ minimum: "0.5.0", recommended: "0.6.7", maximum: null },
|
|
148
|
+
"0.6.0",
|
|
149
|
+
);
|
|
150
|
+
expect(out.upgradeRecommended).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("no upgrade flag when at or above recommended", () => {
|
|
154
|
+
const out = computeCompatibility(range, "0.6.7");
|
|
155
|
+
expect(out.upgradeRecommended).toBeUndefined();
|
|
156
|
+
expect(out.upgradeDashboard).toBeUndefined();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("flags upgradeDashboard when above maximum", () => {
|
|
160
|
+
const out = computeCompatibility(
|
|
161
|
+
{ minimum: "0.6.7", recommended: "0.6.7", maximum: "0.9.x" },
|
|
162
|
+
"0.10.0",
|
|
163
|
+
);
|
|
164
|
+
expect(out.upgradeDashboard).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// See change: warn-pi-version-skew-in-cli.
|
|
169
|
+
describe("readCurrentPiVersion (realpath symlinks)", () => {
|
|
170
|
+
let tmpDir: string;
|
|
171
|
+
|
|
172
|
+
beforeEach(() => {
|
|
173
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-skew-realpath-"));
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
function stubRegistry(resolvedPath: string): ToolRegistry {
|
|
177
|
+
return {
|
|
178
|
+
resolve: (name: string): Resolution => ({
|
|
179
|
+
ok: true,
|
|
180
|
+
name,
|
|
181
|
+
path: resolvedPath,
|
|
182
|
+
source: "system",
|
|
183
|
+
tried: [],
|
|
184
|
+
resolvedAt: Date.now(),
|
|
185
|
+
}),
|
|
186
|
+
} as unknown as ToolRegistry;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
it("npm-global symlinked bin launcher resolves to the real package.json", () => {
|
|
190
|
+
// Simulate ~/.nvm/.../bin/pi → ../lib/node_modules/@mariozechner/pi-coding-agent/dist/cli.js
|
|
191
|
+
const nodeRoot = path.join(tmpDir, "node-install");
|
|
192
|
+
const binDir = path.join(nodeRoot, "bin");
|
|
193
|
+
const pkgDir = path.join(nodeRoot, "lib", "node_modules", "@mariozechner", "pi-coding-agent");
|
|
194
|
+
const distDir = path.join(pkgDir, "dist");
|
|
195
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
196
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
197
|
+
fs.writeFileSync(path.join(distDir, "cli.js"), "// stub");
|
|
198
|
+
fs.writeFileSync(
|
|
199
|
+
path.join(pkgDir, "package.json"),
|
|
200
|
+
JSON.stringify({ name: "@mariozechner/pi-coding-agent", version: "0.70.0" }),
|
|
201
|
+
);
|
|
202
|
+
// The bad path (what old code computed) must NOT exist.
|
|
203
|
+
// That is: nodeRoot/package.json. We leave it absent.
|
|
204
|
+
|
|
205
|
+
const binLink = path.join(binDir, "pi");
|
|
206
|
+
// relative symlink matches npm's install layout.
|
|
207
|
+
fs.symlinkSync(
|
|
208
|
+
path.relative(binDir, path.join(distDir, "cli.js")),
|
|
209
|
+
binLink,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const registry = stubRegistry(binLink);
|
|
213
|
+
expect(readCurrentPiVersion(registry)).toBe("0.70.0");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("non-symlinked path is a no-op under realpath", () => {
|
|
217
|
+
const pkgDir = path.join(tmpDir, "pkg");
|
|
218
|
+
const distDir = path.join(pkgDir, "dist");
|
|
219
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
220
|
+
const cli = path.join(distDir, "cli.js");
|
|
221
|
+
fs.writeFileSync(cli, "// stub");
|
|
222
|
+
fs.writeFileSync(
|
|
223
|
+
path.join(pkgDir, "package.json"),
|
|
224
|
+
JSON.stringify({ name: "@mariozechner/pi-coding-agent", version: "0.69.0" }),
|
|
225
|
+
);
|
|
226
|
+
const registry = stubRegistry(cli);
|
|
227
|
+
expect(readCurrentPiVersion(registry)).toBe("0.69.0");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("dangling symlink returns undefined", () => {
|
|
231
|
+
const link = path.join(tmpDir, "dangling-pi");
|
|
232
|
+
fs.symlinkSync(path.join(tmpDir, "does-not-exist", "cli.js"), link);
|
|
233
|
+
const registry = stubRegistry(link);
|
|
234
|
+
expect(readCurrentPiVersion(registry)).toBeUndefined();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -9,6 +9,14 @@ vi.mock("../resolve-path.js", () => ({
|
|
|
9
9
|
safeRealpathSync: (p: string) => p,
|
|
10
10
|
}));
|
|
11
11
|
|
|
12
|
+
// Canonical host-platform absolute paths. Using raw POSIX strings like
|
|
13
|
+
// `/a` would normalize to `B:\a` on Windows (path.win32.resolve prepends
|
|
14
|
+
// the current drive), breaking assertions. These constants produce paths
|
|
15
|
+
// that survive `normalizePath` unchanged on their host platform.
|
|
16
|
+
const A_PATH = path.resolve(os.tmpdir(), "pref-a");
|
|
17
|
+
const B_PATH = path.resolve(os.tmpdir(), "pref-b");
|
|
18
|
+
const X_PATH = path.resolve(os.tmpdir(), "pref-x");
|
|
19
|
+
|
|
12
20
|
describe("preferences-store", () => {
|
|
13
21
|
let tmpDir: string;
|
|
14
22
|
let filePath: string;
|
|
@@ -33,12 +41,12 @@ describe("preferences-store", () => {
|
|
|
33
41
|
|
|
34
42
|
it("should load existing preferences", () => {
|
|
35
43
|
fs.writeFileSync(filePath, JSON.stringify({
|
|
36
|
-
pinnedDirectories: [
|
|
37
|
-
sessionOrder: {
|
|
44
|
+
pinnedDirectories: [A_PATH, B_PATH],
|
|
45
|
+
sessionOrder: { [A_PATH]: ["s1", "s2"] },
|
|
38
46
|
}));
|
|
39
47
|
const store = createPreferencesStore(filePath);
|
|
40
|
-
expect(store.getPinnedDirectories()).toEqual([
|
|
41
|
-
expect(store.getSessionOrder()).toEqual({
|
|
48
|
+
expect(store.getPinnedDirectories()).toEqual([A_PATH, B_PATH]);
|
|
49
|
+
expect(store.getSessionOrder()).toEqual({ [A_PATH]: ["s1", "s2"] });
|
|
42
50
|
store.dispose();
|
|
43
51
|
});
|
|
44
52
|
|
|
@@ -97,6 +105,67 @@ describe("preferences-store", () => {
|
|
|
97
105
|
store.dispose();
|
|
98
106
|
});
|
|
99
107
|
|
|
108
|
+
// ── Normalize-on-load migration (platform-path-normalization) ───────────
|
|
109
|
+
|
|
110
|
+
it("normalizes drifty pinned paths on load", () => {
|
|
111
|
+
// Seed a file with the kinds of drift that existed pre-normalization:
|
|
112
|
+
// trailing separators, `.` / `..` segments, duplicate separators. The
|
|
113
|
+
// store should collapse them to canonical form on first read.
|
|
114
|
+
fs.writeFileSync(filePath, JSON.stringify({
|
|
115
|
+
pinnedDirectories: [
|
|
116
|
+
process.platform === "win32"
|
|
117
|
+
? "C:\\Users\\me\\Dev\\" // trailing separator
|
|
118
|
+
: "/Users/me/Dev/",
|
|
119
|
+
process.platform === "win32"
|
|
120
|
+
? "C:\\Users\\me\\Dev\\.\\BB" // `.` segment
|
|
121
|
+
: "/Users/me/Dev/./BB",
|
|
122
|
+
],
|
|
123
|
+
sessionOrder: {},
|
|
124
|
+
}));
|
|
125
|
+
const store = createPreferencesStore(filePath);
|
|
126
|
+
const pinned = store.getPinnedDirectories();
|
|
127
|
+
expect(pinned).toHaveLength(2);
|
|
128
|
+
// Expect canonical forms (trailing separator stripped, `.` resolved).
|
|
129
|
+
if (process.platform === "win32") {
|
|
130
|
+
expect(pinned[0]).toBe("C:\\Users\\me\\Dev");
|
|
131
|
+
expect(pinned[1]).toBe("C:\\Users\\me\\Dev\\BB");
|
|
132
|
+
} else {
|
|
133
|
+
expect(pinned[0]).toBe("/Users/me/Dev");
|
|
134
|
+
expect(pinned[1]).toBe("/Users/me/Dev/BB");
|
|
135
|
+
}
|
|
136
|
+
store.dispose();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("deduplicates entries that collapse to the same canonical form", () => {
|
|
140
|
+
// Two different-looking entries that normalize to the same path must
|
|
141
|
+
// become one stored entry.
|
|
142
|
+
const entries = process.platform === "win32"
|
|
143
|
+
? ["C:\\Users\\me", "C:\\Users\\me\\", "C:/Users/me"]
|
|
144
|
+
: ["/Users/me", "/Users/me/", "/Users/./me"];
|
|
145
|
+
fs.writeFileSync(filePath, JSON.stringify({
|
|
146
|
+
pinnedDirectories: entries,
|
|
147
|
+
sessionOrder: {},
|
|
148
|
+
}));
|
|
149
|
+
const store = createPreferencesStore(filePath);
|
|
150
|
+
expect(store.getPinnedDirectories()).toHaveLength(1);
|
|
151
|
+
store.dispose();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("persists the normalized form back to disk on first debounce", () => {
|
|
155
|
+
fs.writeFileSync(filePath, JSON.stringify({
|
|
156
|
+
pinnedDirectories: [
|
|
157
|
+
process.platform === "win32" ? "C:\\Users\\me\\" : "/Users/me/",
|
|
158
|
+
],
|
|
159
|
+
sessionOrder: {},
|
|
160
|
+
}));
|
|
161
|
+
const store = createPreferencesStore(filePath);
|
|
162
|
+
vi.advanceTimersByTime(1000);
|
|
163
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
164
|
+
const expected = process.platform === "win32" ? "C:\\Users\\me" : "/Users/me";
|
|
165
|
+
expect(data.pinnedDirectories).toEqual([expected]);
|
|
166
|
+
store.dispose();
|
|
167
|
+
});
|
|
168
|
+
|
|
100
169
|
it("should not contain hiddenSessions in output", () => {
|
|
101
170
|
const store = createPreferencesStore(filePath);
|
|
102
171
|
store.pinDirectory("/a");
|
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { buildTmuxCommand, buildHeadlessArgs, shellEscape, spawnPiSession, buildSpawnEnv, type SessionOptions } from "../process-manager.js";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
expect(result.strategy).toBe("tmux");
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("should detect Linux", () => {
|
|
12
|
-
const result = detectPlatform("linux");
|
|
13
|
-
expect(result.strategy).toBe("tmux");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("should detect Windows with WSL fallback", () => {
|
|
17
|
-
const result = detectPlatform("win32");
|
|
18
|
-
expect(result.strategy).toBe("wsl");
|
|
19
|
-
});
|
|
20
|
-
});
|
|
4
|
+
// Note: platform-dispatch tests live in packages/shared/src/__tests__/
|
|
5
|
+
// spawn-mechanism.test.ts. `detectPlatform` was removed in change:
|
|
6
|
+
// consolidate-windows-spawn-and-platform-handlers — its job is now
|
|
7
|
+
// owned by platform/spawn-mechanism.ts `selectMechanism`.
|
|
21
8
|
|
|
9
|
+
describe("Process Manager", () => {
|
|
22
10
|
describe("buildTmuxCommand", () => {
|
|
23
11
|
it("should create new session when no pi-dashboard session exists", () => {
|
|
24
12
|
const cmd = buildTmuxCommand("/home/user/project", false);
|
|
@@ -184,4 +172,43 @@ describe("Process Manager", () => {
|
|
|
184
172
|
expect(result.message).toContain("does not exist");
|
|
185
173
|
});
|
|
186
174
|
});
|
|
175
|
+
|
|
176
|
+
// ── Fork/continue option forwarding ──────────────────────────────────────
|
|
177
|
+
// Regression guard for B1/B2: Windows WSL/cmd fallback used to drop
|
|
178
|
+
// sessionFile + mode silently. buildTmuxCommand and buildHeadlessArgs
|
|
179
|
+
// both go through `sessionFlagsToArgv`; make sure neither drops.
|
|
180
|
+
describe("session-flag forwarding", () => {
|
|
181
|
+
it("buildHeadlessArgs includes --fork for fork mode", () => {
|
|
182
|
+
const args = buildHeadlessArgs({ sessionFile: "C:\\x\\session.jsonl", mode: "fork" });
|
|
183
|
+
expect(args).toEqual(["--mode", "rpc", "--fork", "C:\\x\\session.jsonl"]);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("buildHeadlessArgs includes --session for continue mode", () => {
|
|
187
|
+
const args = buildHeadlessArgs({ sessionFile: "/s/abc.jsonl", mode: "continue" });
|
|
188
|
+
expect(args).toEqual(["--mode", "rpc", "--session", "/s/abc.jsonl"]);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("buildHeadlessArgs omits session flags when absent", () => {
|
|
192
|
+
const args = buildHeadlessArgs({});
|
|
193
|
+
expect(args).toEqual(["--mode", "rpc"]);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("buildTmuxCommand includes --fork in the pi command", () => {
|
|
197
|
+
const cmd = buildTmuxCommand("/project", false, { sessionFile: "/s/abc.jsonl", mode: "fork" });
|
|
198
|
+
expect(cmd).toContain("pi --fork /s/abc.jsonl");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("buildTmuxCommand includes --session in the pi command", () => {
|
|
202
|
+
const cmd = buildTmuxCommand("/project", false, { sessionFile: "/s/abc.jsonl", mode: "continue" });
|
|
203
|
+
expect(cmd).toContain("pi --session /s/abc.jsonl");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("buildTmuxCommand with special-character sessionFile still shell-escapes", () => {
|
|
207
|
+
const cmd = buildTmuxCommand("/project", false, {
|
|
208
|
+
sessionFile: "/s/with space.jsonl",
|
|
209
|
+
mode: "fork",
|
|
210
|
+
});
|
|
211
|
+
expect(cmd).toContain("--fork '/s/with space.jsonl'");
|
|
212
|
+
});
|
|
213
|
+
});
|
|
187
214
|
});
|