@blackbelt-technology/pi-agent-dashboard 0.2.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +64 -8
- package/README.md +308 -101
- package/docs/architecture.md +515 -16
- package/package.json +14 -7
- package/packages/extension/package.json +11 -3
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +300 -3
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +201 -0
- package/packages/extension/src/__tests__/fork-entryid-timing.test.ts +100 -0
- package/packages/extension/src/__tests__/git-info.test.ts +67 -55
- package/packages/extension/src/__tests__/openspec-poller.test.ts +101 -96
- package/packages/extension/src/__tests__/process-scanner-kill.test.ts +61 -0
- package/packages/extension/src/__tests__/provider-register-reload.test.ts +394 -0
- package/packages/extension/src/__tests__/server-auto-start.test.ts +95 -4
- package/packages/extension/src/__tests__/server-launcher.test.ts +16 -0
- package/packages/extension/src/ask-user-tool.ts +289 -20
- package/packages/extension/src/bridge.ts +107 -6
- package/packages/extension/src/command-handler.ts +34 -39
- package/packages/extension/src/dev-build.ts +1 -1
- package/packages/extension/src/git-info.ts +9 -19
- package/packages/extension/src/pi-env.d.ts +1 -0
- package/packages/extension/src/process-scanner.ts +72 -38
- package/packages/extension/src/prompt-expander.ts +25 -4
- package/packages/extension/src/provider-register.ts +304 -16
- package/packages/extension/src/server-auto-start.ts +27 -1
- package/packages/extension/src/server-launcher.ts +71 -27
- package/packages/server/package.json +17 -2
- package/packages/server/src/__tests__/auto-attach.test.ts +10 -1
- package/packages/server/src/__tests__/auto-shutdown.test.ts +8 -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 +246 -10
- package/packages/server/src/__tests__/browser-gateway-handler-errors.test.ts +129 -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__/cors.test.ts +34 -2
- 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-manager-pid-registry.test.ts +168 -0
- package/packages/server/src/__tests__/editor-manager.test.ts +33 -0
- package/packages/server/src/__tests__/editor-pid-registry.test.ts +191 -0
- package/packages/server/src/__tests__/editor-registry.test.ts +29 -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__/fix-pty-permissions.test.ts +59 -0
- package/packages/server/src/__tests__/force-kill-handler.test.ts +57 -8
- package/packages/server/src/__tests__/git-operations.test.ts +9 -7
- package/packages/server/src/__tests__/health-endpoint.test.ts +11 -13
- 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__/openspec-tasks-parser.test.ts +178 -0
- package/packages/server/src/__tests__/openspec-tasks-routes.test.ts +180 -0
- package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +126 -0
- package/packages/server/src/__tests__/package-manager-wrapper.test.ts +45 -10
- package/packages/server/src/__tests__/pi-core-checker.test.ts +195 -0
- package/packages/server/src/__tests__/pi-core-routes.test.ts +184 -0
- package/packages/server/src/__tests__/pi-core-updater.test.ts +214 -0
- package/packages/server/src/__tests__/pi-version-skew.test.ts +165 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +73 -4
- package/packages/server/src/__tests__/process-manager.test.ts +45 -18
- package/packages/server/src/__tests__/provider-auth-routes.test.ts +13 -3
- 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__/recommended-routes.test.ts +389 -0
- package/packages/server/src/__tests__/restart-helper.test.ts +83 -0
- package/packages/server/src/__tests__/session-action-handler-headless-reload.test.ts +467 -0
- package/packages/server/src/__tests__/session-action-handler-reload-predicate.test.ts +73 -0
- package/packages/server/src/__tests__/session-action-handler-spawn-error.test.ts +74 -0
- package/packages/server/src/__tests__/session-file-dedup.test.ts +10 -10
- package/packages/server/src/__tests__/session-lifecycle-logging.test.ts +8 -2
- package/packages/server/src/__tests__/sleep-aware-heartbeat.test.ts +3 -1
- package/packages/server/src/__tests__/smoke-integration.test.ts +10 -10
- package/packages/server/src/__tests__/terminal-manager.test.ts +41 -1
- package/packages/server/src/__tests__/test-server-canary.test.ts +31 -0
- 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 +103 -6
- package/packages/server/src/__tests__/ws-ping-pong.test.ts +10 -2
- 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 +108 -9
- package/packages/server/src/browser-gateway.ts +16 -3
- package/packages/server/src/browser-handlers/directory-handler.ts +23 -8
- package/packages/server/src/browser-handlers/session-action-handler.ts +213 -79
- package/packages/server/src/browser-handlers/session-action-helpers.ts +36 -0
- package/packages/server/src/cli.ts +256 -32
- package/packages/server/src/config-api.ts +16 -0
- package/packages/server/src/directory-service.ts +270 -39
- package/packages/server/src/editor-detection.ts +12 -9
- package/packages/server/src/editor-manager.ts +39 -5
- package/packages/server/src/editor-pid-registry.ts +199 -0
- package/packages/server/src/editor-registry.ts +22 -25
- package/packages/server/src/fix-pty-permissions.ts +44 -0
- package/packages/server/src/git-operations.ts +1 -1
- package/packages/server/src/headless-pid-registry.ts +16 -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/npm-search-proxy.ts +71 -0
- package/packages/server/src/openspec-tasks.ts +158 -0
- package/packages/server/src/package-manager-wrapper.ts +225 -34
- package/packages/server/src/pi-core-checker.ts +290 -0
- package/packages/server/src/pi-core-updater.ts +172 -0
- package/packages/server/src/pi-gateway.ts +7 -0
- package/packages/server/src/pi-resource-scanner.ts +5 -8
- package/packages/server/src/pi-version-skew.ts +196 -0
- package/packages/server/src/preferences-store.ts +17 -3
- package/packages/server/src/process-manager.ts +403 -222
- package/packages/server/src/provider-probe.ts +234 -0
- package/packages/server/src/restart-helper.ts +130 -0
- package/packages/server/src/routes/bootstrap-routes.ts +88 -0
- package/packages/server/src/routes/file-routes.ts +30 -3
- package/packages/server/src/routes/openspec-routes.ts +107 -1
- package/packages/server/src/routes/pi-core-routes.ts +140 -0
- package/packages/server/src/routes/provider-auth-routes.ts +12 -10
- package/packages/server/src/routes/provider-routes.ts +55 -2
- package/packages/server/src/routes/recommended-routes.ts +225 -0
- package/packages/server/src/routes/system-routes.ts +30 -34
- 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 +363 -26
- 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 +65 -20
- package/packages/server/src/test-env-guard.ts +26 -0
- package/packages/server/src/test-support/test-server.ts +63 -0
- package/packages/server/src/tunnel.ts +172 -34
- 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 +59 -3
- 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__/openspec-poller.test.ts +44 -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 +156 -0
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +43 -7
- package/packages/shared/src/__tests__/semaphore.test.ts +119 -0
- package/packages/shared/src/__tests__/source-matching.test.ts +143 -0
- package/packages/shared/src/__tests__/spawn-mechanism.test.ts +131 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +239 -0
- package/packages/shared/src/__tests__/tool-registry-overrides.test.ts +137 -0
- package/packages/shared/src/__tests__/tool-registry-registry.test.ts +343 -0
- package/packages/shared/src/bootstrap-install.ts +212 -0
- package/packages/shared/src/bridge-register.ts +87 -20
- package/packages/shared/src/browser-protocol.ts +93 -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 +71 -49
- package/packages/shared/src/{tool-resolver.ts → platform/binary-lookup.ts} +125 -25
- package/packages/shared/src/platform/commands.ts +100 -0
- package/packages/shared/src/platform/detached-spawn.ts +305 -0
- package/packages/shared/src/platform/exec.ts +220 -0
- package/packages/shared/src/platform/git.ts +155 -0
- package/packages/shared/src/platform/index.ts +15 -0
- package/packages/shared/src/platform/npm.ts +162 -0
- package/packages/shared/src/platform/openspec.ts +91 -0
- package/packages/shared/src/platform/paths.ts +276 -0
- package/packages/shared/src/platform/process-identify.ts +126 -0
- package/packages/shared/src/platform/process-scan.ts +94 -0
- package/packages/shared/src/platform/process.ts +168 -0
- package/packages/shared/src/platform/runner.ts +369 -0
- package/packages/shared/src/platform/shell.ts +44 -0
- package/packages/shared/src/platform/spawn-mechanism.ts +124 -0
- package/packages/shared/src/platform/subprocess-adapter.ts +124 -0
- package/packages/shared/src/recommended-extensions.ts +196 -0
- package/packages/shared/src/resolve-jiti.ts +62 -3
- package/packages/shared/src/rest-api.ts +97 -0
- package/packages/shared/src/semaphore.ts +83 -0
- package/packages/shared/src/source-matching.ts +126 -0
- package/packages/shared/src/test-support/setup-home.ts +74 -0
- package/packages/shared/src/tool-registry/definitions.ts +342 -0
- package/packages/shared/src/tool-registry/index.ts +56 -0
- package/packages/shared/src/tool-registry/overrides.ts +118 -0
- package/packages/shared/src/tool-registry/registry.ts +262 -0
- package/packages/shared/src/tool-registry/strategies.ts +198 -0
- package/packages/shared/src/tool-registry/types.ts +180 -0
- package/packages/shared/src/types.ts +7 -0
|
@@ -17,17 +17,42 @@
|
|
|
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
21
|
import { createRequire } from "node:module";
|
|
22
|
-
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
23
23
|
import fs from "node:fs";
|
|
24
|
+
import os from "node:os";
|
|
24
25
|
import path from "node:path";
|
|
25
|
-
import { readPid,
|
|
26
|
+
import { readPid, removePid, isServerRunning } from "./server-pid.js";
|
|
27
|
+
import {
|
|
28
|
+
findPortHolders as platformFindPortHolders,
|
|
29
|
+
isProcessAlive as platformIsProcessAlive,
|
|
30
|
+
killProcess as platformKillProcess,
|
|
31
|
+
parseNetstatListeners as platformParseNetstatListeners,
|
|
32
|
+
} from "@blackbelt-technology/pi-dashboard-shared/platform/process.js";
|
|
33
|
+
|
|
34
|
+
// Re-exports for back-compat — other modules / tests may import these from cli.
|
|
35
|
+
export const parseNetstatListeners = platformParseNetstatListeners;
|
|
36
|
+
export function findPortHolders(
|
|
37
|
+
port: number,
|
|
38
|
+
execImpl?: (cmd: string, opts: { encoding: "utf-8" }) => string,
|
|
39
|
+
): number[] {
|
|
40
|
+
return platformFindPortHolders(port, execImpl ? { exec: execImpl } : undefined);
|
|
41
|
+
}
|
|
26
42
|
import { isDashboardRunning } from "@blackbelt-technology/pi-dashboard-shared/server-identity.js";
|
|
27
43
|
import { discoverDashboard } from "@blackbelt-technology/pi-dashboard-shared/mdns-discovery.js";
|
|
28
44
|
import { resolveJitiImport } from "@blackbelt-technology/pi-dashboard-shared/resolve-jiti.js";
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
import { assertNodeVersionSupported } from "./node-guard.js";
|
|
46
|
+
import { getDefaultRegistry } from "@blackbelt-technology/pi-dashboard-shared/tool-registry/index.js";
|
|
47
|
+
import { bootstrapInstall } from "@blackbelt-technology/pi-dashboard-shared/bootstrap-install.js";
|
|
48
|
+
import {
|
|
49
|
+
findBundledExtension,
|
|
50
|
+
registerBridgeExtension,
|
|
51
|
+
} from "@blackbelt-technology/pi-dashboard-shared/bridge-register.js";
|
|
52
|
+
import type { DashboardServer } from "./server.js";
|
|
53
|
+
import { updateBootstrapCompatibility } from "./pi-version-skew.js";
|
|
54
|
+
|
|
55
|
+
const SUBCOMMANDS = ["start", "stop", "restart", "status", "upgrade-pi"] as const;
|
|
31
56
|
type Subcommand = (typeof SUBCOMMANDS)[number];
|
|
32
57
|
|
|
33
58
|
export interface ParsedArgs {
|
|
@@ -87,6 +112,7 @@ export function buildConfig(flags: Partial<ServerConfig>): ServerConfig {
|
|
|
87
112
|
maxStringFieldSize: fileConfig.memoryLimits.maxStringFieldSize,
|
|
88
113
|
maxWsBufferBytes: fileConfig.memoryLimits.maxWsBufferBytes,
|
|
89
114
|
editor: fileConfig.editor,
|
|
115
|
+
openspec: fileConfig.openspec,
|
|
90
116
|
resolvedTrustedNetworks: fileConfig.resolvedTrustedNetworks,
|
|
91
117
|
corsAllowedOrigins: fileConfig.cors.allowedOrigins,
|
|
92
118
|
};
|
|
@@ -94,8 +120,17 @@ export function buildConfig(flags: Partial<ServerConfig>): ServerConfig {
|
|
|
94
120
|
|
|
95
121
|
/**
|
|
96
122
|
* Run the server in the foreground (original behavior).
|
|
123
|
+
*
|
|
124
|
+
* After the server starts listening, the degraded-mode bootstrap kicks
|
|
125
|
+
* off: if `pi` is not resolvable via the ToolRegistry, the server flips
|
|
126
|
+
* `bootstrapState` to "installing" and begins a background
|
|
127
|
+
* `bootstrapInstall`. Session-spawn and other pi-dependent endpoints
|
|
128
|
+
* queue or 503 during this window (see change tasks §5).
|
|
129
|
+
*
|
|
130
|
+
* See change: unified-bootstrap-install.
|
|
97
131
|
*/
|
|
98
132
|
async function runForeground(config: ServerConfig): Promise<void> {
|
|
133
|
+
assertNodeVersionSupported();
|
|
99
134
|
const server = await createServer(config);
|
|
100
135
|
|
|
101
136
|
let shuttingDown = false;
|
|
@@ -114,12 +149,139 @@ async function runForeground(config: ServerConfig): Promise<void> {
|
|
|
114
149
|
process.on("SIGTERM", shutdown);
|
|
115
150
|
|
|
116
151
|
await server.start();
|
|
152
|
+
|
|
153
|
+
// Kick off the degraded-mode first-run bootstrap if pi is unresolvable.
|
|
154
|
+
// Runs async — server is already listening, so UI + non-pi endpoints
|
|
155
|
+
// remain fully operational during the ~30s install window.
|
|
156
|
+
// TODO(single-dashboard-per-home): when home-lock wiring lands, wrap
|
|
157
|
+
// this inside the acquired lock to serialize concurrent first-run
|
|
158
|
+
// installs from multiple dashboard invocations on the same HOME.
|
|
159
|
+
runDegradedModeBootstrap(server).catch((err) => {
|
|
160
|
+
console.error("[bootstrap] unexpected failure in bootstrap orchestrator:", err);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Orchestrate the first-run bootstrap flow.
|
|
166
|
+
*
|
|
167
|
+
* - If pi is already resolvable → leave `bootstrapState` at the default
|
|
168
|
+
* "ready" and return immediately.
|
|
169
|
+
* - Otherwise flip to "installing", run `bootstrapInstall`, then:
|
|
170
|
+
* • on success, rescan the registry, attempt bridge registration
|
|
171
|
+
* (failures are non-fatal and land in `bridgeRegistrationError`),
|
|
172
|
+
* flip to "ready".
|
|
173
|
+
* • on failure, flip to "failed" with the error.
|
|
174
|
+
*
|
|
175
|
+
* Structured log lines at each transition aid diagnosis in daemon-mode
|
|
176
|
+
* (stdout goes to ~/.pi/dashboard/server.log).
|
|
177
|
+
*/
|
|
178
|
+
async function runDegradedModeBootstrap(server: DashboardServer): Promise<void> {
|
|
179
|
+
const registry = getDefaultRegistry();
|
|
180
|
+
const initial = registry.resolve("pi");
|
|
181
|
+
|
|
182
|
+
if (initial.ok) {
|
|
183
|
+
// Default state is "ready" — no change needed. Log once for clarity.
|
|
184
|
+
console.log(`[bootstrap] ready (pi resolved via ${initial.source})`);
|
|
185
|
+
// Populate version-skew compatibility info even when no install was
|
|
186
|
+
// needed — the UI banner renders upgradeRecommended hints.
|
|
187
|
+
try {
|
|
188
|
+
const serverPkg = path.resolve(
|
|
189
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
190
|
+
"..",
|
|
191
|
+
"package.json",
|
|
192
|
+
);
|
|
193
|
+
updateBootstrapCompatibility(server.bootstrapState, serverPkg);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
console.warn("[bootstrap] version-skew check failed (non-fatal):", err);
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const installPackages = ["@mariozechner/pi-coding-agent", "@fission-ai/openspec", "tsx"];
|
|
201
|
+
server.bootstrapState.setLastInstallPackages(installPackages);
|
|
202
|
+
console.log("[bootstrap] installing (pi unresolved, running background install)");
|
|
203
|
+
server.bootstrapState.set({
|
|
204
|
+
status: "installing",
|
|
205
|
+
progress: { step: "pi", output: "starting install…" },
|
|
206
|
+
error: undefined,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const res = await bootstrapInstall({
|
|
211
|
+
packages: installPackages,
|
|
212
|
+
progress: (p) => {
|
|
213
|
+
server.bootstrapState.set({
|
|
214
|
+
progress: { step: p.step, output: p.output },
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (!res.ok) {
|
|
220
|
+
console.error(`[bootstrap] failed: ${res.error}`);
|
|
221
|
+
server.bootstrapState.set({
|
|
222
|
+
status: "failed",
|
|
223
|
+
error: { message: res.error },
|
|
224
|
+
progress: undefined,
|
|
225
|
+
});
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Rescan registry so pi is re-resolved after the fresh install.
|
|
230
|
+
// If `rescan` is not exposed, the resolver's strategy chain re-runs
|
|
231
|
+
// on the next `resolve()` call anyway; we just want fresh timing.
|
|
232
|
+
type Rescannable = { rescan?: (name: string) => void };
|
|
233
|
+
const maybeRescan = (registry as unknown as Rescannable).rescan;
|
|
234
|
+
if (typeof maybeRescan === "function") maybeRescan.call(registry, "pi");
|
|
235
|
+
|
|
236
|
+
// Attempt bridge registration. Failures are non-fatal per spec §10.3.
|
|
237
|
+
let bridgeErr: string | undefined;
|
|
238
|
+
try {
|
|
239
|
+
const extPath = findBundledExtension(process.cwd());
|
|
240
|
+
if (extPath) {
|
|
241
|
+
registerBridgeExtension(extPath);
|
|
242
|
+
} else {
|
|
243
|
+
bridgeErr = "bundled extension not found after install";
|
|
244
|
+
}
|
|
245
|
+
} catch (err) {
|
|
246
|
+
bridgeErr = err instanceof Error ? err.message : String(err);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
server.bootstrapState.set({
|
|
250
|
+
status: "ready",
|
|
251
|
+
progress: undefined,
|
|
252
|
+
error: undefined,
|
|
253
|
+
bridgeRegistrationError: bridgeErr,
|
|
254
|
+
});
|
|
255
|
+
// Populate compatibility info after a successful install.
|
|
256
|
+
try {
|
|
257
|
+
const serverPkg = path.resolve(
|
|
258
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
259
|
+
"..",
|
|
260
|
+
"package.json",
|
|
261
|
+
);
|
|
262
|
+
updateBootstrapCompatibility(server.bootstrapState, serverPkg);
|
|
263
|
+
} catch (err) {
|
|
264
|
+
console.warn("[bootstrap] version-skew check failed (non-fatal):", err);
|
|
265
|
+
}
|
|
266
|
+
console.log(
|
|
267
|
+
`[bootstrap] ready (installed ${res.installed.join(", ")}${bridgeErr ? `; bridge warning: ${bridgeErr}` : ""})`,
|
|
268
|
+
);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
271
|
+
console.error(`[bootstrap] failed: ${message}`);
|
|
272
|
+
server.bootstrapState.set({
|
|
273
|
+
status: "failed",
|
|
274
|
+
error: { message },
|
|
275
|
+
progress: undefined,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
117
278
|
}
|
|
118
279
|
|
|
119
280
|
/**
|
|
120
281
|
* Start the server as a detached background daemon.
|
|
121
282
|
*/
|
|
122
283
|
async function cmdStart(config: ServerConfig): Promise<void> {
|
|
284
|
+
assertNodeVersionSupported();
|
|
123
285
|
const running = await isServerRunning(config.port);
|
|
124
286
|
if (running) {
|
|
125
287
|
console.log(`Dashboard server is already running (pid ${running})`);
|
|
@@ -146,10 +308,14 @@ async function cmdStart(config: ServerConfig): Promise<void> {
|
|
|
146
308
|
try {
|
|
147
309
|
tsLoader = resolveJitiImport();
|
|
148
310
|
} catch {
|
|
149
|
-
// Fallback to tsx when jiti is not available (e.g. running outside pi)
|
|
311
|
+
// Fallback to tsx when jiti is not available (e.g. running outside pi).
|
|
312
|
+
// The loader is passed to `node --import`; on Windows, Node >= 20 rejects
|
|
313
|
+
// raw absolute paths with a drive letter (parsed as URL scheme), so we
|
|
314
|
+
// return a file:// URL. See change: fix-windows-server-parity.
|
|
150
315
|
try {
|
|
151
316
|
const tsxMain = createRequire(cliPath).resolve("tsx");
|
|
152
|
-
|
|
317
|
+
const tsxLoaderPath = path.join(path.dirname(tsxMain), "esm", "index.mjs");
|
|
318
|
+
tsLoader = pathToFileURL(tsxLoaderPath).href;
|
|
153
319
|
} catch {
|
|
154
320
|
console.error(
|
|
155
321
|
"[pi-dashboard] Cannot find TypeScript loader. " +
|
|
@@ -159,17 +325,30 @@ async function cmdStart(config: ServerConfig): Promise<void> {
|
|
|
159
325
|
}
|
|
160
326
|
}
|
|
161
327
|
|
|
162
|
-
// Redirect daemon stdout/stderr to a log file for crash diagnosis
|
|
163
|
-
|
|
328
|
+
// Redirect daemon stdout/stderr to a log file for crash diagnosis.
|
|
329
|
+
// Log is opened in append mode ("a") so output from prior start attempts
|
|
330
|
+
// is preserved across retries — critical for diagnosing intermittent or
|
|
331
|
+
// silent launch failures. A timestamped header line distinguishes runs.
|
|
332
|
+
// See change: fix-windows-server-parity.
|
|
333
|
+
const logDir = path.join(os.homedir(), ".pi", "dashboard");
|
|
164
334
|
fs.mkdirSync(logDir, { recursive: true });
|
|
165
|
-
const
|
|
166
|
-
|
|
335
|
+
const logPath = path.join(logDir, "server.log");
|
|
336
|
+
const logFd = fs.openSync(logPath, "a");
|
|
337
|
+
fs.writeSync(
|
|
338
|
+
logFd,
|
|
339
|
+
`\n[${new Date().toISOString()}] pi-dashboard start (parent pid ${process.pid}, port ${config.port})\n`,
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// tsLoader is a file:// URL (required on Windows for node --import).
|
|
343
|
+
// See change: fix-windows-server-parity.
|
|
167
344
|
const child = spawn(process.execPath, ["--import", tsLoader, cliPath, ...args], {
|
|
168
345
|
detached: true,
|
|
169
346
|
stdio: ["ignore", logFd, logFd],
|
|
170
347
|
env: { ...process.env },
|
|
171
348
|
});
|
|
172
349
|
child.unref();
|
|
350
|
+
// Close the parent's copy of the fd — child has its own via stdio inheritance.
|
|
351
|
+
try { fs.closeSync(logFd); } catch { /* ignore */ }
|
|
173
352
|
|
|
174
353
|
// Wait for dashboard to become available (up to 5 seconds)
|
|
175
354
|
const deadline = Date.now() + 5000;
|
|
@@ -196,31 +375,21 @@ async function cmdStart(config: ServerConfig): Promise<void> {
|
|
|
196
375
|
/**
|
|
197
376
|
* Stop the running server daemon.
|
|
198
377
|
*/
|
|
199
|
-
/**
|
|
378
|
+
/**
|
|
379
|
+
* Kill a process by PID with logging. Delegates to the shared platform
|
|
380
|
+
* primitive (`packages/shared/src/platform/process.ts`) which handles the
|
|
381
|
+
* Windows (taskkill) vs Unix (SIGTERM→SIGKILL) split.
|
|
382
|
+
* See change: consolidate-platform-handlers.
|
|
383
|
+
*/
|
|
200
384
|
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})`);
|
|
385
|
+
const result = await platformKillProcess(pid);
|
|
386
|
+
if (!result.ok) return false;
|
|
387
|
+
console.log(`${label} stopped${result.forced ? " (forced)" : ""} (pid ${pid})`);
|
|
213
388
|
return true;
|
|
214
389
|
}
|
|
215
390
|
|
|
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
|
-
}
|
|
391
|
+
// Local alias to preserve prior internal references.
|
|
392
|
+
const isProcessAlive = (pid: number) => platformIsProcessAlive(pid);
|
|
224
393
|
|
|
225
394
|
async function cmdStop(): Promise<void> {
|
|
226
395
|
const config = loadConfig();
|
|
@@ -255,6 +424,58 @@ async function cmdStop(): Promise<void> {
|
|
|
255
424
|
/**
|
|
256
425
|
* Show server status.
|
|
257
426
|
*/
|
|
427
|
+
/**
|
|
428
|
+
* `pi-dashboard upgrade-pi` — upgrade pi-coding-agent via bootstrap.
|
|
429
|
+
*
|
|
430
|
+
* If a dashboard is currently running, POST to /api/bootstrap/upgrade-pi
|
|
431
|
+
* (so the running server owns the install, broadcasts state, and reloads
|
|
432
|
+
* connected sessions). Otherwise run `bootstrapInstall` directly with a
|
|
433
|
+
* streaming progress formatter and exit when done.
|
|
434
|
+
*
|
|
435
|
+
* See change: unified-bootstrap-install §8.
|
|
436
|
+
*/
|
|
437
|
+
async function cmdUpgradePi(config: ServerConfig): Promise<void> {
|
|
438
|
+
const status = await isDashboardRunning(config.port);
|
|
439
|
+
if (status.running) {
|
|
440
|
+
console.log(
|
|
441
|
+
`[upgrade-pi] dashboard running at http://localhost:${config.port}, delegating to server`,
|
|
442
|
+
);
|
|
443
|
+
try {
|
|
444
|
+
const res = await fetch(`http://localhost:${config.port}/api/bootstrap/upgrade-pi`, {
|
|
445
|
+
method: "POST",
|
|
446
|
+
});
|
|
447
|
+
if (!res.ok) {
|
|
448
|
+
const body = await res.text();
|
|
449
|
+
console.error(`[upgrade-pi] server rejected upgrade: HTTP ${res.status} ${body}`);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
const body = (await res.json()) as { ticketId?: string };
|
|
453
|
+
console.log(`[upgrade-pi] queued (ticketId=${body.ticketId ?? "?"})`);
|
|
454
|
+
console.log("[upgrade-pi] progress is streamed to open dashboard tabs; CLI exits now.");
|
|
455
|
+
return;
|
|
456
|
+
} catch (err) {
|
|
457
|
+
console.error("[upgrade-pi] failed to reach server:", err);
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
console.log("[upgrade-pi] no dashboard running — installing directly");
|
|
463
|
+
const res = await bootstrapInstall({
|
|
464
|
+
packages: ["@mariozechner/pi-coding-agent"],
|
|
465
|
+
progress: (p) => {
|
|
466
|
+
const line = p.output
|
|
467
|
+
? `[upgrade-pi] ${p.step} ${p.status}: ${p.output}`
|
|
468
|
+
: `[upgrade-pi] ${p.step} ${p.status}`;
|
|
469
|
+
console.log(line);
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
if (!res.ok) {
|
|
473
|
+
console.error(`[upgrade-pi] failed: ${res.error}`);
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
console.log(`[upgrade-pi] ✓ installed ${res.installed.join(", ")}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
258
479
|
async function cmdStatus(port: number): Promise<void> {
|
|
259
480
|
// 1. Try mDNS discovery first
|
|
260
481
|
try {
|
|
@@ -328,6 +549,9 @@ async function main() {
|
|
|
328
549
|
case "status":
|
|
329
550
|
await cmdStatus(config.port);
|
|
330
551
|
break;
|
|
552
|
+
case "upgrade-pi":
|
|
553
|
+
await cmdUpgradePi(config);
|
|
554
|
+
break;
|
|
331
555
|
default:
|
|
332
556
|
// No subcommand — run in foreground (backward compatible)
|
|
333
557
|
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
|