@blackbelt-technology/pi-agent-dashboard 0.3.0 → 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 +67 -116
- package/README.md +93 -7
- package/docs/architecture.md +408 -9
- package/package.json +6 -4
- package/packages/extension/package.json +11 -3
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +201 -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/bridge.ts +69 -2
- 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/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 +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__/force-kill-handler.test.ts +57 -8
- 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 +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-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 +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__/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 +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 +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 +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/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__/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__/semaphore.test.ts +119 -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 +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 +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 +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/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
|
@@ -5,6 +5,7 @@ import type { FastifyInstance } from "fastify";
|
|
|
5
5
|
import type { SessionManager } from "../memory-session-manager.js";
|
|
6
6
|
import type { PreferencesStore } from "../preferences-store.js";
|
|
7
7
|
import type { MetaPersistence } from "../meta-persistence.js";
|
|
8
|
+
import type { DirectoryService } from "../directory-service.js";
|
|
8
9
|
import type { ServerConfig } from "../server.js";
|
|
9
10
|
import type { ApiResponse } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
10
11
|
import type { NetworkGuard } from "./route-deps.js";
|
|
@@ -12,7 +13,8 @@ import { detectEditors, EDITORS } from "../editor-registry.js";
|
|
|
12
13
|
import { detectCodeServerBinary, resetDetectionCache } from "../editor-detection.js";
|
|
13
14
|
import { readConfigRedacted, writeConfigPartial } from "../config-api.js";
|
|
14
15
|
import { createTunnel, deleteTunnel, getTunnelStatus } from "../tunnel.js";
|
|
15
|
-
import {
|
|
16
|
+
import { spawnRestart } from "../restart-helper.js";
|
|
17
|
+
import { spawn } from "@blackbelt-technology/pi-dashboard-shared/platform/exec.js";
|
|
16
18
|
import path from "node:path";
|
|
17
19
|
import os from "node:os";
|
|
18
20
|
import { localhostGuard, netmaskToCidrBits, networkAddress } from "../localhost-guard.js";
|
|
@@ -27,9 +29,10 @@ export function registerSystemRoutes(
|
|
|
27
29
|
config: ServerConfig;
|
|
28
30
|
networkGuard: NetworkGuard;
|
|
29
31
|
version?: string;
|
|
32
|
+
directoryService?: DirectoryService;
|
|
30
33
|
},
|
|
31
34
|
) {
|
|
32
|
-
const { sessionManager, preferencesStore, metaPersistence, config, networkGuard, version } = deps;
|
|
35
|
+
const { sessionManager, preferencesStore, metaPersistence, config, networkGuard, version, directoryService } = deps;
|
|
33
36
|
const serverStartTime = Date.now();
|
|
34
37
|
|
|
35
38
|
// Editor detection endpoint
|
|
@@ -127,6 +130,9 @@ export function registerSystemRoutes(
|
|
|
127
130
|
await (fastify as any)._reloadAuth(reloaded.auth);
|
|
128
131
|
}
|
|
129
132
|
}
|
|
133
|
+
if (partial.openspec !== undefined && directoryService) {
|
|
134
|
+
directoryService.reconfigurePolling(reloaded.openspec);
|
|
135
|
+
}
|
|
130
136
|
|
|
131
137
|
return { success: true, restartRequired: result.restartRequired };
|
|
132
138
|
},
|
|
@@ -213,42 +219,23 @@ export function registerSystemRoutes(
|
|
|
213
219
|
|
|
214
220
|
// Find the TypeScript loader from process.execArgv (--import <loader>)
|
|
215
221
|
const importIdx = process.execArgv.indexOf("--import");
|
|
216
|
-
const
|
|
222
|
+
const loader = importIdx >= 0 ? (process.execArgv[importIdx + 1] ?? "") : "";
|
|
217
223
|
|
|
218
224
|
// Allow overriding dev mode via request body
|
|
219
225
|
const useDev = request.body?.dev ?? config.dev;
|
|
220
|
-
const
|
|
221
|
-
if (useDev)
|
|
222
|
-
|
|
223
|
-
// Spawn a shell script that:
|
|
224
|
-
// 1. Waits for the old server's port to be free (up to 10s)
|
|
225
|
-
// 2. Starts the new server
|
|
226
|
-
// 3. Verifies health (up to 10s)
|
|
227
|
-
// 4. If health check fails, logs error
|
|
228
|
-
const port = config.port;
|
|
229
|
-
const nodeCmd = `${JSON.stringify(process.execPath)} ${loaderArgs.map(a => JSON.stringify(a)).join(" ")} ${JSON.stringify(cliPath)} ${args.join(" ")}`;
|
|
230
|
-
const script = [
|
|
231
|
-
// Wait for port to be free
|
|
232
|
-
`for i in $(seq 1 20); do`,
|
|
233
|
-
` lsof -i :${port} -sTCP:LISTEN >/dev/null 2>&1 || break`,
|
|
234
|
-
` sleep 0.5`,
|
|
235
|
-
`done`,
|
|
236
|
-
// Start new server
|
|
237
|
-
nodeCmd,
|
|
238
|
-
// Verify health
|
|
239
|
-
`for i in $(seq 1 20); do`,
|
|
240
|
-
` curl -sf http://localhost:${port}/api/health >/dev/null 2>&1 && exit 0`,
|
|
241
|
-
` sleep 0.5`,
|
|
242
|
-
`done`,
|
|
243
|
-
`echo "[dashboard] Restart health check failed" >&2`,
|
|
244
|
-
].join("\n");
|
|
226
|
+
const extraArgs: string[] = [];
|
|
227
|
+
if (useDev) extraArgs.push("--dev");
|
|
245
228
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
229
|
+
// Cross-platform restart: spawns a detached Node orchestrator that
|
|
230
|
+
// polls the port via net, spawns the new server, polls /api/health
|
|
231
|
+
// via http. No dependency on sh/lsof/curl — works on Windows too.
|
|
232
|
+
// See change: fix-windows-server-parity.
|
|
233
|
+
spawnRestart({
|
|
234
|
+
cliPath,
|
|
235
|
+
loader,
|
|
236
|
+
port: config.port,
|
|
237
|
+
extraArgs,
|
|
250
238
|
});
|
|
251
|
-
child.unref();
|
|
252
239
|
|
|
253
240
|
setTimeout(() => process.exit(0), 200);
|
|
254
241
|
return { ok: true };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST routes for the tool registry.
|
|
3
|
+
*
|
|
4
|
+
* GET /api/tools → list all resolutions
|
|
5
|
+
* GET /api/tools/:name → single resolution
|
|
6
|
+
* POST /api/tools/rescan → invalidate + refresh (all or one)
|
|
7
|
+
* PUT /api/tools/:name → set override path
|
|
8
|
+
* DELETE /api/tools/:name → clear override
|
|
9
|
+
* POST /api/tools/diagnostics → text/plain export
|
|
10
|
+
*
|
|
11
|
+
* Every route is guarded by the same network guard used by /api/config.
|
|
12
|
+
*
|
|
13
|
+
* See change: consolidate-tool-resolution (specs/tool-settings-ui).
|
|
14
|
+
*/
|
|
15
|
+
import type { FastifyInstance } from "fastify";
|
|
16
|
+
import type { ApiResponse } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
17
|
+
import type {
|
|
18
|
+
Resolution,
|
|
19
|
+
ToolRegistry,
|
|
20
|
+
} from "@blackbelt-technology/pi-dashboard-shared/tool-registry/index.js";
|
|
21
|
+
import { UnknownToolError } from "@blackbelt-technology/pi-dashboard-shared/tool-registry/index.js";
|
|
22
|
+
import type { NetworkGuard } from "./route-deps.js";
|
|
23
|
+
|
|
24
|
+
export interface ToolRoutesDeps {
|
|
25
|
+
registry: ToolRegistry;
|
|
26
|
+
networkGuard: NetworkGuard;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Format a plain-text diagnostics export. One tool per block, one line
|
|
31
|
+
* per attempted strategy. Used by the Settings panel's "Export diagnostics"
|
|
32
|
+
* action and by bug-report attachments.
|
|
33
|
+
*/
|
|
34
|
+
export function formatDiagnostics(tools: Resolution[]): string {
|
|
35
|
+
const lines: string[] = [];
|
|
36
|
+
lines.push(`# pi-dashboard tool diagnostics — ${new Date().toISOString()}`);
|
|
37
|
+
lines.push("");
|
|
38
|
+
for (const t of tools) {
|
|
39
|
+
const header = t.ok
|
|
40
|
+
? `[ok] ${t.name} (${t.source}) → ${t.path}`
|
|
41
|
+
: `[miss] ${t.name} → not found`;
|
|
42
|
+
lines.push(header);
|
|
43
|
+
for (const entry of t.tried) {
|
|
44
|
+
lines.push(` - ${entry.strategy}: ${entry.result}`);
|
|
45
|
+
}
|
|
46
|
+
lines.push("");
|
|
47
|
+
}
|
|
48
|
+
return lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function registerToolRoutes(
|
|
52
|
+
fastify: FastifyInstance,
|
|
53
|
+
{ registry, networkGuard }: ToolRoutesDeps,
|
|
54
|
+
): void {
|
|
55
|
+
// ── GET /api/tools ─────────────────────────────────────────────────
|
|
56
|
+
fastify.get(
|
|
57
|
+
"/api/tools",
|
|
58
|
+
{ preHandler: networkGuard },
|
|
59
|
+
async () => {
|
|
60
|
+
return { success: true, data: { tools: registry.list() } } satisfies ApiResponse<{
|
|
61
|
+
tools: Resolution[];
|
|
62
|
+
}>;
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// ── GET /api/tools/:name ───────────────────────────────────────────
|
|
67
|
+
fastify.get<{ Params: { name: string } }>(
|
|
68
|
+
"/api/tools/:name",
|
|
69
|
+
{ preHandler: networkGuard },
|
|
70
|
+
async (request, reply) => {
|
|
71
|
+
const { name } = request.params;
|
|
72
|
+
if (!registry.has(name)) {
|
|
73
|
+
reply.status(404);
|
|
74
|
+
return { success: false, error: `Unknown tool: ${name}` } satisfies ApiResponse;
|
|
75
|
+
}
|
|
76
|
+
return { success: true, data: registry.resolve(name) } satisfies ApiResponse<Resolution>;
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// ── POST /api/tools/rescan ─────────────────────────────────────────
|
|
81
|
+
fastify.post<{ Body: { name?: string } }>(
|
|
82
|
+
"/api/tools/rescan",
|
|
83
|
+
{ preHandler: networkGuard },
|
|
84
|
+
async (request, reply) => {
|
|
85
|
+
const name = request.body?.name;
|
|
86
|
+
if (name !== undefined) {
|
|
87
|
+
if (!registry.has(name)) {
|
|
88
|
+
reply.status(404);
|
|
89
|
+
return { success: false, error: `Unknown tool: ${name}` } satisfies ApiResponse;
|
|
90
|
+
}
|
|
91
|
+
registry.rescan(name);
|
|
92
|
+
} else {
|
|
93
|
+
registry.rescan();
|
|
94
|
+
}
|
|
95
|
+
return { success: true, data: { tools: registry.list() } } satisfies ApiResponse<{
|
|
96
|
+
tools: Resolution[];
|
|
97
|
+
}>;
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// ── PUT /api/tools/:name ───────────────────────────────────────────
|
|
102
|
+
fastify.put<{ Params: { name: string }; Body: { path?: string } }>(
|
|
103
|
+
"/api/tools/:name",
|
|
104
|
+
{ preHandler: networkGuard },
|
|
105
|
+
async (request, reply) => {
|
|
106
|
+
const { name } = request.params;
|
|
107
|
+
const overridePath = request.body?.path;
|
|
108
|
+
if (typeof overridePath !== "string" || !overridePath.trim()) {
|
|
109
|
+
reply.status(400);
|
|
110
|
+
return { success: false, error: "body.path is required (non-empty string)" } satisfies ApiResponse;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
registry.setOverride(name, overridePath.trim());
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (err instanceof UnknownToolError) {
|
|
116
|
+
reply.status(404);
|
|
117
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
118
|
+
}
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
return { success: true, data: registry.resolve(name) } satisfies ApiResponse<Resolution>;
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// ── DELETE /api/tools/:name ────────────────────────────────────────
|
|
126
|
+
fastify.delete<{ Params: { name: string } }>(
|
|
127
|
+
"/api/tools/:name",
|
|
128
|
+
{ preHandler: networkGuard },
|
|
129
|
+
async (request, reply) => {
|
|
130
|
+
const { name } = request.params;
|
|
131
|
+
try {
|
|
132
|
+
registry.clearOverride(name);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
if (err instanceof UnknownToolError) {
|
|
135
|
+
reply.status(404);
|
|
136
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
137
|
+
}
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
return { success: true, data: registry.resolve(name) } satisfies ApiResponse<Resolution>;
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// ── POST /api/tools/diagnostics ────────────────────────────────────
|
|
145
|
+
fastify.post(
|
|
146
|
+
"/api/tools/diagnostics",
|
|
147
|
+
{ preHandler: networkGuard },
|
|
148
|
+
async (_request, reply) => {
|
|
149
|
+
reply.type("text/plain; charset=utf-8");
|
|
150
|
+
return formatDiagnostics(registry.list());
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -6,6 +6,7 @@ import fs from "node:fs";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import { isDashboardRunning } from "@blackbelt-technology/pi-dashboard-shared/server-identity.js";
|
|
9
|
+
import { isProcessAlive } from "@blackbelt-technology/pi-dashboard-shared/platform/process.js";
|
|
9
10
|
|
|
10
11
|
const DEFAULT_PID_PATH = path.join(os.homedir(), ".pi", "dashboard", "server.pid");
|
|
11
12
|
|
|
@@ -14,16 +15,11 @@ export interface ServerPidOptions {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
+
* Re-export the platform's liveness primitive so existing importers of
|
|
19
|
+
* `server-pid.ts::isProcessAlive` keep working. See change:
|
|
20
|
+
* route-kill-paths-through-platform.
|
|
18
21
|
*/
|
|
19
|
-
export
|
|
20
|
-
try {
|
|
21
|
-
process.kill(pid, 0);
|
|
22
|
-
return true;
|
|
23
|
-
} catch {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
22
|
+
export { isProcessAlive };
|
|
27
23
|
|
|
28
24
|
/**
|
|
29
25
|
* Write the current process PID to the PID file.
|
|
@@ -47,6 +47,12 @@ import { registerRecommendedRoutes, invalidateRecommendedCache } from "./routes/
|
|
|
47
47
|
import { registerPiCoreRoutes } from "./routes/pi-core-routes.js";
|
|
48
48
|
import { PiCoreChecker } from "./pi-core-checker.js";
|
|
49
49
|
import { PiCoreUpdater } from "./pi-core-updater.js";
|
|
50
|
+
import { registerToolRoutes } from "./routes/tool-routes.js";
|
|
51
|
+
import { registerBootstrapRoutes } from "./routes/bootstrap-routes.js";
|
|
52
|
+
import { createBootstrapState, type BootstrapStateStore } from "./bootstrap-state.js";
|
|
53
|
+
import { createBootstrapQueue } from "./bootstrap-queue.js";
|
|
54
|
+
import { bootstrapInstall } from "@blackbelt-technology/pi-dashboard-shared/bootstrap-install.js";
|
|
55
|
+
import { getDefaultRegistry } from "@blackbelt-technology/pi-dashboard-shared/tool-registry/index.js";
|
|
50
56
|
import { registerProviderRoutes } from "./routes/provider-routes.js";
|
|
51
57
|
import { PackageManagerWrapper } from "./package-manager-wrapper.js";
|
|
52
58
|
import { createEditorManager, type EditorManager } from "./editor-manager.js";
|
|
@@ -73,6 +79,8 @@ export interface ServerConfig {
|
|
|
73
79
|
maxWsBufferBytes?: number;
|
|
74
80
|
/** Editor (code-server) config */
|
|
75
81
|
editor: import("@blackbelt-technology/pi-dashboard-shared/config.js").EditorConfig;
|
|
82
|
+
/** OpenSpec polling config (interval, concurrency, change detection, jitter) */
|
|
83
|
+
openspec?: import("@blackbelt-technology/pi-dashboard-shared/config.js").OpenSpecPollConfig;
|
|
76
84
|
/** Merged trusted networks from config */
|
|
77
85
|
resolvedTrustedNetworks?: string[];
|
|
78
86
|
/** CORS allowed origins from config */
|
|
@@ -85,6 +93,14 @@ export interface DashboardServer {
|
|
|
85
93
|
sessionManager: SessionManager;
|
|
86
94
|
eventStore: EventStore;
|
|
87
95
|
browserGateway: BrowserGateway;
|
|
96
|
+
/**
|
|
97
|
+
* Bootstrap state store. Exposed so the CLI can flip status during
|
|
98
|
+
* degraded-mode first-run (`pi-dashboard` without pi installed) and
|
|
99
|
+
* so the REST handler for `/api/bootstrap/upgrade-pi` can orchestrate
|
|
100
|
+
* async installs without reaching back through closures.
|
|
101
|
+
* See change: unified-bootstrap-install.
|
|
102
|
+
*/
|
|
103
|
+
bootstrapState: BootstrapStateStore;
|
|
88
104
|
/** Resolved HTTP port after start() (useful for port:0 in tests). Returns null if not listening. */
|
|
89
105
|
httpPort(): number | null;
|
|
90
106
|
/** Resolved pi gateway port after start(). Returns null if not listening. */
|
|
@@ -94,9 +110,20 @@ export interface DashboardServer {
|
|
|
94
110
|
export async function createServer(config: ServerConfig): Promise<DashboardServer> {
|
|
95
111
|
// Ensure bridge extension is registered in pi's global settings
|
|
96
112
|
// (needed for bundled installs where pi can't discover it from package.json)
|
|
113
|
+
//
|
|
114
|
+
// __serverDir = <repo>/packages/server/src
|
|
115
|
+
// baseDir MUST be <repo>/ so findBundledExtension resolves
|
|
116
|
+
// <repo>/packages/extension. Three levels up, not two.
|
|
97
117
|
const __serverDir = path.dirname(fileURLToPath(import.meta.url));
|
|
98
|
-
const extPath = findBundledExtension(path.resolve(__serverDir, "..", ".."));
|
|
99
|
-
if (extPath)
|
|
118
|
+
const extPath = findBundledExtension(path.resolve(__serverDir, "..", "..", ".."));
|
|
119
|
+
if (extPath) {
|
|
120
|
+
registerBridgeExtension(extPath);
|
|
121
|
+
console.log(`[dashboard] Bridge extension registered: ${extPath}`);
|
|
122
|
+
} else {
|
|
123
|
+
console.warn(`[dashboard] Bridge extension NOT found (searched from ${__serverDir}). ` +
|
|
124
|
+
`Sessions will spawn but never connect to the gateway. ` +
|
|
125
|
+
`Manually add the extension path to ~/.pi/agent/settings.json packages[] as a workaround.`);
|
|
126
|
+
}
|
|
100
127
|
|
|
101
128
|
// Run migration from sessions.json + state.json if needed
|
|
102
129
|
if (needsMigration()) {
|
|
@@ -161,7 +188,7 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
161
188
|
knownSessionIds.add(s.id);
|
|
162
189
|
}
|
|
163
190
|
|
|
164
|
-
const directoryService = createDirectoryService(preferencesStore, sessionManager);
|
|
191
|
+
const directoryService = createDirectoryService(preferencesStore, sessionManager, config.openspec);
|
|
165
192
|
|
|
166
193
|
// mDNS peer discovery state
|
|
167
194
|
let mdnsBrowser: DashboardBrowser | null = null;
|
|
@@ -329,6 +356,33 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
329
356
|
fastify.get("/auth/status", async () => ({ authenticated: true, authEnabled: false }));
|
|
330
357
|
}
|
|
331
358
|
|
|
359
|
+
// ── Bootstrap state + queue ──────────────────────────────────────
|
|
360
|
+
// Declared here (before session-api registration) so the session
|
|
361
|
+
// routes can gate spawn operations on bootstrap status.
|
|
362
|
+
// See change: unified-bootstrap-install.
|
|
363
|
+
const bootstrapState = createBootstrapState();
|
|
364
|
+
const bootstrapQueue = createBootstrapQueue();
|
|
365
|
+
let lastBootstrapStatus: "ready" | "installing" | "failed" = "ready";
|
|
366
|
+
const unsubscribeBootstrap = bootstrapState.subscribe((snapshot) => {
|
|
367
|
+
browserGateway.broadcastToAll({
|
|
368
|
+
type: "bootstrap_status_update",
|
|
369
|
+
state: snapshot,
|
|
370
|
+
});
|
|
371
|
+
// Flush queued pi-dependent operations on ready transition.
|
|
372
|
+
if (lastBootstrapStatus !== "ready" && snapshot.status === "ready") {
|
|
373
|
+
void bootstrapQueue.flushAll();
|
|
374
|
+
}
|
|
375
|
+
lastBootstrapStatus = snapshot.status;
|
|
376
|
+
});
|
|
377
|
+
const unsubscribeQueueComplete = bootstrapQueue.onTicketComplete((evt) => {
|
|
378
|
+
browserGateway.broadcastToAll({
|
|
379
|
+
type: "bootstrap_ticket_complete",
|
|
380
|
+
ticketId: evt.ticketId,
|
|
381
|
+
success: evt.success,
|
|
382
|
+
error: evt.error,
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
332
386
|
// Session control REST API (wraps WebSocket-only operations)
|
|
333
387
|
registerSessionApi(fastify, {
|
|
334
388
|
sessionManager,
|
|
@@ -336,6 +390,8 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
336
390
|
browserGateway,
|
|
337
391
|
pendingForkRegistry,
|
|
338
392
|
pendingDashboardSpawns,
|
|
393
|
+
bootstrapState,
|
|
394
|
+
bootstrapQueue,
|
|
339
395
|
});
|
|
340
396
|
|
|
341
397
|
// Register route modules
|
|
@@ -350,12 +406,118 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
350
406
|
preferencesStore,
|
|
351
407
|
directoryService,
|
|
352
408
|
networkGuard,
|
|
409
|
+
bootstrapState,
|
|
353
410
|
onOpenSpecChanged: (cwd) => {
|
|
354
411
|
const data = directoryService.getOpenSpecData(cwd);
|
|
355
412
|
if (data) browserGateway.broadcastToAll({ type: "openspec_update", cwd, data });
|
|
356
413
|
},
|
|
357
414
|
});
|
|
358
|
-
registerSystemRoutes(fastify, { sessionManager, preferencesStore, metaPersistence, config, networkGuard, version: pkgVersion });
|
|
415
|
+
registerSystemRoutes(fastify, { sessionManager, preferencesStore, metaPersistence, config, networkGuard, version: pkgVersion, directoryService });
|
|
416
|
+
registerToolRoutes(fastify, { registry: getDefaultRegistry(), networkGuard });
|
|
417
|
+
|
|
418
|
+
// ── Bootstrap REST routes ────────────────────────────────────────
|
|
419
|
+
// The routes module is registered here; state + queue are declared
|
|
420
|
+
// above (before session-api) so session routes can see them.
|
|
421
|
+
registerBootstrapRoutes(fastify, {
|
|
422
|
+
bootstrapState,
|
|
423
|
+
networkGuard,
|
|
424
|
+
triggerUpgradePi: async () => {
|
|
425
|
+
const packages = ["@mariozechner/pi-coding-agent"];
|
|
426
|
+
bootstrapState.setLastInstallPackages(packages);
|
|
427
|
+
bootstrapState.set({
|
|
428
|
+
status: "installing",
|
|
429
|
+
progress: { step: "@mariozechner/pi-coding-agent", output: "starting upgrade…" },
|
|
430
|
+
error: undefined,
|
|
431
|
+
});
|
|
432
|
+
try {
|
|
433
|
+
const res = await bootstrapInstall({
|
|
434
|
+
packages,
|
|
435
|
+
progress: (p) => {
|
|
436
|
+
bootstrapState.set({
|
|
437
|
+
progress: { step: p.step, output: p.output },
|
|
438
|
+
});
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
if (res.ok) {
|
|
442
|
+
bootstrapState.set({
|
|
443
|
+
status: "ready",
|
|
444
|
+
progress: undefined,
|
|
445
|
+
error: undefined,
|
|
446
|
+
});
|
|
447
|
+
// Broadcast /reload to connected sessions so they pick up the
|
|
448
|
+
// new pi version. Mirrors the pi-core update pattern above.
|
|
449
|
+
const connectedIds = piGateway.getConnectedSessionIds();
|
|
450
|
+
for (const sid of connectedIds) {
|
|
451
|
+
const session = sessionManager.get(sid);
|
|
452
|
+
if (session && session.status !== "ended") {
|
|
453
|
+
piGateway.sendToSession(sid, {
|
|
454
|
+
type: "send_prompt",
|
|
455
|
+
sessionId: sid,
|
|
456
|
+
text: "/reload",
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
bootstrapState.set({
|
|
462
|
+
status: "failed",
|
|
463
|
+
error: { message: res.error },
|
|
464
|
+
progress: undefined,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
} catch (err) {
|
|
468
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
469
|
+
bootstrapState.set({
|
|
470
|
+
status: "failed",
|
|
471
|
+
error: { message },
|
|
472
|
+
progress: undefined,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
triggerRetry: async () => {
|
|
477
|
+
// Retry re-runs the EXACT package set from the last failed install.
|
|
478
|
+
// Falls back to the default first-run set if no prior install was
|
|
479
|
+
// recorded (edge case: manual retry before any install attempt).
|
|
480
|
+
const prev = bootstrapState.getLastInstallPackages();
|
|
481
|
+
const packages = prev.length > 0
|
|
482
|
+
? prev
|
|
483
|
+
: ["@mariozechner/pi-coding-agent", "@fission-ai/openspec", "tsx"];
|
|
484
|
+
bootstrapState.set({
|
|
485
|
+
status: "installing",
|
|
486
|
+
progress: { step: "retry", output: `restarting install (${packages.length} pkg${packages.length === 1 ? "" : "s"})…` },
|
|
487
|
+
error: undefined,
|
|
488
|
+
});
|
|
489
|
+
try {
|
|
490
|
+
const res = await bootstrapInstall({
|
|
491
|
+
packages,
|
|
492
|
+
progress: (p) => {
|
|
493
|
+
bootstrapState.set({
|
|
494
|
+
progress: { step: p.step, output: p.output },
|
|
495
|
+
});
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
if (res.ok) {
|
|
499
|
+
bootstrapState.set({
|
|
500
|
+
status: "ready",
|
|
501
|
+
progress: undefined,
|
|
502
|
+
error: undefined,
|
|
503
|
+
});
|
|
504
|
+
} else {
|
|
505
|
+
bootstrapState.set({
|
|
506
|
+
status: "failed",
|
|
507
|
+
error: { message: res.error },
|
|
508
|
+
progress: undefined,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
} catch (err) {
|
|
512
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
513
|
+
bootstrapState.set({
|
|
514
|
+
status: "failed",
|
|
515
|
+
error: { message },
|
|
516
|
+
progress: undefined,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
});
|
|
359
521
|
// Package management
|
|
360
522
|
const packageManagerWrapper = new PackageManagerWrapper();
|
|
361
523
|
|
|
@@ -374,6 +536,7 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
374
536
|
scope: result.scope,
|
|
375
537
|
success: result.success,
|
|
376
538
|
error: result.error,
|
|
539
|
+
diagnostics: result.diagnostics,
|
|
377
540
|
sessionsReloaded: (result as any).sessionsReloaded,
|
|
378
541
|
} as any);
|
|
379
542
|
if (result.success) invalidateRecommendedCache();
|
|
@@ -432,6 +595,7 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
432
595
|
registerPiCoreRoutes(fastify, {
|
|
433
596
|
piCoreChecker,
|
|
434
597
|
piCoreUpdater,
|
|
598
|
+
bootstrapState,
|
|
435
599
|
onUpdateComplete: (payload) => {
|
|
436
600
|
browserGateway.broadcastToAll({
|
|
437
601
|
type: "pi_core_update_complete",
|
|
@@ -441,6 +605,17 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
441
605
|
},
|
|
442
606
|
});
|
|
443
607
|
|
|
608
|
+
// Warm pi-coding-agent module import + DefaultPackageManager instances
|
|
609
|
+
// on startup so the first user request to /api/packages/* doesn't pay
|
|
610
|
+
// the 3-5s cold-load cost. Runs in background; errors are swallowed
|
|
611
|
+
// (user-visible flow surfaces any real problem with the full diagnostic
|
|
612
|
+
// trail via the OperationResult.diagnostics field).
|
|
613
|
+
// See change: consolidate-tool-resolution.
|
|
614
|
+
void Promise.allSettled([
|
|
615
|
+
packageManagerWrapper.listInstalled("global"),
|
|
616
|
+
packageManagerWrapper.listInstalled("local"),
|
|
617
|
+
]);
|
|
618
|
+
|
|
444
619
|
// Editor (code-server) routes and proxy.
|
|
445
620
|
// NOTE: routes are *registered* here but cannot dispatch until fastify.listen runs
|
|
446
621
|
// inside server.start(). The orphan sweep in editorPidRegistry.cleanupOrphans()
|
|
@@ -453,17 +628,38 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
453
628
|
registerKnownServersRoutes(fastify, { networkGuard, getPeerServers: () => peerServers });
|
|
454
629
|
registerProviderRoutes(fastify, { networkGuard, piGateway, browserGateway });
|
|
455
630
|
|
|
456
|
-
// Serve static files / SPA fallback
|
|
457
|
-
//
|
|
631
|
+
// Serve static files / SPA fallback.
|
|
632
|
+
//
|
|
633
|
+
// Resolution strategies, in order:
|
|
634
|
+
// 1. Node module resolver — works in ANY install layout
|
|
635
|
+
// (flat `node_modules/`, scoped, nested, pnpm, whatever).
|
|
636
|
+
// 2. Sibling-to-server in the installed @scope layout.
|
|
637
|
+
// 3. Monorepo workspace sibling.
|
|
638
|
+
// 4. Legacy dist/client.
|
|
639
|
+
//
|
|
640
|
+
// Same class of bug as commits 40a1319 (bridge auto-registration)
|
|
641
|
+
// and e11f5eb (server-launcher.ts resolve): sibling-path arithmetic
|
|
642
|
+
// that works in the dev repo silently returns wrong paths in the
|
|
643
|
+
// installed node_modules layout. require.resolve identifies packages
|
|
644
|
+
// by name, which is the only canonical identity across layouts.
|
|
458
645
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
459
|
-
const clientSearchPaths = [
|
|
460
|
-
|
|
461
|
-
|
|
646
|
+
const clientSearchPaths: string[] = [];
|
|
647
|
+
try {
|
|
648
|
+
const webPkgJson = createRequire(import.meta.url).resolve("@blackbelt-technology/pi-dashboard-web/package.json");
|
|
649
|
+
clientSearchPaths.push(path.join(path.dirname(webPkgJson), "dist"));
|
|
650
|
+
} catch {
|
|
651
|
+
// Web package not resolvable — fall through to path-based search.
|
|
652
|
+
}
|
|
653
|
+
clientSearchPaths.push(
|
|
654
|
+
// Installed as scoped sibling of server
|
|
655
|
+
path.join(__dirname, "..", "..", "pi-dashboard-web", "dist"),
|
|
656
|
+
// Installed in a parent node_modules (hoisted)
|
|
657
|
+
path.join(__dirname, "..", "..", "..", "@blackbelt-technology", "pi-dashboard-web", "dist"),
|
|
462
658
|
// Monorepo workspace sibling
|
|
463
659
|
path.join(__dirname, "../../client/dist"),
|
|
464
660
|
// Legacy path
|
|
465
661
|
path.join(__dirname, "../../dist/client"),
|
|
466
|
-
|
|
662
|
+
);
|
|
467
663
|
const clientDir = clientSearchPaths.find(p => existsSync(path.join(p, "index.html"))) ?? "";
|
|
468
664
|
const hasProductionBuild = !!clientDir;
|
|
469
665
|
if (!hasProductionBuild) {
|
|
@@ -548,6 +744,7 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
548
744
|
sessionManager,
|
|
549
745
|
eventStore,
|
|
550
746
|
browserGateway,
|
|
747
|
+
bootstrapState,
|
|
551
748
|
|
|
552
749
|
httpPort() {
|
|
553
750
|
const addr = fastify.server.address();
|
|
@@ -670,6 +867,10 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
670
867
|
preferencesStore.flush();
|
|
671
868
|
preferencesStore.dispose();
|
|
672
869
|
|
|
870
|
+
unsubscribeBootstrap();
|
|
871
|
+
unsubscribeQueueComplete();
|
|
872
|
+
bootstrapState.dispose();
|
|
873
|
+
bootstrapQueue.clear("server shutting down");
|
|
673
874
|
await deleteTunnel(config.port);
|
|
674
875
|
piGateway.stop();
|
|
675
876
|
for (const client of browserGateway.wss.clients) {
|