@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
|
@@ -17,17 +17,74 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { createServer, type ServerConfig } from "./server.js";
|
|
19
19
|
import { loadConfig, ensureConfig } from "@blackbelt-technology/pi-dashboard-shared/config.js";
|
|
20
|
-
import { spawn } from "
|
|
20
|
+
import { spawn } from "@blackbelt-technology/pi-dashboard-shared/platform/exec.js";
|
|
21
|
+
import { spawnNodeScript } from "@blackbelt-technology/pi-dashboard-shared/platform/node-spawn.js";
|
|
21
22
|
import { createRequire } from "node:module";
|
|
22
|
-
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
23
24
|
import fs from "node:fs";
|
|
25
|
+
import os from "node:os";
|
|
24
26
|
import path from "node:path";
|
|
25
|
-
import { readPid,
|
|
27
|
+
import { readPid, removePid, isServerRunning } from "./server-pid.js";
|
|
28
|
+
import {
|
|
29
|
+
findPortHolders as platformFindPortHolders,
|
|
30
|
+
isProcessAlive as platformIsProcessAlive,
|
|
31
|
+
killProcess as platformKillProcess,
|
|
32
|
+
parseNetstatListeners as platformParseNetstatListeners,
|
|
33
|
+
} from "@blackbelt-technology/pi-dashboard-shared/platform/process.js";
|
|
34
|
+
|
|
35
|
+
// Re-exports for back-compat — other modules / tests may import these from cli.
|
|
36
|
+
export const parseNetstatListeners = platformParseNetstatListeners;
|
|
37
|
+
export function findPortHolders(
|
|
38
|
+
port: number,
|
|
39
|
+
execImpl?: (cmd: string, opts: { encoding: "utf-8" }) => string,
|
|
40
|
+
): number[] {
|
|
41
|
+
return platformFindPortHolders(port, execImpl ? { exec: execImpl } : undefined);
|
|
42
|
+
}
|
|
26
43
|
import { isDashboardRunning } from "@blackbelt-technology/pi-dashboard-shared/server-identity.js";
|
|
27
44
|
import { discoverDashboard } from "@blackbelt-technology/pi-dashboard-shared/mdns-discovery.js";
|
|
28
45
|
import { resolveJitiImport } from "@blackbelt-technology/pi-dashboard-shared/resolve-jiti.js";
|
|
46
|
+
import { assertNodeVersionSupported } from "./node-guard.js";
|
|
47
|
+
import { getDefaultRegistry } from "@blackbelt-technology/pi-dashboard-shared/tool-registry/index.js";
|
|
48
|
+
import { bootstrapInstall } from "@blackbelt-technology/pi-dashboard-shared/bootstrap-install.js";
|
|
49
|
+
import {
|
|
50
|
+
findBundledExtension,
|
|
51
|
+
registerBridgeExtension,
|
|
52
|
+
} from "@blackbelt-technology/pi-dashboard-shared/bridge-register.js";
|
|
53
|
+
import type { DashboardServer } from "./server.js";
|
|
54
|
+
import { updateBootstrapCompatibility } from "./pi-version-skew.js";
|
|
55
|
+
import type { BootstrapStateStore } from "./bootstrap-state.js";
|
|
29
56
|
|
|
30
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Emit a stderr warning at CLI startup when the resolved pi version is
|
|
59
|
+
* below `piCompatibility.minimum` (blocking) or below `.recommended`
|
|
60
|
+
* (advisory). Reads from the already-populated `bootstrapState` so no
|
|
61
|
+
* additional I/O happens here. See change: warn-pi-version-skew-in-cli.
|
|
62
|
+
*/
|
|
63
|
+
function logCompatibilityWarning(store: BootstrapStateStore): void {
|
|
64
|
+
const s = store.get();
|
|
65
|
+
const c = s.compatibility;
|
|
66
|
+
if (!c || !c.current) return;
|
|
67
|
+
// Below minimum: `updateBootstrapCompatibility` sets `error.message`.
|
|
68
|
+
// We treat the presence of a blocking error + upgradeRecommended as the
|
|
69
|
+
// below-minimum signal; `upgradeRecommended` alone means below-recommended.
|
|
70
|
+
if (s.error?.message && c.upgradeRecommended) {
|
|
71
|
+
console.error(
|
|
72
|
+
`[bootstrap] ⚠ pi ${c.current} is below the required minimum ${c.minimum}.`,
|
|
73
|
+
);
|
|
74
|
+
console.error(
|
|
75
|
+
`[bootstrap] All pi-dependent features (sessions, resources, openspec) will return 503.`,
|
|
76
|
+
);
|
|
77
|
+
console.error(`[bootstrap] Run: pi-dashboard upgrade-pi`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (c.upgradeRecommended) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`[bootstrap] pi ${c.current} is below the recommended ${c.recommended} — consider running \`pi-dashboard upgrade-pi\``,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const SUBCOMMANDS = ["start", "stop", "restart", "status", "upgrade-pi"] as const;
|
|
31
88
|
type Subcommand = (typeof SUBCOMMANDS)[number];
|
|
32
89
|
|
|
33
90
|
export interface ParsedArgs {
|
|
@@ -87,6 +144,7 @@ export function buildConfig(flags: Partial<ServerConfig>): ServerConfig {
|
|
|
87
144
|
maxStringFieldSize: fileConfig.memoryLimits.maxStringFieldSize,
|
|
88
145
|
maxWsBufferBytes: fileConfig.memoryLimits.maxWsBufferBytes,
|
|
89
146
|
editor: fileConfig.editor,
|
|
147
|
+
openspec: fileConfig.openspec,
|
|
90
148
|
resolvedTrustedNetworks: fileConfig.resolvedTrustedNetworks,
|
|
91
149
|
corsAllowedOrigins: fileConfig.cors.allowedOrigins,
|
|
92
150
|
};
|
|
@@ -94,8 +152,17 @@ export function buildConfig(flags: Partial<ServerConfig>): ServerConfig {
|
|
|
94
152
|
|
|
95
153
|
/**
|
|
96
154
|
* Run the server in the foreground (original behavior).
|
|
155
|
+
*
|
|
156
|
+
* After the server starts listening, the degraded-mode bootstrap kicks
|
|
157
|
+
* off: if `pi` is not resolvable via the ToolRegistry, the server flips
|
|
158
|
+
* `bootstrapState` to "installing" and begins a background
|
|
159
|
+
* `bootstrapInstall`. Session-spawn and other pi-dependent endpoints
|
|
160
|
+
* queue or 503 during this window (see change tasks §5).
|
|
161
|
+
*
|
|
162
|
+
* See change: unified-bootstrap-install.
|
|
97
163
|
*/
|
|
98
164
|
async function runForeground(config: ServerConfig): Promise<void> {
|
|
165
|
+
assertNodeVersionSupported();
|
|
99
166
|
const server = await createServer(config);
|
|
100
167
|
|
|
101
168
|
let shuttingDown = false;
|
|
@@ -114,12 +181,141 @@ async function runForeground(config: ServerConfig): Promise<void> {
|
|
|
114
181
|
process.on("SIGTERM", shutdown);
|
|
115
182
|
|
|
116
183
|
await server.start();
|
|
184
|
+
|
|
185
|
+
// Kick off the degraded-mode first-run bootstrap if pi is unresolvable.
|
|
186
|
+
// Runs async — server is already listening, so UI + non-pi endpoints
|
|
187
|
+
// remain fully operational during the ~30s install window.
|
|
188
|
+
// TODO(single-dashboard-per-home): when home-lock wiring lands, wrap
|
|
189
|
+
// this inside the acquired lock to serialize concurrent first-run
|
|
190
|
+
// installs from multiple dashboard invocations on the same HOME.
|
|
191
|
+
runDegradedModeBootstrap(server).catch((err) => {
|
|
192
|
+
console.error("[bootstrap] unexpected failure in bootstrap orchestrator:", err);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Orchestrate the first-run bootstrap flow.
|
|
198
|
+
*
|
|
199
|
+
* - If pi is already resolvable → leave `bootstrapState` at the default
|
|
200
|
+
* "ready" and return immediately.
|
|
201
|
+
* - Otherwise flip to "installing", run `bootstrapInstall`, then:
|
|
202
|
+
* • on success, rescan the registry, attempt bridge registration
|
|
203
|
+
* (failures are non-fatal and land in `bridgeRegistrationError`),
|
|
204
|
+
* flip to "ready".
|
|
205
|
+
* • on failure, flip to "failed" with the error.
|
|
206
|
+
*
|
|
207
|
+
* Structured log lines at each transition aid diagnosis in daemon-mode
|
|
208
|
+
* (stdout goes to ~/.pi/dashboard/server.log).
|
|
209
|
+
*/
|
|
210
|
+
async function runDegradedModeBootstrap(server: DashboardServer): Promise<void> {
|
|
211
|
+
const registry = getDefaultRegistry();
|
|
212
|
+
const initial = registry.resolve("pi");
|
|
213
|
+
|
|
214
|
+
if (initial.ok) {
|
|
215
|
+
// Default state is "ready" — no change needed. Log once for clarity.
|
|
216
|
+
console.log(`[bootstrap] ready (pi resolved via ${initial.source})`);
|
|
217
|
+
// Populate version-skew compatibility info even when no install was
|
|
218
|
+
// needed — the UI banner renders upgradeRecommended hints.
|
|
219
|
+
try {
|
|
220
|
+
const serverPkg = path.resolve(
|
|
221
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
222
|
+
"..",
|
|
223
|
+
"package.json",
|
|
224
|
+
);
|
|
225
|
+
updateBootstrapCompatibility(server.bootstrapState, serverPkg);
|
|
226
|
+
logCompatibilityWarning(server.bootstrapState);
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.warn("[bootstrap] version-skew check failed (non-fatal):", err);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const installPackages = ["@mariozechner/pi-coding-agent", "@fission-ai/openspec", "tsx"];
|
|
234
|
+
server.bootstrapState.setLastInstallPackages(installPackages);
|
|
235
|
+
console.log("[bootstrap] installing (pi unresolved, running background install)");
|
|
236
|
+
server.bootstrapState.set({
|
|
237
|
+
status: "installing",
|
|
238
|
+
progress: { step: "pi", output: "starting install…" },
|
|
239
|
+
error: undefined,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const res = await bootstrapInstall({
|
|
244
|
+
packages: installPackages,
|
|
245
|
+
progress: (p) => {
|
|
246
|
+
server.bootstrapState.set({
|
|
247
|
+
progress: { step: p.step, output: p.output },
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
if (!res.ok) {
|
|
253
|
+
console.error(`[bootstrap] failed: ${res.error}`);
|
|
254
|
+
server.bootstrapState.set({
|
|
255
|
+
status: "failed",
|
|
256
|
+
error: { message: res.error },
|
|
257
|
+
progress: undefined,
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Rescan registry so pi is re-resolved after the fresh install.
|
|
263
|
+
// If `rescan` is not exposed, the resolver's strategy chain re-runs
|
|
264
|
+
// on the next `resolve()` call anyway; we just want fresh timing.
|
|
265
|
+
type Rescannable = { rescan?: (name: string) => void };
|
|
266
|
+
const maybeRescan = (registry as unknown as Rescannable).rescan;
|
|
267
|
+
if (typeof maybeRescan === "function") maybeRescan.call(registry, "pi");
|
|
268
|
+
|
|
269
|
+
// Attempt bridge registration. Failures are non-fatal per spec §10.3.
|
|
270
|
+
let bridgeErr: string | undefined;
|
|
271
|
+
try {
|
|
272
|
+
const extPath = findBundledExtension(process.cwd());
|
|
273
|
+
if (extPath) {
|
|
274
|
+
registerBridgeExtension(extPath);
|
|
275
|
+
} else {
|
|
276
|
+
bridgeErr = "bundled extension not found after install";
|
|
277
|
+
}
|
|
278
|
+
} catch (err) {
|
|
279
|
+
bridgeErr = err instanceof Error ? err.message : String(err);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
server.bootstrapState.set({
|
|
283
|
+
status: "ready",
|
|
284
|
+
progress: undefined,
|
|
285
|
+
error: undefined,
|
|
286
|
+
bridgeRegistrationError: bridgeErr,
|
|
287
|
+
});
|
|
288
|
+
// Populate compatibility info after a successful install.
|
|
289
|
+
try {
|
|
290
|
+
const serverPkg = path.resolve(
|
|
291
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
292
|
+
"..",
|
|
293
|
+
"package.json",
|
|
294
|
+
);
|
|
295
|
+
updateBootstrapCompatibility(server.bootstrapState, serverPkg);
|
|
296
|
+
logCompatibilityWarning(server.bootstrapState);
|
|
297
|
+
} catch (err) {
|
|
298
|
+
console.warn("[bootstrap] version-skew check failed (non-fatal):", err);
|
|
299
|
+
}
|
|
300
|
+
console.log(
|
|
301
|
+
`[bootstrap] ready (installed ${res.installed.join(", ")}${bridgeErr ? `; bridge warning: ${bridgeErr}` : ""})`,
|
|
302
|
+
);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
305
|
+
console.error(`[bootstrap] failed: ${message}`);
|
|
306
|
+
server.bootstrapState.set({
|
|
307
|
+
status: "failed",
|
|
308
|
+
error: { message },
|
|
309
|
+
progress: undefined,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
117
312
|
}
|
|
118
313
|
|
|
119
314
|
/**
|
|
120
315
|
* Start the server as a detached background daemon.
|
|
121
316
|
*/
|
|
122
317
|
async function cmdStart(config: ServerConfig): Promise<void> {
|
|
318
|
+
assertNodeVersionSupported();
|
|
123
319
|
const running = await isServerRunning(config.port);
|
|
124
320
|
if (running) {
|
|
125
321
|
console.log(`Dashboard server is already running (pid ${running})`);
|
|
@@ -146,10 +342,14 @@ async function cmdStart(config: ServerConfig): Promise<void> {
|
|
|
146
342
|
try {
|
|
147
343
|
tsLoader = resolveJitiImport();
|
|
148
344
|
} catch {
|
|
149
|
-
// Fallback to tsx when jiti is not available (e.g. running outside pi)
|
|
345
|
+
// Fallback to tsx when jiti is not available (e.g. running outside pi).
|
|
346
|
+
// The loader is passed to `node --import`; on Windows, Node >= 20 rejects
|
|
347
|
+
// raw absolute paths with a drive letter (parsed as URL scheme), so we
|
|
348
|
+
// return a file:// URL. See change: fix-windows-server-parity.
|
|
150
349
|
try {
|
|
151
350
|
const tsxMain = createRequire(cliPath).resolve("tsx");
|
|
152
|
-
|
|
351
|
+
const tsxLoaderPath = path.join(path.dirname(tsxMain), "esm", "index.mjs");
|
|
352
|
+
tsLoader = pathToFileURL(tsxLoaderPath).href;
|
|
153
353
|
} catch {
|
|
154
354
|
console.error(
|
|
155
355
|
"[pi-dashboard] Cannot find TypeScript loader. " +
|
|
@@ -159,22 +359,45 @@ async function cmdStart(config: ServerConfig): Promise<void> {
|
|
|
159
359
|
}
|
|
160
360
|
}
|
|
161
361
|
|
|
162
|
-
// Redirect daemon stdout/stderr to a log file for crash diagnosis
|
|
163
|
-
|
|
362
|
+
// Redirect daemon stdout/stderr to a log file for crash diagnosis.
|
|
363
|
+
// Log is opened in append mode ("a") so output from prior start attempts
|
|
364
|
+
// is preserved across retries — critical for diagnosing intermittent or
|
|
365
|
+
// silent launch failures. A timestamped header line distinguishes runs.
|
|
366
|
+
// See change: fix-windows-server-parity.
|
|
367
|
+
const logDir = path.join(os.homedir(), ".pi", "dashboard");
|
|
164
368
|
fs.mkdirSync(logDir, { recursive: true });
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
369
|
+
const logPath = path.join(logDir, "server.log");
|
|
370
|
+
const logFd = fs.openSync(logPath, "a");
|
|
371
|
+
fs.writeSync(
|
|
372
|
+
logFd,
|
|
373
|
+
`\n[${new Date().toISOString()}] pi-dashboard start (parent pid ${process.pid}, port ${config.port})\n`,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
// Both tsLoader and cliPath are wrapped as file:// URLs by spawnNodeScript.
|
|
377
|
+
// Required on Windows for node --import (see change: fix-windows-entry-script-url).
|
|
378
|
+
const child = spawnNodeScript({
|
|
379
|
+
loader: tsLoader,
|
|
380
|
+
entry: cliPath,
|
|
381
|
+
args,
|
|
382
|
+
spawnOptions: {
|
|
383
|
+
detached: true,
|
|
384
|
+
stdio: ["ignore", logFd, logFd],
|
|
385
|
+
env: { ...process.env },
|
|
386
|
+
},
|
|
171
387
|
});
|
|
172
388
|
child.unref();
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
389
|
+
// Close the parent's copy of the fd — child has its own via stdio inheritance.
|
|
390
|
+
try { fs.closeSync(logFd); } catch { /* ignore */ }
|
|
391
|
+
|
|
392
|
+
// Wait for dashboard to become available. Windows + jiti cold-start can
|
|
393
|
+
// take 10s+ (TS compile on first boot, native module loads). 30s is the
|
|
394
|
+
// outer bound — if the server isn't up by then, something's genuinely wrong.
|
|
395
|
+
const READINESS_TIMEOUT_MS = 30_000;
|
|
396
|
+
const deadline = Date.now() + READINESS_TIMEOUT_MS;
|
|
176
397
|
let started = false;
|
|
177
398
|
while (Date.now() < deadline) {
|
|
399
|
+
// Also bail if the child has already exited (fast-path crash detection).
|
|
400
|
+
if (child.exitCode !== null) break;
|
|
178
401
|
await new Promise((r) => setTimeout(r, 300));
|
|
179
402
|
const status = await isDashboardRunning(config.port);
|
|
180
403
|
if (status.running) {
|
|
@@ -187,7 +410,10 @@ async function cmdStart(config: ServerConfig): Promise<void> {
|
|
|
187
410
|
const pid = readPid();
|
|
188
411
|
console.log(`Dashboard server started (pid ${pid ?? child.pid}) at http://localhost:${config.port}`);
|
|
189
412
|
} else {
|
|
190
|
-
|
|
413
|
+
const reason = child.exitCode !== null
|
|
414
|
+
? `child process exited with code ${child.exitCode}`
|
|
415
|
+
: `timed out after ${READINESS_TIMEOUT_MS / 1000}s`;
|
|
416
|
+
console.error(`Failed to start dashboard server (${reason})`);
|
|
191
417
|
console.error(`Check logs at ${path.join(logDir, "server.log")}`);
|
|
192
418
|
process.exit(1);
|
|
193
419
|
}
|
|
@@ -196,31 +422,21 @@ async function cmdStart(config: ServerConfig): Promise<void> {
|
|
|
196
422
|
/**
|
|
197
423
|
* Stop the running server daemon.
|
|
198
424
|
*/
|
|
199
|
-
/**
|
|
425
|
+
/**
|
|
426
|
+
* Kill a process by PID with logging. Delegates to the shared platform
|
|
427
|
+
* primitive (`packages/shared/src/platform/process.ts`) which handles the
|
|
428
|
+
* Windows (taskkill) vs Unix (SIGTERM→SIGKILL) split.
|
|
429
|
+
* See change: consolidate-platform-handlers.
|
|
430
|
+
*/
|
|
200
431
|
async function killProcess(pid: number, label: string): Promise<boolean> {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
while (Date.now() < deadline) {
|
|
205
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
206
|
-
if (!isProcessAlive(pid)) {
|
|
207
|
-
console.log(`${label} stopped (pid ${pid})`);
|
|
208
|
-
return true;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
try { process.kill(pid, "SIGKILL"); } catch { /* already dead */ }
|
|
212
|
-
console.log(`${label} stopped (forced, pid ${pid})`);
|
|
432
|
+
const result = await platformKillProcess(pid);
|
|
433
|
+
if (!result.ok) return false;
|
|
434
|
+
console.log(`${label} stopped${result.forced ? " (forced)" : ""} (pid ${pid})`);
|
|
213
435
|
return true;
|
|
214
436
|
}
|
|
215
437
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
const { execSync } = require("node:child_process");
|
|
220
|
-
const output = execSync(`lsof -t -i :${port} -sTCP:LISTEN 2>/dev/null`, { encoding: "utf-8" });
|
|
221
|
-
return output.trim().split("\n").map(Number).filter((n: number) => n > 0 && n !== process.pid);
|
|
222
|
-
} catch { return []; }
|
|
223
|
-
}
|
|
438
|
+
// Local alias to preserve prior internal references.
|
|
439
|
+
const isProcessAlive = (pid: number) => platformIsProcessAlive(pid);
|
|
224
440
|
|
|
225
441
|
async function cmdStop(): Promise<void> {
|
|
226
442
|
const config = loadConfig();
|
|
@@ -255,6 +471,58 @@ async function cmdStop(): Promise<void> {
|
|
|
255
471
|
/**
|
|
256
472
|
* Show server status.
|
|
257
473
|
*/
|
|
474
|
+
/**
|
|
475
|
+
* `pi-dashboard upgrade-pi` — upgrade pi-coding-agent via bootstrap.
|
|
476
|
+
*
|
|
477
|
+
* If a dashboard is currently running, POST to /api/bootstrap/upgrade-pi
|
|
478
|
+
* (so the running server owns the install, broadcasts state, and reloads
|
|
479
|
+
* connected sessions). Otherwise run `bootstrapInstall` directly with a
|
|
480
|
+
* streaming progress formatter and exit when done.
|
|
481
|
+
*
|
|
482
|
+
* See change: unified-bootstrap-install §8.
|
|
483
|
+
*/
|
|
484
|
+
async function cmdUpgradePi(config: ServerConfig): Promise<void> {
|
|
485
|
+
const status = await isDashboardRunning(config.port);
|
|
486
|
+
if (status.running) {
|
|
487
|
+
console.log(
|
|
488
|
+
`[upgrade-pi] dashboard running at http://localhost:${config.port}, delegating to server`,
|
|
489
|
+
);
|
|
490
|
+
try {
|
|
491
|
+
const res = await fetch(`http://localhost:${config.port}/api/bootstrap/upgrade-pi`, {
|
|
492
|
+
method: "POST",
|
|
493
|
+
});
|
|
494
|
+
if (!res.ok) {
|
|
495
|
+
const body = await res.text();
|
|
496
|
+
console.error(`[upgrade-pi] server rejected upgrade: HTTP ${res.status} ${body}`);
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
const body = (await res.json()) as { ticketId?: string };
|
|
500
|
+
console.log(`[upgrade-pi] queued (ticketId=${body.ticketId ?? "?"})`);
|
|
501
|
+
console.log("[upgrade-pi] progress is streamed to open dashboard tabs; CLI exits now.");
|
|
502
|
+
return;
|
|
503
|
+
} catch (err) {
|
|
504
|
+
console.error("[upgrade-pi] failed to reach server:", err);
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
console.log("[upgrade-pi] no dashboard running — installing directly");
|
|
510
|
+
const res = await bootstrapInstall({
|
|
511
|
+
packages: ["@mariozechner/pi-coding-agent"],
|
|
512
|
+
progress: (p) => {
|
|
513
|
+
const line = p.output
|
|
514
|
+
? `[upgrade-pi] ${p.step} ${p.status}: ${p.output}`
|
|
515
|
+
: `[upgrade-pi] ${p.step} ${p.status}`;
|
|
516
|
+
console.log(line);
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
if (!res.ok) {
|
|
520
|
+
console.error(`[upgrade-pi] failed: ${res.error}`);
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
console.log(`[upgrade-pi] ✓ installed ${res.installed.join(", ")}`);
|
|
524
|
+
}
|
|
525
|
+
|
|
258
526
|
async function cmdStatus(port: number): Promise<void> {
|
|
259
527
|
// 1. Try mDNS discovery first
|
|
260
528
|
try {
|
|
@@ -328,6 +596,9 @@ async function main() {
|
|
|
328
596
|
case "status":
|
|
329
597
|
await cmdStatus(config.port);
|
|
330
598
|
break;
|
|
599
|
+
case "upgrade-pi":
|
|
600
|
+
await cmdUpgradePi(config);
|
|
601
|
+
break;
|
|
331
602
|
default:
|
|
332
603
|
// No subcommand — run in foreground (backward compatible)
|
|
333
604
|
await runForeground(config);
|
|
@@ -100,6 +100,17 @@ export function writeConfigPartial(partial: Record<string, any>): WriteConfigRes
|
|
|
100
100
|
mergedAuth.allowedUsers = partial.auth.allowedUsers;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
// fix-trusted-networks-no-oauth: propagate bypassHosts / bypassUrls
|
|
104
|
+
// from the incoming partial. Without these, the UI's Trusted Networks
|
|
105
|
+
// save path silently dropped every entry on disk. `!== undefined`
|
|
106
|
+
// (not truthiness) lets an empty array clear all entries.
|
|
107
|
+
if (partial.auth.bypassHosts !== undefined) {
|
|
108
|
+
mergedAuth.bypassHosts = partial.auth.bypassHosts;
|
|
109
|
+
}
|
|
110
|
+
if (partial.auth.bypassUrls !== undefined) {
|
|
111
|
+
mergedAuth.bypassUrls = partial.auth.bypassUrls;
|
|
112
|
+
}
|
|
113
|
+
|
|
103
114
|
partial.auth = mergedAuth;
|
|
104
115
|
}
|
|
105
116
|
|
|
@@ -114,6 +125,11 @@ export function writeConfigPartial(partial: Record<string, any>): WriteConfigRes
|
|
|
114
125
|
restartRequired = true;
|
|
115
126
|
}
|
|
116
127
|
|
|
128
|
+
// Merge openspec sub-object (no restart required — live-reconfigured)
|
|
129
|
+
if (partial.openspec) {
|
|
130
|
+
partial.openspec = { ...existing.openspec, ...partial.openspec };
|
|
131
|
+
}
|
|
132
|
+
|
|
117
133
|
const merged = { ...existing, ...partial };
|
|
118
134
|
|
|
119
135
|
// Remove computed fields that shouldn't be persisted
|