@blackbelt-technology/pi-agent-dashboard 0.5.0 → 0.5.2
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 +26 -5
- package/README.md +49 -7
- package/docs/architecture.md +129 -1
- package/package.json +15 -15
- package/packages/extension/package.json +11 -3
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +1 -1
- package/packages/extension/src/__tests__/bridge-slash-command-routing.test.ts +362 -0
- package/packages/extension/src/__tests__/command-handler.test.ts +78 -8
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +1 -1
- package/packages/extension/src/__tests__/extension-slash-command-detection.test.ts +107 -0
- package/packages/extension/src/__tests__/no-tui-multiselect-arm-regression.test.ts +1 -1
- package/packages/extension/src/__tests__/prompt-expander.test.ts +110 -1
- package/packages/extension/src/__tests__/provider-register-reload.test.ts +74 -0
- package/packages/extension/src/__tests__/retry-tracker.test.ts +147 -0
- package/packages/extension/src/__tests__/server-launcher-launch.test.ts +78 -0
- package/packages/extension/src/__tests__/session-sync.test.ts +72 -0
- package/packages/extension/src/__tests__/usage-limit-orderer.test.ts +105 -0
- package/packages/extension/src/ask-user-tool.ts +1 -1
- package/packages/extension/src/bridge-context.ts +68 -4
- package/packages/extension/src/bridge.ts +79 -11
- package/packages/extension/src/command-handler.ts +95 -15
- package/packages/extension/src/flow-event-wiring.ts +1 -1
- package/packages/extension/src/multiselect-list.ts +1 -1
- package/packages/extension/src/pi-env.d.ts +16 -9
- package/packages/extension/src/prompt-expander.ts +74 -63
- package/packages/extension/src/provider-register.ts +16 -9
- package/packages/extension/src/retry-tracker.ts +123 -0
- package/packages/extension/src/server-launcher.ts +31 -70
- package/packages/extension/src/session-sync.ts +10 -1
- package/packages/extension/src/slash-dispatch.ts +123 -0
- package/packages/extension/src/usage-limit-orderer.ts +76 -0
- package/packages/server/bin/pi-dashboard.mjs +84 -0
- package/packages/server/package.json +8 -7
- package/packages/server/scripts/fix-pty-permissions.cjs +52 -0
- package/packages/server/src/__tests__/changelog-fs.test.ts +171 -0
- package/packages/server/src/__tests__/changelog-parser.test.ts +220 -0
- package/packages/server/src/__tests__/changelog-remote.test.ts +193 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +16 -4
- package/packages/server/src/__tests__/directory-service-openspec-enabled.test.ts +187 -0
- package/packages/server/src/__tests__/directory-service-refresh-force.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service-specs-mtime.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service-toctou.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service.test.ts +2 -2
- package/packages/server/src/__tests__/dispatch-extension-command-router.test.ts +178 -0
- package/packages/server/src/__tests__/e2e/model-proxy-google-flash.test.ts +184 -0
- package/packages/server/src/__tests__/event-wiring-providers-list.test.ts +68 -1
- package/packages/server/src/__tests__/fixtures/pi-changelog-slice.md +180 -0
- package/packages/server/src/__tests__/fork-empty-session-preflight.test.ts +268 -0
- package/packages/server/src/__tests__/headless-pid-registry.test.ts +316 -0
- package/packages/server/src/__tests__/is-pi-process.test.ts +1 -1
- package/packages/server/src/__tests__/keeper-manager.test.ts +298 -0
- package/packages/server/src/__tests__/legacy-pi-cleanup.test.ts +149 -0
- package/packages/server/src/__tests__/model-proxy-api-key-routes.test.ts +277 -0
- package/packages/server/src/__tests__/model-proxy-auth-gate.test.ts +263 -0
- package/packages/server/src/__tests__/model-proxy-multi-user.test.ts +169 -0
- package/packages/server/src/__tests__/model-proxy-routes.test.ts +286 -0
- package/packages/server/src/__tests__/model-proxy-second-port.test.ts +116 -0
- package/packages/server/src/__tests__/openspec-connect-snapshot.test.ts +64 -8
- package/packages/server/src/__tests__/openspec-group-broadcast.test.ts +97 -0
- package/packages/server/src/__tests__/openspec-group-join.test.ts +80 -0
- package/packages/server/src/__tests__/openspec-group-routes.test.ts +370 -0
- package/packages/server/src/__tests__/openspec-group-store.test.ts +496 -0
- package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +4 -4
- package/packages/server/src/__tests__/package-routes.test.ts +1 -1
- package/packages/server/src/__tests__/pending-fork-registry.test.ts +48 -24
- package/packages/server/src/__tests__/pi-ai-shape.test.ts +147 -0
- package/packages/server/src/__tests__/pi-changelog-integration.test.ts +165 -0
- package/packages/server/src/__tests__/pi-changelog-routes.test.ts +409 -0
- package/packages/server/src/__tests__/pi-core-checker.test.ts +155 -13
- package/packages/server/src/__tests__/pi-core-updater-managed-path.test.ts +62 -3
- package/packages/server/src/__tests__/pi-core-updater.test.ts +1 -1
- package/packages/server/src/__tests__/pi-dashboard-bin-wrapper.test.ts +84 -0
- package/packages/server/src/__tests__/pi-dev-version-check.test.ts +184 -0
- package/packages/server/src/__tests__/pi-version-skew.test.ts +4 -4
- package/packages/server/src/__tests__/process-manager-keeper-spawn.test.ts +206 -0
- package/packages/server/src/__tests__/provider-auth-routes.test.ts +12 -4
- package/packages/server/src/__tests__/provider-catalogue-cache.test.ts +13 -23
- package/packages/server/src/__tests__/provider-routes-recursion-guard.test.ts +131 -0
- package/packages/server/src/__tests__/recommended-routes.test.ts +3 -3
- package/packages/server/src/__tests__/spawn-correlation-token-integration.test.ts +91 -0
- package/packages/server/src/__tests__/spawn-register-watchdog.test.ts +84 -0
- package/packages/server/src/__tests__/spawn-token.test.ts +57 -0
- package/packages/server/src/__tests__/tunnel-watchdog.test.ts +139 -0
- package/packages/server/src/auth-plugin.ts +3 -0
- package/packages/server/src/bootstrap-state.ts +10 -0
- package/packages/server/src/browser-gateway.ts +27 -10
- package/packages/server/src/browser-handlers/handler-context.ts +9 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +128 -19
- package/packages/server/src/changelog-fs.ts +167 -0
- package/packages/server/src/changelog-parser.ts +321 -0
- package/packages/server/src/changelog-remote.ts +134 -0
- package/packages/server/src/cli.ts +62 -82
- package/packages/server/src/config-api.ts +14 -2
- package/packages/server/src/directory-service.ts +106 -4
- package/packages/server/src/event-wiring.ts +90 -6
- package/packages/server/src/headless-pid-registry.ts +344 -37
- package/packages/server/src/legacy-pi-cleanup.ts +151 -0
- package/packages/server/src/model-proxy/__tests__/api-key-store.test.ts +142 -0
- package/packages/server/src/model-proxy/__tests__/auth-json-contention.test.ts +98 -0
- package/packages/server/src/model-proxy/__tests__/concurrency.test.ts +107 -0
- package/packages/server/src/model-proxy/__tests__/failed-auth-backoff.test.ts +46 -0
- package/packages/server/src/model-proxy/__tests__/recursion-guard.test.ts +61 -0
- package/packages/server/src/model-proxy/__tests__/streamer.test.ts +139 -0
- package/packages/server/src/model-proxy/api-key-store.ts +87 -0
- package/packages/server/src/model-proxy/auth-gate.ts +116 -0
- package/packages/server/src/model-proxy/concurrency.ts +76 -0
- package/packages/server/src/model-proxy/convert/UPSTREAM.md +13 -0
- package/packages/server/src/model-proxy/convert/__tests__/anthropic-in.test.ts +137 -0
- package/packages/server/src/model-proxy/convert/__tests__/anthropic-out.test.ts +183 -0
- package/packages/server/src/model-proxy/convert/__tests__/openai-in.test.ts +134 -0
- package/packages/server/src/model-proxy/convert/__tests__/openai-out.test.ts +166 -0
- package/packages/server/src/model-proxy/convert/anthropic-in.ts +129 -0
- package/packages/server/src/model-proxy/convert/anthropic-out.ts +173 -0
- package/packages/server/src/model-proxy/convert/index.ts +8 -0
- package/packages/server/src/model-proxy/convert/openai-in.ts +119 -0
- package/packages/server/src/model-proxy/convert/openai-out.ts +151 -0
- package/packages/server/src/model-proxy/convert/types.ts +70 -0
- package/packages/server/src/model-proxy/failed-auth-backoff.ts +45 -0
- package/packages/server/src/model-proxy/internal-auth-storage.ts +146 -0
- package/packages/server/src/model-proxy/internal-registry.ts +157 -0
- package/packages/server/src/model-proxy/recursion-guard.ts +72 -0
- package/packages/server/src/model-proxy/registry-singleton.ts +109 -0
- package/packages/server/src/model-proxy/request-log.ts +53 -0
- package/packages/server/src/model-proxy/streamer.ts +59 -0
- package/packages/server/src/openspec-group-store.ts +490 -0
- package/packages/server/src/pending-client-correlations.ts +73 -0
- package/packages/server/src/pending-fork-registry.ts +24 -12
- package/packages/server/src/pi-core-checker.ts +77 -17
- package/packages/server/src/pi-core-updater.ts +16 -6
- package/packages/server/src/pi-dev-version-check.ts +145 -0
- package/packages/server/src/pi-gateway.ts +4 -0
- package/packages/server/src/pi-version-skew.ts +12 -4
- package/packages/server/src/process-manager.ts +182 -11
- package/packages/server/src/provider-auth-storage.ts +29 -47
- package/packages/server/src/provider-catalogue-cache.ts +24 -18
- package/packages/server/src/restart-helper.ts +17 -16
- package/packages/server/src/routes/bootstrap-routes.ts +37 -0
- package/packages/server/src/routes/jj-routes.ts +3 -0
- package/packages/server/src/routes/model-proxy-api-key-routes.ts +168 -0
- package/packages/server/src/routes/model-proxy-refresh-routes.ts +24 -0
- package/packages/server/src/routes/model-proxy-routes.ts +330 -0
- package/packages/server/src/routes/openspec-group-routes.ts +231 -0
- package/packages/server/src/routes/pi-changelog-routes.ts +194 -0
- package/packages/server/src/routes/pi-core-routes.ts +1 -1
- package/packages/server/src/routes/provider-auth-routes.ts +8 -1
- package/packages/server/src/routes/provider-routes.ts +28 -5
- package/packages/server/src/routes/system-routes.ts +44 -2
- package/packages/server/src/rpc-keeper/__tests__/fixtures/mock-pi-shim.sh +9 -0
- package/packages/server/src/rpc-keeper/__tests__/fixtures/mock-pi.cjs +50 -0
- package/packages/server/src/rpc-keeper/__tests__/keeper.test.ts +371 -0
- package/packages/server/src/rpc-keeper/dispatch-router.ts +85 -0
- package/packages/server/src/rpc-keeper/keeper-manager.ts +364 -0
- package/packages/server/src/rpc-keeper/keeper.cjs +313 -0
- package/packages/server/src/server.ts +254 -60
- package/packages/server/src/session-api.ts +63 -4
- package/packages/server/src/session-discovery.ts +1 -1
- package/packages/server/src/session-file-reader.ts +1 -1
- package/packages/server/src/spawn-register-watchdog.ts +62 -7
- package/packages/server/src/spawn-token.ts +20 -0
- package/packages/server/src/tunnel-watchdog.ts +230 -0
- package/packages/server/src/tunnel.ts +5 -1
- package/packages/shared/package.json +1 -1
- package/packages/shared/src/__tests__/binary-lookup-resolveJiti.test.ts +228 -0
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +24 -17
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +5 -4
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +6 -5
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +1 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +5 -4
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +2 -1
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +5 -3
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +1 -1
- package/packages/shared/src/__tests__/changelog-types.test.ts +78 -0
- package/packages/shared/src/__tests__/config-openspec.test.ts +74 -0
- package/packages/shared/src/__tests__/model-proxy-config.test.ts +146 -0
- package/packages/shared/src/__tests__/no-raw-node-import.test.ts +7 -5
- package/packages/shared/src/__tests__/node-spawn-jiti-contract.test.ts +56 -20
- package/packages/shared/src/__tests__/node-spawn.test.ts +51 -0
- package/packages/shared/src/__tests__/openspec-groups-types.test.ts +135 -0
- package/packages/shared/src/__tests__/publish-workflow-contract.test.ts +96 -0
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +11 -3
- package/packages/shared/src/__tests__/server-launcher.test.ts +227 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +1 -1
- package/packages/shared/src/bootstrap-install.ts +1 -1
- package/packages/shared/src/browser-protocol.ts +70 -0
- package/packages/shared/src/changelog-types.ts +111 -0
- package/packages/shared/src/config.ts +172 -2
- package/packages/shared/src/dashboard-plugin/manifest-types.ts +16 -1
- package/packages/shared/src/dashboard-plugin/slot-props.ts +8 -0
- package/packages/shared/src/dashboard-plugin/slot-types.ts +57 -0
- package/packages/shared/src/platform/binary-lookup.ts +204 -0
- package/packages/shared/src/platform/node-spawn.ts +71 -26
- package/packages/shared/src/protocol.ts +27 -1
- package/packages/shared/src/recommended-extensions.ts +18 -0
- package/packages/shared/src/rest-api.ts +219 -1
- package/packages/shared/src/server-launcher.ts +277 -0
- package/packages/shared/src/skill-block-parser.ts +1 -1
- package/packages/shared/src/tool-registry/__tests__/pi-ai-registration.test.ts +124 -0
- package/packages/shared/src/tool-registry/definitions.ts +15 -3
- package/packages/shared/src/types.ts +62 -0
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +0 -53
- package/packages/shared/src/resolve-jiti.ts +0 -102
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pin
|
|
2
|
+
* Pin the jiti version contract for `shouldUrlWrapEntry()`.
|
|
3
3
|
*
|
|
4
4
|
* The Windows-non-tsx arm in `platform/node-spawn.ts::shouldUrlWrapEntry`
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* relies on jiti's file:/// URL handling. This was VERIFIED on:
|
|
6
|
+
* - `@mariozechner/pi-coding-agent@0.70.x` shipping `jiti@2.x` (original baseline)
|
|
7
|
+
* - `@earendil-works/pi-coding-agent@0.74.x` shipping `jiti@^2.7.0` (current baseline)
|
|
8
|
+
* It was BROKEN on `pi-coding-agent@0.71.x` shipping `jiti@2.6.5`, which
|
|
9
|
+
* misnormalised triple-slash file:/// URLs on Windows. Keep that data
|
|
10
|
+
* point in the contract so a contributor recognises the regression
|
|
11
|
+
* pattern if it recurs.
|
|
8
12
|
*
|
|
9
13
|
* This test ensures:
|
|
10
14
|
* 1. The offline-cacache pin in `packages/electron/offline-packages.json`
|
|
11
|
-
* stays
|
|
12
|
-
*
|
|
15
|
+
* stays on a supported pi version under one of the two supported
|
|
16
|
+
* forks. A bump outside the verified set fires this test and
|
|
17
|
+
* forces the contributor to either:
|
|
13
18
|
* - re-verify the contract on Windows
|
|
14
19
|
* - add a per-jiti-version branch
|
|
15
20
|
* - switch the bundled loader to tsx
|
|
16
21
|
* 2. The `shouldUrlWrapEntry` header comment documents the contract
|
|
17
22
|
* so future contributors discover the constraint at the call site.
|
|
18
23
|
*
|
|
19
|
-
* See
|
|
24
|
+
* See changes: fix-electron-windows-installer-and-server-bootstrap (Defect 2),
|
|
25
|
+
* migrate-pi-fork-to-earendil (E.6).
|
|
20
26
|
*/
|
|
21
27
|
import { describe, it, expect } from "vitest";
|
|
22
28
|
import fs from "node:fs";
|
|
@@ -40,34 +46,54 @@ const NODE_SPAWN_PATH = path.join(
|
|
|
40
46
|
"node-spawn.ts",
|
|
41
47
|
);
|
|
42
48
|
|
|
49
|
+
/** Versions verified against the Windows file:/// jiti contract. */
|
|
50
|
+
const VERIFIED_PI_PINS: ReadonlyArray<{ name: string; versionPrefix: string }> = [
|
|
51
|
+
{ name: "@earendil-works/pi-coding-agent", versionPrefix: "0.74." },
|
|
52
|
+
{ name: "@mariozechner/pi-coding-agent", versionPrefix: "0.70." },
|
|
53
|
+
];
|
|
54
|
+
|
|
43
55
|
describe("jiti version contract for shouldUrlWrapEntry", () => {
|
|
44
|
-
it("offline-packages.json pins
|
|
56
|
+
it("offline-packages.json pins pi-coding-agent at a verified version under a supported fork", () => {
|
|
45
57
|
const raw = fs.readFileSync(OFFLINE_PACKAGES_PATH, "utf8");
|
|
46
58
|
const manifest = JSON.parse(raw) as {
|
|
47
59
|
packages: { name: string; version: string }[];
|
|
48
60
|
};
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
|
|
62
|
+
const supportedNames = VERIFIED_PI_PINS.map((p) => p.name);
|
|
63
|
+
const piEntry = manifest.packages.find((p) =>
|
|
64
|
+
supportedNames.includes(p.name),
|
|
51
65
|
);
|
|
52
66
|
if (!piEntry) {
|
|
53
67
|
throw new Error(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
`No supported pi-coding-agent fork found in offline-packages.json. ` +
|
|
69
|
+
`Expected one of: ${supportedNames.join(", ")}. ` +
|
|
70
|
+
`The offline cacache must include pi-coding-agent. ` +
|
|
71
|
+
`See changes: fix-electron-windows-installer-and-server-bootstrap (Defect 2), ` +
|
|
72
|
+
`migrate-pi-fork-to-earendil (E.6).`,
|
|
57
73
|
);
|
|
58
74
|
}
|
|
59
|
-
|
|
75
|
+
|
|
76
|
+
const verifiedPin = VERIFIED_PI_PINS.find(
|
|
77
|
+
(p) => p.name === piEntry.name && piEntry.version.startsWith(p.versionPrefix),
|
|
78
|
+
);
|
|
79
|
+
if (!verifiedPin) {
|
|
80
|
+
const allowedRanges = VERIFIED_PI_PINS
|
|
81
|
+
.map((p) => `${p.name}@${p.versionPrefix}x`)
|
|
82
|
+
.join(", ");
|
|
60
83
|
throw new Error(
|
|
61
|
-
`pi-coding-agent pinned at ${piEntry.version}, but ` +
|
|
62
|
-
`shouldUrlWrapEntry()'s Windows-non-tsx arm only supports
|
|
84
|
+
`pi-coding-agent pinned at ${piEntry.name}@${piEntry.version}, but ` +
|
|
85
|
+
`shouldUrlWrapEntry()'s Windows-non-tsx arm only supports verified pins: ` +
|
|
86
|
+
`${allowedRanges}. ` +
|
|
63
87
|
`Newer jiti versions (e.g. 2.6.5 in pi 0.71.x) misnormalize ` +
|
|
64
88
|
`file:/// URL entries on Windows. Either re-verify the contract, ` +
|
|
65
89
|
`add a per-jiti-version branch in shouldUrlWrapEntry(), or switch ` +
|
|
66
|
-
`the bundled loader to tsx. See
|
|
67
|
-
`fix-electron-windows-installer-and-server-bootstrap (Defect 2)
|
|
90
|
+
`the bundled loader to tsx. See changes: ` +
|
|
91
|
+
`fix-electron-windows-installer-and-server-bootstrap (Defect 2), ` +
|
|
92
|
+
`migrate-pi-fork-to-earendil (E.6).`,
|
|
68
93
|
);
|
|
69
94
|
}
|
|
70
|
-
|
|
95
|
+
|
|
96
|
+
expect(piEntry.version.startsWith(verifiedPin.versionPrefix)).toBe(true);
|
|
71
97
|
});
|
|
72
98
|
|
|
73
99
|
it("node-spawn.ts source contains the documented JITI VERSION CONTRACT block", () => {
|
|
@@ -75,7 +101,17 @@ describe("jiti version contract for shouldUrlWrapEntry", () => {
|
|
|
75
101
|
|
|
76
102
|
// Contract block markers
|
|
77
103
|
expect(source).toContain("JITI VERSION CONTRACT");
|
|
78
|
-
|
|
104
|
+
// Documented baseline references (at least one of the verified version markers).
|
|
105
|
+
const hasBaselineMarker =
|
|
106
|
+
source.includes("0.74.") || source.includes("0.70.x");
|
|
107
|
+
if (!hasBaselineMarker) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"shouldUrlWrapEntry() docstring is missing the verified-baseline marker. " +
|
|
110
|
+
"It must mention at least one of: '0.74.' (current earendil baseline) " +
|
|
111
|
+
"or '0.70.x' (legacy mariozechner baseline). See change: " +
|
|
112
|
+
"migrate-pi-fork-to-earendil (E.7).",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
79
115
|
|
|
80
116
|
// Version drift markers (at least one of these identifies the broken jiti)
|
|
81
117
|
const hasVersionDriftMarker =
|
|
@@ -180,6 +180,57 @@ describe("spawnNodeScript", () => {
|
|
|
180
180
|
});
|
|
181
181
|
});
|
|
182
182
|
|
|
183
|
+
describe("buildNodeImportArgvParts", () => {
|
|
184
|
+
// Pure helper shared by spawnNodeScript and restart-helper.ts so the
|
|
185
|
+
// `--import` argv shape lives in exactly one place.
|
|
186
|
+
it("POSIX + jiti: entry passed RAW (jiti rejects file:// URL entries)", async () => {
|
|
187
|
+
const { buildNodeImportArgvParts } = await import("../platform/node-spawn.js");
|
|
188
|
+
const parts = buildNodeImportArgvParts({
|
|
189
|
+
loader: "/usr/lib/jiti/lib/jiti-register.mjs",
|
|
190
|
+
entry: "/srv/cli.ts",
|
|
191
|
+
args: ["start", "--port", "8000"],
|
|
192
|
+
platform: "linux",
|
|
193
|
+
});
|
|
194
|
+
expect(parts[0]).toBe("--import");
|
|
195
|
+
expect(parts[1]).toMatch(/^file:\/\//);
|
|
196
|
+
expect(parts[2]).toBe("/srv/cli.ts"); // RAW
|
|
197
|
+
expect(parts.slice(3)).toEqual(["start", "--port", "8000"]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("Windows + jiti: entry URL-wrapped", async () => {
|
|
201
|
+
const { buildNodeImportArgvParts } = await import("../platform/node-spawn.js");
|
|
202
|
+
const parts = buildNodeImportArgvParts({
|
|
203
|
+
loader: "B:\\Dev\\jiti\\lib\\jiti-register.mjs",
|
|
204
|
+
entry: "B:\\srv\\cli.ts",
|
|
205
|
+
args: ["start"],
|
|
206
|
+
platform: "win32",
|
|
207
|
+
});
|
|
208
|
+
expect(parts[1]).toBe("file:///B:/Dev/jiti/lib/jiti-register.mjs");
|
|
209
|
+
expect(parts[2]).toBe("file:///B:/srv/cli.ts");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("tsx loader: entry RAW on any platform", async () => {
|
|
213
|
+
const { buildNodeImportArgvParts } = await import("../platform/node-spawn.js");
|
|
214
|
+
const parts = buildNodeImportArgvParts({
|
|
215
|
+
loader: "/x/tsx/dist/esm/index.mjs",
|
|
216
|
+
entry: "C:\\srv\\cli.ts",
|
|
217
|
+
args: [],
|
|
218
|
+
platform: "win32",
|
|
219
|
+
});
|
|
220
|
+
expect(parts[2]).toBe("C:\\srv\\cli.ts"); // RAW (tsx rejects file:// entries)
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("omits args when none supplied", async () => {
|
|
224
|
+
const { buildNodeImportArgvParts } = await import("../platform/node-spawn.js");
|
|
225
|
+
const parts = buildNodeImportArgvParts({
|
|
226
|
+
loader: "/x/jiti/lib/jiti-register.mjs",
|
|
227
|
+
entry: "/srv/cli.ts",
|
|
228
|
+
platform: "linux",
|
|
229
|
+
});
|
|
230
|
+
expect(parts).toEqual(["--import", `file://${"/x/jiti/lib/jiti-register.mjs"}`, "/srv/cli.ts"]);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
183
234
|
describe("shouldUrlWrapEntry", () => {
|
|
184
235
|
it("returns false for tsx loader on any platform", () => {
|
|
185
236
|
const tsxLoader = "file:///home/u/node_modules/tsx/dist/esm/index.mjs";
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level tests for OpenSpec change-grouping shared types.
|
|
3
|
+
*
|
|
4
|
+
* Asserts:
|
|
5
|
+
* - `OpenSpecGroup` shape compiles.
|
|
6
|
+
* - `OpenSpecChange.groupId?: string | null` is optional.
|
|
7
|
+
* - `OpenSpecGroupsFile` shape compiles.
|
|
8
|
+
* - `OPENSPEC_GROUPS_SCHEMA_VERSION` is the literal `1`.
|
|
9
|
+
* - `BrowserOpenSpecGroupsUpdateMessage` is a member of `ServerToBrowserMessage`
|
|
10
|
+
* (otherwise esbuild would dead-code-eliminate the consumer switch arm).
|
|
11
|
+
* - REST request/response shapes for the five `/api/openspec/groups*` routes compile.
|
|
12
|
+
*
|
|
13
|
+
* See change: add-openspec-change-grouping (tasks 1.1–1.7).
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it, expect } from "vitest";
|
|
16
|
+
import type {
|
|
17
|
+
OpenSpecGroup,
|
|
18
|
+
OpenSpecChange,
|
|
19
|
+
OpenSpecGroupsFile,
|
|
20
|
+
} from "../types.js";
|
|
21
|
+
import { OPENSPEC_GROUPS_SCHEMA_VERSION } from "../types.js";
|
|
22
|
+
import type {
|
|
23
|
+
ServerToBrowserMessage,
|
|
24
|
+
BrowserOpenSpecGroupsUpdateMessage,
|
|
25
|
+
} from "../browser-protocol.js";
|
|
26
|
+
import type {
|
|
27
|
+
GetOpenSpecGroupsResponse,
|
|
28
|
+
CreateOpenSpecGroupRequest,
|
|
29
|
+
CreateOpenSpecGroupResponse,
|
|
30
|
+
UpdateOpenSpecGroupRequest,
|
|
31
|
+
UpdateOpenSpecGroupResponse,
|
|
32
|
+
DeleteOpenSpecGroupResponse,
|
|
33
|
+
SetOpenSpecGroupAssignmentRequest,
|
|
34
|
+
SetOpenSpecGroupAssignmentResponse,
|
|
35
|
+
} from "../rest-api.js";
|
|
36
|
+
|
|
37
|
+
// Type-level assertion: if the type does NOT extend the union, this will fail to compile.
|
|
38
|
+
type AssertExtends<T, U> = T extends U ? true : never;
|
|
39
|
+
|
|
40
|
+
// 1.5 — broadcast variant lives in the union.
|
|
41
|
+
type _GroupsUpdateInBrowserUnion = AssertExtends<
|
|
42
|
+
BrowserOpenSpecGroupsUpdateMessage,
|
|
43
|
+
ServerToBrowserMessage
|
|
44
|
+
>;
|
|
45
|
+
|
|
46
|
+
// 1.1 — group shape.
|
|
47
|
+
type _GroupShape = AssertExtends<
|
|
48
|
+
{ id: string; name: string; color?: string; order: number },
|
|
49
|
+
OpenSpecGroup
|
|
50
|
+
>;
|
|
51
|
+
|
|
52
|
+
// 1.2 — `groupId?` is optional on `OpenSpecChange`.
|
|
53
|
+
type _ChangeGroupIdOptional = AssertExtends<
|
|
54
|
+
{ name: string; status: "in-progress"; completedTasks: 0; totalTasks: 0; artifacts: [] },
|
|
55
|
+
OpenSpecChange
|
|
56
|
+
>;
|
|
57
|
+
// And it accepts a string when present.
|
|
58
|
+
type _ChangeWithGroupId = AssertExtends<
|
|
59
|
+
{
|
|
60
|
+
name: string;
|
|
61
|
+
status: "in-progress";
|
|
62
|
+
completedTasks: 0;
|
|
63
|
+
totalTasks: 0;
|
|
64
|
+
artifacts: [];
|
|
65
|
+
groupId: "ui";
|
|
66
|
+
},
|
|
67
|
+
OpenSpecChange
|
|
68
|
+
>;
|
|
69
|
+
|
|
70
|
+
// 1.4 — file shape.
|
|
71
|
+
type _FileShape = AssertExtends<
|
|
72
|
+
{
|
|
73
|
+
schemaVersion: 1;
|
|
74
|
+
groups: OpenSpecGroup[];
|
|
75
|
+
assignments: Record<string, string>;
|
|
76
|
+
},
|
|
77
|
+
OpenSpecGroupsFile
|
|
78
|
+
>;
|
|
79
|
+
|
|
80
|
+
// 1.6 — REST shapes (compile-time only).
|
|
81
|
+
const _getResp: GetOpenSpecGroupsResponse = {
|
|
82
|
+
success: true,
|
|
83
|
+
data: { schemaVersion: 1, groups: [], assignments: {} },
|
|
84
|
+
};
|
|
85
|
+
const _createReq: CreateOpenSpecGroupRequest = { name: "UI", color: "#3b82f6" };
|
|
86
|
+
const _createResp: CreateOpenSpecGroupResponse = {
|
|
87
|
+
success: true,
|
|
88
|
+
data: { id: "ui", name: "UI", color: "#3b82f6", order: 0 },
|
|
89
|
+
};
|
|
90
|
+
const _updateReq: UpdateOpenSpecGroupRequest = { name: "Frontend" };
|
|
91
|
+
const _updateResp: UpdateOpenSpecGroupResponse = {
|
|
92
|
+
success: true,
|
|
93
|
+
data: { id: "ui", name: "Frontend", order: 0 },
|
|
94
|
+
};
|
|
95
|
+
const _deleteResp: DeleteOpenSpecGroupResponse = { success: true };
|
|
96
|
+
const _putReq: SetOpenSpecGroupAssignmentRequest = { changeName: "add-foo", groupId: "ui" };
|
|
97
|
+
const _putReqNull: SetOpenSpecGroupAssignmentRequest = { changeName: "add-foo", groupId: null };
|
|
98
|
+
const _putResp: SetOpenSpecGroupAssignmentResponse = { success: true };
|
|
99
|
+
|
|
100
|
+
// Suppress unused-locals for compile-time-only declarations.
|
|
101
|
+
void _getResp;
|
|
102
|
+
void _createReq;
|
|
103
|
+
void _createResp;
|
|
104
|
+
void _updateReq;
|
|
105
|
+
void _updateResp;
|
|
106
|
+
void _deleteResp;
|
|
107
|
+
void _putReq;
|
|
108
|
+
void _putReqNull;
|
|
109
|
+
void _putResp;
|
|
110
|
+
|
|
111
|
+
describe("OpenSpec change-grouping shared types", () => {
|
|
112
|
+
it("OPENSPEC_GROUPS_SCHEMA_VERSION is the literal 1", () => {
|
|
113
|
+
expect(OPENSPEC_GROUPS_SCHEMA_VERSION).toBe(1);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("openspec_groups_update is reachable in a runtime switch over ServerToBrowserMessage", () => {
|
|
117
|
+
// Runtime check that the discriminant survives type-narrowing — mirrors
|
|
118
|
+
// the prompt-message regression guard in browser-protocol-types.test.ts.
|
|
119
|
+
const sample: ServerToBrowserMessage = {
|
|
120
|
+
type: "openspec_groups_update",
|
|
121
|
+
cwd: "/tmp/foo",
|
|
122
|
+
groups: [],
|
|
123
|
+
assignments: {},
|
|
124
|
+
};
|
|
125
|
+
let hit = false;
|
|
126
|
+
switch (sample.type) {
|
|
127
|
+
case "openspec_groups_update":
|
|
128
|
+
hit = true;
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
expect(hit).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -128,6 +128,102 @@ describe("publish.yml — electron job dependency-graph contract", () => {
|
|
|
128
128
|
// single source of truth is the `prepare` job's computed `is_prerelease`
|
|
129
129
|
// output. See change: eliminate-bash-on-windows-runners (D6).
|
|
130
130
|
|
|
131
|
+
// ── Lockfile-regen contract ──────────────────────────────────────────────
|
|
132
|
+
// The `prepare` job MUST regenerate package-lock.json with the bumped
|
|
133
|
+
// versions (between sync-versions.js and the git commit) so consumers'
|
|
134
|
+
// `npm ci` doesn't fall back to stale registry tarballs via strict
|
|
135
|
+
// prerelease semver. See change: fix-release-lockfile-drift.
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Parse the `steps:` block of a single job into an array of `{ run }`
|
|
139
|
+
* entries. We only care about the `run:` field for this contract; the
|
|
140
|
+
* step delimiter is any ` - ` line (6-space indent + dash + space).
|
|
141
|
+
* Multi-line `run: |` blocks fold into a single `run` string.
|
|
142
|
+
*/
|
|
143
|
+
function parseJobSteps(jobBlock: string): Array<{ run: string }> {
|
|
144
|
+
const lines = jobBlock.split("\n");
|
|
145
|
+
const steps: Array<{ run: string }> = [];
|
|
146
|
+
let i = 0;
|
|
147
|
+
// Find the ` steps:` line.
|
|
148
|
+
while (i < lines.length && !/^ steps:\s*$/.test(lines[i])) i++;
|
|
149
|
+
i++;
|
|
150
|
+
let current: { run: string } | null = null;
|
|
151
|
+
let inRunBlock = false;
|
|
152
|
+
let runBlockIndent = 0;
|
|
153
|
+
while (i < lines.length) {
|
|
154
|
+
const line = lines[i];
|
|
155
|
+
// New step delimiter: ` - ` at 6-space indent.
|
|
156
|
+
if (/^ - /.test(line)) {
|
|
157
|
+
if (current) steps.push(current);
|
|
158
|
+
current = { run: "" };
|
|
159
|
+
inRunBlock = false;
|
|
160
|
+
// Inline `- run: foo` form.
|
|
161
|
+
const inlineRun = line.match(/^ -\s+run:\s+(.*)$/);
|
|
162
|
+
if (inlineRun) current.run = inlineRun[1];
|
|
163
|
+
i++;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (current) {
|
|
167
|
+
// Block scalar ` run: |`.
|
|
168
|
+
const blockStart = line.match(/^ run:\s*\|?\s*$/);
|
|
169
|
+
const inlineKey = line.match(/^ run:\s+(.+)$/);
|
|
170
|
+
if (blockStart) {
|
|
171
|
+
inRunBlock = true;
|
|
172
|
+
runBlockIndent = 10; // body lines start at ≥ 10-space indent
|
|
173
|
+
i++;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (inlineKey) {
|
|
177
|
+
current.run += (current.run ? "\n" : "") + inlineKey[1];
|
|
178
|
+
i++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (inRunBlock) {
|
|
182
|
+
// Body line of a `run: |` block. Stop when we hit a less-indented
|
|
183
|
+
// line (next key at 8-space indent, or the next step at 6-space).
|
|
184
|
+
if (line.length === 0) {
|
|
185
|
+
current.run += "\n";
|
|
186
|
+
i++;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const indent = line.length - line.trimStart().length;
|
|
190
|
+
if (indent < runBlockIndent) {
|
|
191
|
+
inRunBlock = false;
|
|
192
|
+
continue; // re-process this line as a key
|
|
193
|
+
}
|
|
194
|
+
current.run += (current.run ? "\n" : "") + line.slice(runBlockIndent);
|
|
195
|
+
i++;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
i++;
|
|
200
|
+
}
|
|
201
|
+
if (current) steps.push(current);
|
|
202
|
+
return steps;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
describe("publish.yml — prepare job lockfile-regen contract", () => {
|
|
206
|
+
const yaml = fs.readFileSync(WORKFLOW_PATH, "utf8");
|
|
207
|
+
const prepareBlock = extractJobBlock(yaml, "prepare");
|
|
208
|
+
const prepareSteps = parseJobSteps(prepareBlock);
|
|
209
|
+
|
|
210
|
+
it("prepare job regenerates lockfile after version bump (fix-release-lockfile-drift)", () => {
|
|
211
|
+
const syncIdx = prepareSteps.findIndex((s) => /sync-versions\.js/.test(s.run || ""));
|
|
212
|
+
const regenIdx = prepareSteps.findIndex((s) =>
|
|
213
|
+
/npm install --package-lock-only/.test(s.run || ""),
|
|
214
|
+
);
|
|
215
|
+
const commitIdx = prepareSteps.findIndex((s) =>
|
|
216
|
+
/git commit -m "chore\(release\)/.test(s.run || ""),
|
|
217
|
+
);
|
|
218
|
+
expect(syncIdx, "sync-versions.js step missing").toBeGreaterThanOrEqual(0);
|
|
219
|
+
expect(
|
|
220
|
+
regenIdx,
|
|
221
|
+
"lockfile regen step missing — see change fix-release-lockfile-drift",
|
|
222
|
+
).toBeGreaterThan(syncIdx);
|
|
223
|
+
expect(commitIdx, "git commit step missing").toBeGreaterThan(regenIdx);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
131
227
|
describe("publish.yml — prerelease safety contract", () => {
|
|
132
228
|
const yaml = fs.readFileSync(WORKFLOW_PATH, "utf8");
|
|
133
229
|
const prepareBlock = extractJobBlock(yaml, "prepare");
|
|
@@ -8,13 +8,14 @@ import {
|
|
|
8
8
|
} from "../recommended-extensions.js";
|
|
9
9
|
|
|
10
10
|
describe("RECOMMENDED_EXTENSIONS manifest", () => {
|
|
11
|
-
it("contains exactly the
|
|
11
|
+
it("contains exactly the six expected entries", () => {
|
|
12
12
|
const ids = RECOMMENDED_EXTENSIONS.map((e) => e.id).sort();
|
|
13
13
|
expect(ids).toEqual(
|
|
14
14
|
[
|
|
15
15
|
"pi-anthropic-messages",
|
|
16
16
|
"pi-agent-browser",
|
|
17
17
|
"pi-flows",
|
|
18
|
+
"pi-memory-honcho",
|
|
18
19
|
"pi-web-access",
|
|
19
20
|
"tintinweb-pi-subagents",
|
|
20
21
|
].sort(),
|
|
@@ -62,7 +63,12 @@ describe("RECOMMENDED_EXTENSIONS manifest", () => {
|
|
|
62
63
|
it("npm-sourced entries use the npm: prefix", () => {
|
|
63
64
|
const npmEntries = RECOMMENDED_EXTENSIONS.filter((e) => e.source.startsWith("npm:"));
|
|
64
65
|
expect(npmEntries.map((e) => e.id).sort()).toEqual(
|
|
65
|
-
[
|
|
66
|
+
[
|
|
67
|
+
"pi-agent-browser",
|
|
68
|
+
"pi-memory-honcho",
|
|
69
|
+
"pi-web-access",
|
|
70
|
+
"tintinweb-pi-subagents",
|
|
71
|
+
].sort(),
|
|
66
72
|
);
|
|
67
73
|
});
|
|
68
74
|
|
|
@@ -105,7 +111,9 @@ describe("getRecommendedByStatus", () => {
|
|
|
105
111
|
|
|
106
112
|
it("filters by optional", () => {
|
|
107
113
|
const optional = getRecommendedByStatus("optional");
|
|
108
|
-
expect(optional.map((e) => e.id)).toEqual(
|
|
114
|
+
expect(optional.map((e) => e.id).sort()).toEqual(
|
|
115
|
+
["pi-agent-browser", "pi-memory-honcho"].sort(),
|
|
116
|
+
);
|
|
109
117
|
});
|
|
110
118
|
});
|
|
111
119
|
|