@blackbelt-technology/pi-agent-dashboard 0.4.6 → 0.5.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 +339 -190
- package/README.md +50 -7
- package/docs/architecture.md +238 -23
- package/package.json +23 -13
- package/packages/extension/package.json +11 -3
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +1 -1
- package/packages/extension/src/__tests__/build-provider-catalogue.test.ts +176 -0
- package/packages/extension/src/__tests__/command-handler.test.ts +68 -0
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +1 -1
- package/packages/extension/src/__tests__/markdown-image-inliner.test.ts +355 -0
- package/packages/extension/src/__tests__/no-tui-multiselect-arm-regression.test.ts +1 -1
- package/packages/extension/src/__tests__/openspec-activity-detector.test.ts +68 -0
- package/packages/extension/src/__tests__/prompt-expander.test.ts +45 -0
- 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.test.ts +24 -1
- 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 +1 -1
- package/packages/extension/src/bridge.ts +169 -4
- package/packages/extension/src/command-handler.ts +65 -2
- package/packages/extension/src/flow-event-wiring.ts +1 -1
- package/packages/extension/src/markdown-image-inliner.ts +268 -0
- 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 +50 -2
- package/packages/extension/src/provider-register.ts +132 -8
- package/packages/extension/src/retry-tracker.ts +123 -0
- package/packages/extension/src/server-launcher.ts +18 -1
- package/packages/extension/src/session-sync.ts +15 -1
- package/packages/extension/src/usage-limit-orderer.ts +76 -0
- package/packages/server/package.json +6 -6
- package/packages/server/src/__tests__/auto-attach-slug-defense.test.ts +104 -0
- package/packages/server/src/__tests__/bootstrap-install-from-list.test.ts +263 -0
- package/packages/server/src/__tests__/browser-gateway-snapshot-on-connect.test.ts +143 -0
- package/packages/server/src/__tests__/build-auth-status.test.ts +190 -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 +22 -4
- package/packages/server/src/__tests__/cold-boot-openspec-broadcast.test.ts +161 -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 +1 -1
- package/packages/server/src/__tests__/doctor-route.test.ts +132 -0
- package/packages/server/src/__tests__/event-wiring-providers-list.test.ts +154 -0
- 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__/has-openspec-dir.test.ts +64 -0
- package/packages/server/src/__tests__/headless-pid-registry.test.ts +83 -0
- package/packages/server/src/__tests__/health-shape.test.ts +43 -0
- package/packages/server/src/__tests__/idle-timer-respects-terminals.test.ts +115 -0
- package/packages/server/src/__tests__/is-pi-process.test.ts +1 -1
- package/packages/server/src/__tests__/openspec-connect-snapshot.test.ts +92 -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-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 +236 -0
- package/packages/server/src/__tests__/pi-core-updater.test.ts +1 -1
- 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-codes.test.ts +80 -0
- package/packages/server/src/__tests__/process-manager-managed-path.test.ts +73 -0
- package/packages/server/src/__tests__/provider-auth-routes.test.ts +12 -4
- package/packages/server/src/__tests__/provider-auth-storage.test.ts +42 -11
- package/packages/server/src/__tests__/provider-catalogue-cache.test.ts +44 -0
- package/packages/server/src/__tests__/recommended-routes.test.ts +1 -1
- package/packages/server/src/__tests__/session-action-handler-spawn-error.test.ts +17 -2
- package/packages/server/src/__tests__/session-action-handler-spawn.test.ts +150 -0
- package/packages/server/src/__tests__/session-discovery-skill-firstmessage.test.ts +95 -0
- package/packages/server/src/__tests__/spawn-correlation-token-integration.test.ts +91 -0
- package/packages/server/src/__tests__/spawn-failure-log.test.ts +118 -0
- package/packages/server/src/__tests__/spawn-preflight.test.ts +91 -0
- package/packages/server/src/__tests__/spawn-register-watchdog.test.ts +250 -0
- package/packages/server/src/__tests__/spawn-token.test.ts +57 -0
- package/packages/server/src/__tests__/subscription-handler.test.ts +98 -6
- package/packages/server/src/__tests__/system-routes-reextract.test.ts +91 -0
- package/packages/server/src/__tests__/system-routes-spawn-failures.test.ts +84 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +45 -0
- package/packages/server/src/bootstrap-install-from-list.ts +232 -0
- package/packages/server/src/bootstrap-state.ts +18 -0
- package/packages/server/src/browser-gateway.ts +70 -24
- package/packages/server/src/browser-handlers/directory-handler.ts +4 -0
- package/packages/server/src/browser-handlers/handler-context.ts +9 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +159 -18
- package/packages/server/src/browser-handlers/subscription-handler.ts +50 -3
- 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 +23 -2
- package/packages/server/src/directory-service.ts +31 -0
- package/packages/server/src/event-wiring.ts +105 -5
- package/packages/server/src/headless-pid-registry.ts +54 -5
- package/packages/server/src/home-lock.d.ts +124 -0
- package/packages/server/src/home-lock.js +330 -0
- package/packages/server/src/home-lock.js.map +1 -0
- package/packages/server/src/idle-timer.ts +15 -1
- 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 +81 -15
- package/packages/server/src/pi-dev-version-check.ts +145 -0
- package/packages/server/src/pi-gateway.ts +10 -0
- package/packages/server/src/pi-version-skew.ts +12 -4
- package/packages/server/src/process-manager.ts +115 -21
- package/packages/server/src/provider-auth-handlers.ts +9 -0
- package/packages/server/src/provider-auth-storage.ts +83 -51
- package/packages/server/src/provider-catalogue-cache.ts +47 -0
- package/packages/server/src/routes/doctor-routes.ts +140 -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 +14 -1
- package/packages/server/src/routes/provider-routes.ts +4 -4
- package/packages/server/src/routes/system-routes.ts +38 -1
- package/packages/server/src/server.ts +85 -66
- package/packages/server/src/session-api.ts +54 -3
- package/packages/server/src/session-bootstrap.ts +27 -12
- package/packages/server/src/session-discovery.ts +11 -4
- package/packages/server/src/session-file-reader.ts +1 -1
- package/packages/server/src/session-scanner.ts +4 -2
- package/packages/server/src/spawn-failure-log.ts +130 -0
- package/packages/server/src/spawn-preflight.ts +82 -0
- package/packages/server/src/spawn-register-watchdog.ts +291 -0
- package/packages/server/src/spawn-token.ts +20 -0
- package/packages/server/src/terminal-manager.ts +12 -1
- package/packages/shared/package.json +1 -1
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +25 -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 +6 -3
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +1 -1
- package/packages/shared/src/__tests__/bootstrap-install-resolve-npm.test.ts +72 -0
- package/packages/shared/src/__tests__/browser-protocol-types.test.ts +47 -1
- package/packages/shared/src/__tests__/changelog-types.test.ts +78 -0
- package/packages/shared/src/__tests__/config.test.ts +48 -0
- package/packages/shared/src/__tests__/dashboard-starter.test.ts +40 -0
- package/packages/shared/src/__tests__/detached-spawn.test.ts +24 -0
- package/packages/shared/src/__tests__/doctor-core.test.ts +134 -0
- package/packages/shared/src/__tests__/doctor-fault-tolerance.test.ts +218 -0
- package/packages/shared/src/__tests__/doctor-format.test.ts +121 -0
- package/packages/shared/src/__tests__/install-managed-node-bootstrap-order.test.ts +68 -0
- package/packages/shared/src/__tests__/install-managed-node.test.ts +192 -0
- package/packages/shared/src/__tests__/installable-list.test.ts +130 -0
- package/packages/shared/src/__tests__/managed-node-path.test.ts +122 -0
- package/packages/shared/src/__tests__/managed-runtime-strategy.test.ts +74 -0
- package/packages/shared/src/__tests__/no-installable-list-in-bridge.test.ts +52 -0
- package/packages/shared/src/__tests__/no-raw-openspec-status-in-skills.test.ts +6 -1
- package/packages/shared/src/__tests__/node-spawn-jiti-contract.test.ts +56 -20
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +140 -9
- package/packages/shared/src/__tests__/skill-block-parser.test.ts +153 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +1 -1
- package/packages/shared/src/bootstrap-install.ts +197 -3
- package/packages/shared/src/browser-protocol.ts +155 -1
- package/packages/shared/src/changelog-types.ts +111 -0
- package/packages/shared/src/config.ts +15 -0
- package/packages/shared/src/dashboard-starter.ts +33 -0
- package/packages/shared/src/doctor-core.ts +821 -0
- package/packages/shared/src/index.ts +9 -0
- package/packages/shared/src/installable-list.ts +152 -0
- package/packages/shared/src/launch-source-flag.ts +14 -0
- package/packages/shared/src/launch-source-types.ts +18 -0
- package/packages/shared/src/openspec-activity-detector.ts +25 -7
- package/packages/shared/src/platform/detached-spawn.ts +13 -2
- package/packages/shared/src/platform/managed-node-path.ts +77 -0
- package/packages/shared/src/platform/node-spawn.ts +29 -21
- package/packages/shared/src/protocol.ts +54 -2
- package/packages/shared/src/resolve-jiti.ts +62 -9
- package/packages/shared/src/rest-api.ts +4 -0
- package/packages/shared/src/skill-block-parser.ts +115 -0
- package/packages/shared/src/tool-registry/__tests__/managed-runtime-strategy.test.ts +166 -0
- package/packages/shared/src/tool-registry/definitions.ts +33 -8
- package/packages/shared/src/tool-registry/strategies.ts +42 -0
- package/packages/shared/src/types.ts +64 -0
|
@@ -7,8 +7,9 @@ source: managed
|
|
|
7
7
|
path: <HOME>/.pi-dashboard/node_modules/@mariozechner/pi-coding-agent/dist/cli.js
|
|
8
8
|
tried:
|
|
9
9
|
override no override set
|
|
10
|
+
bare-import cannot resolve @earendil-works/pi-coding-agent/package.json
|
|
10
11
|
bare-import cannot resolve @mariozechner/pi-coding-agent/package.json
|
|
11
|
-
|
|
12
|
+
managed missing: <HOME>/.pi-dashboard/node_modules/@earendil-works/pi-coding-agent/dist/cli.js
|
|
12
13
|
managed ok
|
|
13
14
|
argv:
|
|
14
15
|
- C:/Program Files/nodejs/node.exe
|
|
@@ -22,10 +23,11 @@ source: npm-global
|
|
|
22
23
|
path: <NPM_ROOT>/@mariozechner/pi-coding-agent/dist/cli.js
|
|
23
24
|
tried:
|
|
24
25
|
override no override set
|
|
26
|
+
bare-import cannot resolve @earendil-works/pi-coding-agent/package.json
|
|
25
27
|
bare-import cannot resolve @mariozechner/pi-coding-agent/package.json
|
|
26
|
-
|
|
28
|
+
managed missing: <HOME>/.pi-dashboard/node_modules/@earendil-works/pi-coding-agent/dist/cli.js
|
|
27
29
|
managed missing: <HOME>/.pi-dashboard/node_modules/@mariozechner/pi-coding-agent/dist/cli.js
|
|
28
|
-
|
|
30
|
+
npm-global missing: <NPM_ROOT>/@earendil-works/pi-coding-agent/dist/cli.js
|
|
29
31
|
npm-global ok
|
|
30
32
|
argv:
|
|
31
33
|
- C:/Program Files/nodejs/node.exe
|
|
@@ -39,6 +41,7 @@ source: system
|
|
|
39
41
|
path: C:/Program Files/nodejs/node.exe
|
|
40
42
|
tried:
|
|
41
43
|
override no override set
|
|
44
|
+
managed missing: <HOME>/.pi-dashboard/node/node.exe
|
|
42
45
|
managed missing: <HOME>/.pi-dashboard/node_modules/.bin/node.cmd
|
|
43
46
|
where ok
|
|
44
47
|
argv:
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression: `resolveNpmArgv` from `bootstrap-install.ts` MUST consult
|
|
3
|
+
* the injected `ToolRegistry` for `npm` before falling back to the
|
|
4
|
+
* platform `npm` / `npm.cmd` PATH binary.
|
|
5
|
+
*
|
|
6
|
+
* The change `embed-managed-node-runtime` adds a managedRuntime
|
|
7
|
+
* strategy to the npm chain in the registry. If a future refactor
|
|
8
|
+
* accidentally bypasses the registry inside `resolveNpmArgv`, the
|
|
9
|
+
* managed Node runtime would stop being preferred for shared bootstrap
|
|
10
|
+
* spawns \u2014 the user-visible regression class this whole change exists
|
|
11
|
+
* to prevent.
|
|
12
|
+
*
|
|
13
|
+
* See change: embed-managed-node-runtime (task 4.2).
|
|
14
|
+
*/
|
|
15
|
+
import { describe, expect, it } from "vitest";
|
|
16
|
+
import { resolveNpmArgv } from "../bootstrap-install.js";
|
|
17
|
+
import type {
|
|
18
|
+
Resolution,
|
|
19
|
+
ToolRegistry,
|
|
20
|
+
} from "../tool-registry/index.js";
|
|
21
|
+
|
|
22
|
+
function fakeRegistry(opts: {
|
|
23
|
+
hasNpm?: boolean;
|
|
24
|
+
resolveResult?: Partial<Resolution>;
|
|
25
|
+
}): ToolRegistry {
|
|
26
|
+
// Only the methods `resolveNpmArgv` actually calls.
|
|
27
|
+
return {
|
|
28
|
+
has: (name: string) => name === "npm" && (opts.hasNpm ?? true),
|
|
29
|
+
resolve: () =>
|
|
30
|
+
({
|
|
31
|
+
name: "npm",
|
|
32
|
+
ok: true,
|
|
33
|
+
path: "/managed/node/bin/npm",
|
|
34
|
+
source: "managed",
|
|
35
|
+
tried: [],
|
|
36
|
+
resolvedAt: Date.now(),
|
|
37
|
+
...opts.resolveResult,
|
|
38
|
+
}) as Resolution,
|
|
39
|
+
} as unknown as ToolRegistry;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("resolveNpmArgv", () => {
|
|
43
|
+
it("explicit npmArgv wins over registry", () => {
|
|
44
|
+
const argv = resolveNpmArgv({
|
|
45
|
+
npmArgv: ["/explicit/node", "/explicit/npm-cli.js"],
|
|
46
|
+
registry: fakeRegistry({}),
|
|
47
|
+
});
|
|
48
|
+
expect(argv).toEqual(["/explicit/node", "/explicit/npm-cli.js"]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("uses ToolRegistry.resolve('npm') when no explicit argv", () => {
|
|
52
|
+
const argv = resolveNpmArgv({
|
|
53
|
+
registry: fakeRegistry({
|
|
54
|
+
resolveResult: { ok: true, path: "/managed/node/bin/npm" } as Partial<Resolution>,
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
expect(argv).toEqual(["/managed/node/bin/npm"]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("falls back to npm/npm.cmd on PATH when registry has no entry", () => {
|
|
61
|
+
const argv = resolveNpmArgv({
|
|
62
|
+
registry: {
|
|
63
|
+
has: () => false,
|
|
64
|
+
resolve: () => {
|
|
65
|
+
throw new Error("should not be called");
|
|
66
|
+
},
|
|
67
|
+
} as unknown as ToolRegistry,
|
|
68
|
+
});
|
|
69
|
+
expect(argv).toHaveLength(1);
|
|
70
|
+
expect(argv[0]).toMatch(/^npm(\.cmd)?$/);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -12,8 +12,13 @@ import type {
|
|
|
12
12
|
BrowserPromptDismissMessage,
|
|
13
13
|
BrowserPromptCancelMessage,
|
|
14
14
|
BrowserExtUiDecoratorMessage,
|
|
15
|
+
BrowserAssetRegisterMessage,
|
|
15
16
|
} from "../browser-protocol.js";
|
|
16
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
ExtensionToServerMessage,
|
|
19
|
+
ExtUiDecoratorMessage,
|
|
20
|
+
AssetRegisterMessage,
|
|
21
|
+
} from "../protocol.js";
|
|
17
22
|
import type { DecoratorDescriptor } from "../types.js";
|
|
18
23
|
|
|
19
24
|
// Type-level assertion: if these types are NOT in the union, this will fail to compile.
|
|
@@ -26,6 +31,11 @@ type _PromptCancelInUnion = AssertExtends<BrowserPromptCancelMessage, ServerToBr
|
|
|
26
31
|
// esbuild strips the switch arms in production builds.
|
|
27
32
|
type _ExtUiDecoratorInExtensionUnion = AssertExtends<ExtUiDecoratorMessage, ExtensionToServerMessage>;
|
|
28
33
|
type _ExtUiDecoratorInBrowserUnion = AssertExtends<BrowserExtUiDecoratorMessage, ServerToBrowserMessage>;
|
|
34
|
+
// chat-markdown-local-images-and-math: asset_register must live in BOTH the
|
|
35
|
+
// extension→server union (so the server's switch arm survives esbuild) AND
|
|
36
|
+
// the server→browser union (so the client's reducer arm survives esbuild).
|
|
37
|
+
type _AssetRegisterInExtensionUnion = AssertExtends<AssetRegisterMessage, ExtensionToServerMessage>;
|
|
38
|
+
type _AssetRegisterInBrowserUnion = AssertExtends<BrowserAssetRegisterMessage, ServerToBrowserMessage>;
|
|
29
39
|
|
|
30
40
|
// Runtime verification that the type discriminants are reachable in a switch
|
|
31
41
|
function extractPromptType(msg: ServerToBrowserMessage): string | null {
|
|
@@ -119,3 +129,39 @@ describe("ext_ui_decorator is a member of both protocol unions", () => {
|
|
|
119
129
|
expect(msg.descriptor.kind).toBe("footer-segment");
|
|
120
130
|
});
|
|
121
131
|
});
|
|
132
|
+
|
|
133
|
+
// chat-markdown-local-images-and-math: asset_register switch-arm reachability.
|
|
134
|
+
function extractAssetHash(msg: ServerToBrowserMessage): string | null {
|
|
135
|
+
switch (msg.type) {
|
|
136
|
+
case "asset_register":
|
|
137
|
+
return msg.hash;
|
|
138
|
+
default:
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
describe("asset_register is a member of both protocol unions", () => {
|
|
144
|
+
it("server→browser asset_register is a valid discriminant", () => {
|
|
145
|
+
const msg: BrowserAssetRegisterMessage = {
|
|
146
|
+
type: "asset_register",
|
|
147
|
+
sessionId: "s1",
|
|
148
|
+
hash: "abc1234567890123",
|
|
149
|
+
mimeType: "image/png",
|
|
150
|
+
data: "iVBORw0KGgo=",
|
|
151
|
+
};
|
|
152
|
+
expect(extractAssetHash(msg)).toBe("abc1234567890123");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("extension→server asset_register carries the same shape", () => {
|
|
156
|
+
const msg: AssetRegisterMessage = {
|
|
157
|
+
type: "asset_register",
|
|
158
|
+
sessionId: "s1",
|
|
159
|
+
hash: "abc1234567890123",
|
|
160
|
+
mimeType: "image/svg+xml",
|
|
161
|
+
data: "PHN2Zy8+",
|
|
162
|
+
};
|
|
163
|
+
expect(msg.type).toBe("asset_register");
|
|
164
|
+
expect(msg.hash).toBe("abc1234567890123");
|
|
165
|
+
expect(msg.mimeType).toBe("image/svg+xml");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-only assertions for changelog-types. The runtime body is a
|
|
3
|
+
* single `expect(true).toBe(true)` — the real test is that the
|
|
4
|
+
* `const x: T = {...}` literal blocks compile under `tsc --noEmit`
|
|
5
|
+
* during the normal test run.
|
|
6
|
+
*
|
|
7
|
+
* See change: pi-update-whats-new-panel.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect } from "vitest";
|
|
10
|
+
import type {
|
|
11
|
+
ChangelogBullet,
|
|
12
|
+
ChangelogRelease,
|
|
13
|
+
ChangelogResponse,
|
|
14
|
+
} from "../changelog-types.js";
|
|
15
|
+
|
|
16
|
+
describe("changelog-types", () => {
|
|
17
|
+
it("ChangelogBullet accepts minimal and full shapes", () => {
|
|
18
|
+
const minimal: ChangelogBullet = { text: "fix a bug", issues: [] };
|
|
19
|
+
const full: ChangelogBullet = {
|
|
20
|
+
text: "fix a bug ([#3588](https://github.com/x/y/issues/3588))",
|
|
21
|
+
issues: [{ num: 3588, url: "https://github.com/x/y/issues/3588" }],
|
|
22
|
+
};
|
|
23
|
+
expect(minimal.text).toBe("fix a bug");
|
|
24
|
+
expect(full.issues[0].num).toBe(3588);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("ChangelogRelease accepts a fully populated entry", () => {
|
|
28
|
+
const rel: ChangelogRelease = {
|
|
29
|
+
version: "0.70.0",
|
|
30
|
+
date: "2026-04-23",
|
|
31
|
+
breaking: [{ text: "...", issues: [] }],
|
|
32
|
+
features: [{ text: "...", issues: [] }],
|
|
33
|
+
changed: [],
|
|
34
|
+
fixed: [{ text: "...", issues: [] }],
|
|
35
|
+
raw: "## [0.70.0] - 2026-04-23\n\n…",
|
|
36
|
+
};
|
|
37
|
+
expect(rel.version).toBe("0.70.0");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("ChangelogRelease tolerates null date", () => {
|
|
41
|
+
const rel: ChangelogRelease = {
|
|
42
|
+
version: "0.0.1",
|
|
43
|
+
date: null,
|
|
44
|
+
breaking: [],
|
|
45
|
+
features: [],
|
|
46
|
+
changed: [],
|
|
47
|
+
fixed: [],
|
|
48
|
+
raw: "",
|
|
49
|
+
};
|
|
50
|
+
expect(rel.date).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("ChangelogResponse wraps a release list", () => {
|
|
54
|
+
const resp: ChangelogResponse = {
|
|
55
|
+
pkg: "@mariozechner/pi-coding-agent",
|
|
56
|
+
from: "0.62.0",
|
|
57
|
+
to: "0.70.0",
|
|
58
|
+
releases: [],
|
|
59
|
+
hasBreaking: false,
|
|
60
|
+
changelogUrl: null,
|
|
61
|
+
parsedAt: "2026-05-08T12:00:00.000Z",
|
|
62
|
+
};
|
|
63
|
+
expect(resp.releases).toHaveLength(0);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("ChangelogResponse allows null changelogUrl", () => {
|
|
67
|
+
const resp: ChangelogResponse = {
|
|
68
|
+
pkg: "x",
|
|
69
|
+
from: "1.0.0",
|
|
70
|
+
to: "1.0.1",
|
|
71
|
+
releases: [],
|
|
72
|
+
hasBreaking: false,
|
|
73
|
+
changelogUrl: null,
|
|
74
|
+
parsedAt: new Date().toISOString(),
|
|
75
|
+
};
|
|
76
|
+
expect(resp.changelogUrl).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -471,3 +471,51 @@ describe("loadConfig reattachPlacement", () => {
|
|
|
471
471
|
expect(content.reattachPlacement).toBeUndefined();
|
|
472
472
|
});
|
|
473
473
|
});
|
|
474
|
+
|
|
475
|
+
describe("loadConfig spawnRegisterTimeoutMs", () => {
|
|
476
|
+
let testDir: string;
|
|
477
|
+
let configFile: string;
|
|
478
|
+
let origHome: string;
|
|
479
|
+
|
|
480
|
+
beforeEach(() => {
|
|
481
|
+
testDir = path.join(os.tmpdir(), `test-config-srt-${Date.now()}`);
|
|
482
|
+
fs.mkdirSync(path.join(testDir, ".pi", "dashboard"), { recursive: true });
|
|
483
|
+
configFile = path.join(testDir, ".pi", "dashboard", "config.json");
|
|
484
|
+
origHome = process.env.HOME!;
|
|
485
|
+
process.env.HOME = testDir;
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
afterEach(() => {
|
|
489
|
+
process.env.HOME = origHome;
|
|
490
|
+
if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true });
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("defaults to 30000 when field is omitted", () => {
|
|
494
|
+
expect(loadConfig().spawnRegisterTimeoutMs).toBe(30000);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it("preserves in-range value", () => {
|
|
498
|
+
fs.writeFileSync(configFile, JSON.stringify({ spawnRegisterTimeoutMs: 45000 }));
|
|
499
|
+
expect(loadConfig().spawnRegisterTimeoutMs).toBe(45000);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it("clamps below-range value to 5000", () => {
|
|
503
|
+
fs.writeFileSync(configFile, JSON.stringify({ spawnRegisterTimeoutMs: 1000 }));
|
|
504
|
+
expect(loadConfig().spawnRegisterTimeoutMs).toBe(5000);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it("clamps above-range value to 120000", () => {
|
|
508
|
+
fs.writeFileSync(configFile, JSON.stringify({ spawnRegisterTimeoutMs: 999999 }));
|
|
509
|
+
expect(loadConfig().spawnRegisterTimeoutMs).toBe(120000);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("falls back to default for non-number string", () => {
|
|
513
|
+
fs.writeFileSync(configFile, JSON.stringify({ spawnRegisterTimeoutMs: "thirty" }));
|
|
514
|
+
expect(loadConfig().spawnRegisterTimeoutMs).toBe(30000);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it("falls back to default for null", () => {
|
|
518
|
+
fs.writeFileSync(configFile, JSON.stringify({ spawnRegisterTimeoutMs: null }));
|
|
519
|
+
expect(loadConfig().spawnRegisterTimeoutMs).toBe(30000);
|
|
520
|
+
});
|
|
521
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { parseDashboardStarter } from "../dashboard-starter.js";
|
|
3
|
+
|
|
4
|
+
describe("parseDashboardStarter", () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.restoreAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("returns Standalone when env is empty object", () => {
|
|
10
|
+
expect(parseDashboardStarter({})).toBe("Standalone");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns Standalone when DASHBOARD_STARTER is undefined", () => {
|
|
14
|
+
expect(parseDashboardStarter({ DASHBOARD_STARTER: undefined })).toBe("Standalone");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns Standalone when DASHBOARD_STARTER is empty string", () => {
|
|
18
|
+
expect(parseDashboardStarter({ DASHBOARD_STARTER: "" })).toBe("Standalone");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns Bridge for valid value", () => {
|
|
22
|
+
expect(parseDashboardStarter({ DASHBOARD_STARTER: "Bridge" })).toBe("Bridge");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns Standalone for valid value", () => {
|
|
26
|
+
expect(parseDashboardStarter({ DASHBOARD_STARTER: "Standalone" })).toBe("Standalone");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("returns Electron for valid value", () => {
|
|
30
|
+
expect(parseDashboardStarter({ DASHBOARD_STARTER: "Electron" })).toBe("Electron");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns Standalone and warns on invalid value", () => {
|
|
34
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
35
|
+
const result = parseDashboardStarter({ DASHBOARD_STARTER: "bogus" });
|
|
36
|
+
expect(result).toBe("Standalone");
|
|
37
|
+
expect(warnSpy).toHaveBeenCalledOnce();
|
|
38
|
+
expect(warnSpy.mock.calls[0]![0]).toContain("bogus");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -61,6 +61,30 @@ describe("spawnDetached", () => {
|
|
|
61
61
|
rmSync(path.dirname(logPath), { recursive: true, force: true });
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
// See change: fix-electron-extracted-jiti-and-stdio-capture.
|
|
65
|
+
it("redirects BOTH stdout and stderr to logFd when provided", async () => {
|
|
66
|
+
const logPath = tmpLog();
|
|
67
|
+
const fd = openSync(logPath, "a");
|
|
68
|
+
try {
|
|
69
|
+
const r = await spawnDetached({
|
|
70
|
+
cmd: process.execPath,
|
|
71
|
+
args: [
|
|
72
|
+
"-e",
|
|
73
|
+
"console.log('STDOUT-LINE'); process.stderr.write('STDERR-LINE'); setTimeout(() => process.exit(0), 100)",
|
|
74
|
+
],
|
|
75
|
+
logFd: fd,
|
|
76
|
+
});
|
|
77
|
+
expect(r.ok).toBe(true);
|
|
78
|
+
await new Promise((res) => r.process!.once("exit", res));
|
|
79
|
+
} finally {
|
|
80
|
+
try { closeSync(fd); } catch { /* ignore */ }
|
|
81
|
+
}
|
|
82
|
+
const content = readFileSync(logPath, "utf-8");
|
|
83
|
+
expect(content).toContain("STDOUT-LINE");
|
|
84
|
+
expect(content).toContain("STDERR-LINE");
|
|
85
|
+
rmSync(path.dirname(logPath), { recursive: true, force: true });
|
|
86
|
+
});
|
|
87
|
+
|
|
64
88
|
it("does not keep parent event loop alive (unref)", async () => {
|
|
65
89
|
// Can only check behaviour indirectly: the returned pid/process exist
|
|
66
90
|
// and the child is running detached. Lifecycle survival is covered by
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor core — section assignment, suggestion taxonomy, and the
|
|
3
|
+
* Decision-8 lint (every non-ok check has non-empty
|
|
4
|
+
* message/detail/suggestion). See change: doctor-rich-output.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from "vitest";
|
|
7
|
+
import {
|
|
8
|
+
SECTION_OF,
|
|
9
|
+
SUGGESTIONS,
|
|
10
|
+
stampSectionsAndSuggestions,
|
|
11
|
+
type DoctorCheck,
|
|
12
|
+
type DoctorStatus,
|
|
13
|
+
} from "../doctor-core.js";
|
|
14
|
+
|
|
15
|
+
const ALL_CHECK_NAMES = Object.keys(SECTION_OF);
|
|
16
|
+
|
|
17
|
+
describe("SECTION_OF", () => {
|
|
18
|
+
it("maps every canonical check name to one of the five sections", () => {
|
|
19
|
+
const allowed = new Set(["runtime", "pi-tooling", "server", "setup", "diagnostics"]);
|
|
20
|
+
for (const name of ALL_CHECK_NAMES) {
|
|
21
|
+
expect(allowed.has(SECTION_OF[name])).toBe(true);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("covers all five sections (none empty)", () => {
|
|
26
|
+
const sections = new Set(Object.values(SECTION_OF));
|
|
27
|
+
for (const s of ["runtime", "pi-tooling", "server", "setup", "diagnostics"]) {
|
|
28
|
+
expect(sections.has(s as never)).toBe(true);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("SUGGESTIONS", () => {
|
|
34
|
+
it("returns undefined for status=ok across every check name", () => {
|
|
35
|
+
for (const name of ALL_CHECK_NAMES) {
|
|
36
|
+
const fn = SUGGESTIONS[name];
|
|
37
|
+
expect(fn).toBeDefined();
|
|
38
|
+
expect(fn?.("ok")).toBeUndefined();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns a non-empty string for status=error or warning when defined", () => {
|
|
43
|
+
for (const name of ALL_CHECK_NAMES) {
|
|
44
|
+
const fn = SUGGESTIONS[name];
|
|
45
|
+
// Electron is the only one that returns undefined even for non-ok
|
|
46
|
+
// (because today it never fails). Skip it.
|
|
47
|
+
if (name === "Electron") continue;
|
|
48
|
+
const w = fn?.("warning");
|
|
49
|
+
const e = fn?.("error");
|
|
50
|
+
expect(typeof w === "string" && w.length > 0).toBe(true);
|
|
51
|
+
expect(typeof e === "string" && e.length > 0).toBe(true);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("constrains suggestion text to the allowed Markdown subset", () => {
|
|
56
|
+
// Allowed: **bold**, single-backtick code, [text](url). Disallow: tables,
|
|
57
|
+
// headings, fenced blocks, raw HTML.
|
|
58
|
+
for (const name of ALL_CHECK_NAMES) {
|
|
59
|
+
const fn = SUGGESTIONS[name];
|
|
60
|
+
const candidates: (string | undefined)[] = [
|
|
61
|
+
fn?.("warning"),
|
|
62
|
+
fn?.("error"),
|
|
63
|
+
fn?.("error", undefined, "not-found"),
|
|
64
|
+
fn?.("error", undefined, "permission-denied"),
|
|
65
|
+
fn?.("error", undefined, "timeout"),
|
|
66
|
+
fn?.("error", undefined, "non-zero-exit"),
|
|
67
|
+
];
|
|
68
|
+
for (const s of candidates) {
|
|
69
|
+
if (!s) continue;
|
|
70
|
+
// No fenced code blocks.
|
|
71
|
+
expect(/```/.test(s)).toBe(false);
|
|
72
|
+
// No headings at line start.
|
|
73
|
+
expect(/^#{1,6}\s/m.test(s)).toBe(false);
|
|
74
|
+
// No raw HTML tags (closing, self-closing, or with attributes).
|
|
75
|
+
// Plain `<placeholder>` text is allowed (used as prose).
|
|
76
|
+
expect(/<\/[a-zA-Z]|<[a-zA-Z][^>]*\s+[^>]+>|<[a-zA-Z][^>]*\/>/.test(s)).toBe(false);
|
|
77
|
+
// Triple-asterisk or underline for bold not allowed.
|
|
78
|
+
expect(/\*\*\*|___/.test(s)).toBe(false);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("stampSectionsAndSuggestions (Decision 8 lint)", () => {
|
|
85
|
+
it("stamps section + suggestion on non-ok rows by name", () => {
|
|
86
|
+
const checks: DoctorCheck[] = [
|
|
87
|
+
{ name: "pi CLI", section: undefined as unknown as never, status: "error", message: "Not found", detail: "Searched PATH" },
|
|
88
|
+
{ name: "System Node.js", section: undefined as unknown as never, status: "ok", message: "v22 at /usr/bin/node" },
|
|
89
|
+
];
|
|
90
|
+
const out = stampSectionsAndSuggestions(checks);
|
|
91
|
+
expect(out[0].section).toBe("pi-tooling");
|
|
92
|
+
expect(out[0].suggestion).toBeDefined();
|
|
93
|
+
expect(out[1].section).toBe("runtime");
|
|
94
|
+
expect(out[1].suggestion).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("every non-ok row produced through stamping has non-empty message + detail + suggestion", () => {
|
|
98
|
+
const statuses: DoctorStatus[] = ["warning", "error"];
|
|
99
|
+
for (const name of ALL_CHECK_NAMES) {
|
|
100
|
+
// Electron suggestion is always undefined (decision-by-design); skip.
|
|
101
|
+
if (name === "Electron") continue;
|
|
102
|
+
for (const status of statuses) {
|
|
103
|
+
const checks: DoctorCheck[] = [
|
|
104
|
+
{
|
|
105
|
+
name,
|
|
106
|
+
section: undefined as unknown as never,
|
|
107
|
+
status,
|
|
108
|
+
message: "synthetic message",
|
|
109
|
+
detail: "synthetic detail",
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
const [stamped] = stampSectionsAndSuggestions(checks);
|
|
113
|
+
expect(stamped.message.length).toBeGreaterThan(0);
|
|
114
|
+
expect((stamped.detail ?? "").length).toBeGreaterThan(0);
|
|
115
|
+
expect((stamped.suggestion ?? "").length).toBeGreaterThan(0);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("does not overwrite an existing suggestion", () => {
|
|
121
|
+
const checks: DoctorCheck[] = [
|
|
122
|
+
{
|
|
123
|
+
name: "pi CLI",
|
|
124
|
+
section: "pi-tooling",
|
|
125
|
+
status: "error",
|
|
126
|
+
message: "x",
|
|
127
|
+
detail: "y",
|
|
128
|
+
suggestion: "custom",
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
const out = stampSectionsAndSuggestions(checks);
|
|
132
|
+
expect(out[0].suggestion).toBe("custom");
|
|
133
|
+
});
|
|
134
|
+
});
|