@blackbelt-technology/pi-agent-dashboard 0.5.2 → 0.5.4
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 +19 -30
- package/README.md +69 -1
- package/docs/architecture.md +89 -165
- package/package.json +11 -7
- package/packages/extension/package.json +2 -2
- package/packages/extension/src/__tests__/bridge-default-model-gate.test.ts +47 -0
- package/packages/extension/src/__tests__/bridge-followup-chat-order.test.ts +215 -0
- package/packages/extension/src/__tests__/bridge-followup-multi-entry.test.ts +202 -0
- package/packages/extension/src/__tests__/bridge-queue-update-forward.test.ts +77 -0
- package/packages/extension/src/__tests__/bridge-retry-ordering.test.ts +148 -0
- package/packages/extension/src/__tests__/bridge-shadow-queue-drain.test.ts +221 -0
- package/packages/extension/src/__tests__/bridge-shadow-queue-gate.test.ts +299 -0
- package/packages/extension/src/__tests__/bridge-shutdown-reset.test.ts +238 -0
- package/packages/extension/src/__tests__/bridge-slash-command-routing.test.ts +127 -31
- package/packages/extension/src/__tests__/command-handler.test.ts +105 -3
- package/packages/extension/src/__tests__/fixtures/usage-limit-error-strings.ts +127 -0
- package/packages/extension/src/__tests__/source-detector.test.ts +15 -0
- package/packages/extension/src/__tests__/usage-limit-orderer.test.ts +12 -0
- package/packages/extension/src/bridge-default-model-gate.ts +32 -0
- package/packages/extension/src/bridge.ts +299 -20
- package/packages/extension/src/command-handler.ts +53 -7
- package/packages/extension/src/dashboard-default-adapter.ts +5 -0
- package/packages/extension/src/prompt-bus.ts +15 -0
- package/packages/extension/src/slash-dispatch.ts +30 -15
- package/packages/extension/src/source-detector.ts +13 -5
- package/packages/extension/src/usage-limit-orderer.ts +18 -1
- package/packages/server/bin/pi-dashboard.mjs +62 -14
- package/packages/server/package.json +9 -5
- package/packages/server/src/__tests__/browser-gateway-register-handler.test.ts +69 -0
- package/packages/server/src/__tests__/cli-env-no-clobber.test.ts +46 -0
- package/packages/server/src/__tests__/cli-no-bootstrap-references.test.ts +69 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +9 -10
- package/packages/server/src/__tests__/cli-version.test.ts +151 -0
- package/packages/server/src/__tests__/directory-service-openspec-enabled.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service-refresh-force.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service-specs-mtime.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service-toctou.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service.test.ts +9 -0
- package/packages/server/src/__tests__/doctor-route.test.ts +53 -0
- package/packages/server/src/__tests__/event-wiring-queue-state.test.ts +156 -0
- package/packages/server/src/__tests__/event-wiring-resume-clear.test.ts +105 -0
- package/packages/server/src/__tests__/health-shape.test.ts +35 -12
- package/packages/server/src/__tests__/installed-package-enricher.test.ts +12 -12
- package/packages/server/src/__tests__/is-activity-event.test.ts +4 -7
- package/packages/server/src/__tests__/package-routes.test.ts +6 -2
- package/packages/server/src/__tests__/pi-changelog-routes.test.ts +10 -13
- package/packages/server/src/__tests__/pi-core-checker.test.ts +2 -2
- package/packages/server/src/__tests__/pi-version-skew.test.ts +3 -2
- package/packages/server/src/__tests__/plugin-activation-routes.test.ts +267 -0
- package/packages/server/src/__tests__/plugin-intent-cache.test.ts +75 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +196 -0
- package/packages/server/src/__tests__/reattach-placement.test.ts +9 -0
- package/packages/server/src/__tests__/recommended-routes.test.ts +2 -2
- package/packages/server/src/__tests__/recovery-server.test.ts +203 -0
- package/packages/server/src/__tests__/session-action-handler-clear-queue.test.ts +153 -0
- package/packages/server/src/__tests__/session-action-handler-headless-reload.test.ts +43 -0
- package/packages/server/src/__tests__/session-order-manager.test.ts +9 -0
- package/packages/server/src/__tests__/session-order-reboot.test.ts +9 -0
- package/packages/server/src/__tests__/session-ordering-integration.test.ts +9 -0
- package/packages/server/src/browser-gateway.ts +83 -5
- package/packages/server/src/browser-handlers/directory-handler.ts +69 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +89 -0
- package/packages/server/src/browser-handlers/subscription-handler.ts +23 -0
- package/packages/server/src/changelog-parser.ts +1 -1
- package/packages/server/src/cli.ts +68 -250
- package/packages/server/src/event-status-extraction.ts +14 -62
- package/packages/server/src/event-wiring.ts +23 -10
- package/packages/server/src/memory-session-manager.ts +4 -0
- package/packages/server/src/pi-core-checker.ts +1 -1
- package/packages/server/src/pi-dev-version-check.ts +1 -1
- package/packages/server/src/pi-version-skew.ts +24 -46
- package/packages/server/src/plugin-intent-cache.ts +67 -0
- package/packages/server/src/preferences-store.ts +199 -13
- package/packages/server/src/recovery-server.ts +366 -0
- package/packages/server/src/routes/__tests__/manifest-route.test.ts +138 -0
- package/packages/server/src/routes/doctor-routes.ts +26 -21
- package/packages/server/src/routes/manifest-route.ts +162 -0
- package/packages/server/src/routes/openspec-routes.ts +4 -25
- package/packages/server/src/routes/pi-changelog-routes.ts +5 -24
- package/packages/server/src/routes/pi-core-routes.ts +3 -23
- package/packages/server/src/routes/plugin-activation-routes.ts +193 -0
- package/packages/server/src/routes/recommended-routes.ts +21 -0
- package/packages/server/src/routes/system-routes.ts +73 -11
- package/packages/server/src/server.ts +105 -307
- package/packages/server/src/session-api.ts +5 -63
- package/packages/shared/package.json +1 -1
- package/packages/shared/src/__tests__/binary-lookup-resolveJiti.test.ts +28 -0
- package/packages/shared/src/__tests__/binary-lookup-spawn-env.test.ts +61 -0
- package/packages/shared/src/__tests__/binary-lookup.test.ts +16 -0
- package/packages/shared/src/__tests__/bridge-register.test.ts +67 -0
- package/packages/shared/src/__tests__/ci-electron-no-side-effects.test.ts +129 -0
- package/packages/shared/src/__tests__/config.test.ts +40 -0
- package/packages/shared/src/__tests__/dashboard-paths.test.ts +81 -0
- package/packages/shared/src/__tests__/ensure-windows-path.test.ts +112 -0
- package/packages/shared/src/__tests__/intent-types.test.ts +120 -0
- package/packages/shared/src/__tests__/jiti-packages-parity.test.ts +85 -0
- package/packages/shared/src/__tests__/legacy-managed-dir.test.ts +59 -0
- package/packages/shared/src/__tests__/no-direct-child-process.test.ts +12 -0
- package/packages/shared/src/__tests__/no-electron-execpath-spawn.test.ts +149 -0
- package/packages/shared/src/__tests__/no-flow-command-route-claims.test.ts +71 -0
- package/packages/shared/src/__tests__/no-flow-references-in-shell.test.ts +221 -0
- package/packages/shared/src/__tests__/no-managed-dir-reference.test.ts +134 -0
- package/packages/shared/src/__tests__/no-pi-dashboard-version-jiti-gate.test.ts +41 -0
- package/packages/shared/src/__tests__/no-primitive-direct-import.test.ts +235 -0
- package/packages/shared/src/__tests__/no-server-imports-in-resolver.test.ts +53 -0
- package/packages/shared/src/__tests__/node-spawn-jiti-contract.test.ts +54 -101
- package/packages/shared/src/__tests__/node-spawn.test.ts +29 -13
- package/packages/shared/src/__tests__/pi-package-resolver.test.ts +300 -0
- package/packages/shared/src/__tests__/plugin-activation-contracts.test.ts +74 -0
- package/packages/shared/src/__tests__/plugin-bridge-classify-source.test.ts +73 -0
- package/packages/shared/src/__tests__/plugin-bridge-register-extended.test.ts +17 -5
- package/packages/shared/src/__tests__/plugin-bridge-register-packages.test.ts +233 -0
- package/packages/shared/src/__tests__/plugin-bridge-register.test.ts +19 -9
- package/packages/shared/src/__tests__/publish-workflow-contract.test.ts +154 -15
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +28 -10
- package/packages/shared/src/__tests__/resolver-parity-with-scanner.test.ts +76 -0
- package/packages/shared/src/__tests__/server-identity.test.ts +127 -0
- package/packages/shared/src/__tests__/server-launcher.test.ts +35 -0
- package/packages/shared/src/__tests__/source-matching.test.ts +5 -5
- package/packages/shared/src/__tests__/sync-versions-spec.test.ts +76 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +50 -2
- package/packages/shared/src/bridge-register.ts +35 -2
- package/packages/shared/src/browser-protocol.ts +176 -2
- package/packages/shared/src/config.ts +12 -0
- package/packages/shared/src/dashboard-paths.ts +69 -0
- package/packages/shared/src/dashboard-plugin/index.ts +2 -0
- package/packages/shared/src/dashboard-plugin/intent-types.ts +93 -0
- package/packages/shared/src/dashboard-plugin/manifest-types.ts +55 -1
- package/packages/shared/src/dashboard-plugin/plugin-status.ts +82 -0
- package/packages/shared/src/dashboard-plugin/slot-props.ts +11 -0
- package/packages/shared/src/dashboard-plugin/slot-types.ts +16 -2
- package/packages/shared/src/dashboard-plugin/ui-primitives.ts +287 -0
- package/packages/shared/src/dashboard-starter.ts +22 -0
- package/packages/shared/src/doctor-core.ts +49 -27
- package/packages/shared/src/launch-source-types.ts +9 -9
- package/packages/shared/src/legacy-managed-dir.ts +97 -0
- package/packages/shared/src/mdns-discovery.ts +4 -1
- package/packages/shared/src/pi-package-resolver.ts +388 -0
- package/packages/shared/src/platform/binary-lookup.ts +27 -3
- package/packages/shared/src/platform/ensure-windows-path.ts +95 -0
- package/packages/shared/src/platform/exec.ts +22 -0
- package/packages/shared/src/platform/node-spawn.ts +42 -41
- package/packages/shared/src/plugin-bridge-register.ts +275 -18
- package/packages/shared/src/protocol.ts +94 -2
- package/packages/shared/src/recommended-extensions.ts +34 -10
- package/packages/shared/src/server-identity.ts +74 -5
- package/packages/shared/src/server-launcher.ts +20 -0
- package/packages/shared/src/source-matching.ts +1 -1
- package/packages/shared/src/tool-registry/__tests__/node-script-toargv-fallback.test.ts +84 -0
- package/packages/shared/src/tool-registry/definitions.ts +91 -7
- package/packages/shared/src/types.ts +12 -8
- package/scripts/maybe-patch-package.cjs +44 -0
- package/packages/server/src/__tests__/bootstrap-install-from-list.test.ts +0 -263
- package/packages/server/src/__tests__/bootstrap-queue.test.ts +0 -120
- package/packages/server/src/__tests__/bootstrap-routes.test.ts +0 -125
- package/packages/server/src/__tests__/bootstrap-state.test.ts +0 -119
- package/packages/server/src/__tests__/cli-bootstrap.test.ts +0 -36
- package/packages/server/src/__tests__/event-status-extraction-flow.test.ts +0 -55
- package/packages/server/src/__tests__/legacy-pi-cleanup.test.ts +0 -149
- package/packages/server/src/__tests__/post-install-openspec-refresh.test.ts +0 -180
- package/packages/server/src/__tests__/post-install-rescan.test.ts +0 -134
- package/packages/server/src/__tests__/system-routes-reextract.test.ts +0 -91
- package/packages/server/src/bootstrap-install-from-list.ts +0 -232
- package/packages/server/src/bootstrap-queue.ts +0 -130
- package/packages/server/src/bootstrap-state.ts +0 -159
- package/packages/server/src/legacy-pi-cleanup.ts +0 -151
- package/packages/server/src/routes/bootstrap-routes.ts +0 -125
- package/packages/shared/src/__tests__/bootstrap/README.md +0 -133
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +0 -378
- package/packages/shared/src/__tests__/bootstrap/assertions.ts +0 -136
- package/packages/shared/src/__tests__/bootstrap/cube.test.ts +0 -47
- package/packages/shared/src/__tests__/bootstrap/cube.ts +0 -66
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +0 -84
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +0 -90
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +0 -34
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/d-overrides.test.ts.snap +0 -20
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +0 -62
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +0 -34
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +0 -49
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/j-path-gui-minimal.test.ts.snap +0 -12
- package/packages/shared/src/__tests__/bootstrap/families/a-electron.test.ts +0 -156
- package/packages/shared/src/__tests__/bootstrap/families/b-npm-global.test.ts +0 -157
- package/packages/shared/src/__tests__/bootstrap/families/c-dev-monorepo.test.ts +0 -102
- package/packages/shared/src/__tests__/bootstrap/families/d-overrides.test.ts +0 -76
- package/packages/shared/src/__tests__/bootstrap/families/e-stale-partial.test.ts +0 -94
- package/packages/shared/src/__tests__/bootstrap/families/f-cwd-variants.test.ts +0 -87
- package/packages/shared/src/__tests__/bootstrap/families/g-windows-specifics.test.ts +0 -143
- package/packages/shared/src/__tests__/bootstrap/families/h-home-drift.test.ts +0 -64
- package/packages/shared/src/__tests__/bootstrap/families/i-malformed-settings.test.ts +0 -77
- package/packages/shared/src/__tests__/bootstrap/families/index.ts +0 -19
- package/packages/shared/src/__tests__/bootstrap/families/j-path-gui-minimal.test.ts +0 -61
- package/packages/shared/src/__tests__/bootstrap/families/k-dashboard-absent.test.ts +0 -50
- package/packages/shared/src/__tests__/bootstrap/families/l-instance-coordination.test.ts +0 -272
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +0 -58
- package/packages/shared/src/__tests__/bootstrap/fixtures/electron-layout.ts +0 -84
- package/packages/shared/src/__tests__/bootstrap/fixtures/index.ts +0 -9
- package/packages/shared/src/__tests__/bootstrap/fixtures/managed-install.ts +0 -85
- package/packages/shared/src/__tests__/bootstrap/fixtures/npm-global-layout.ts +0 -122
- package/packages/shared/src/__tests__/bootstrap/fixtures/pi-versions.ts +0 -36
- package/packages/shared/src/__tests__/bootstrap/fixtures/settings-json.ts +0 -39
- package/packages/shared/src/__tests__/bootstrap/harness.smoke.test.ts +0 -220
- package/packages/shared/src/__tests__/bootstrap/harness.ts +0 -413
- package/packages/shared/src/__tests__/bootstrap/scenarios-skipped.ts +0 -125
- package/packages/shared/src/__tests__/bootstrap/scenarios.ts +0 -132
- package/packages/shared/src/__tests__/bootstrap-install-resolve-npm.test.ts +0 -72
- package/packages/shared/src/__tests__/install-managed-node-bootstrap-order.test.ts +0 -68
- package/packages/shared/src/__tests__/install-managed-node.test.ts +0 -192
- package/packages/shared/src/__tests__/installable-list.test.ts +0 -130
- package/packages/shared/src/__tests__/no-installable-list-in-bridge.test.ts +0 -52
- package/packages/shared/src/bootstrap-install.ts +0 -406
- package/packages/shared/src/installable-list.ts +0 -152
- package/packages/shared/src/launch-source-flag.ts +0 -14
|
@@ -15,7 +15,16 @@
|
|
|
15
15
|
* --dev Development mode (skip static files)
|
|
16
16
|
* --no-tunnel Disable zrok tunnel
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
// `createServer` is imported dynamically inside `runForeground()` so a
|
|
19
|
+
// top-level module-resolution failure (missing `fastify` etc.) can be
|
|
20
|
+
// caught and degraded into the recovery HTTP server instead of crashing
|
|
21
|
+
// the process. The type-only import here is fully erased at runtime.
|
|
22
|
+
import type { createServer as _CreateServerType, ServerConfig } from "./server.js";
|
|
23
|
+
import {
|
|
24
|
+
startRecoveryServer,
|
|
25
|
+
isModuleNotFoundError,
|
|
26
|
+
parseModuleNotFoundError,
|
|
27
|
+
} from "./recovery-server.js";
|
|
19
28
|
import { loadConfig, ensureConfig } from "@blackbelt-technology/pi-dashboard-shared/config.js";
|
|
20
29
|
import {
|
|
21
30
|
launchDashboardServer,
|
|
@@ -47,48 +56,13 @@ import { discoverDashboard } from "@blackbelt-technology/pi-dashboard-shared/mdn
|
|
|
47
56
|
|
|
48
57
|
import { assertNodeVersionSupported } from "./node-guard.js";
|
|
49
58
|
import { getDefaultRegistry } from "@blackbelt-technology/pi-dashboard-shared/tool-registry/index.js";
|
|
50
|
-
import { bootstrapInstall } from "@blackbelt-technology/pi-dashboard-shared/bootstrap-install.js";
|
|
51
59
|
import {
|
|
52
60
|
findBundledExtension,
|
|
53
61
|
registerBridgeExtension,
|
|
54
62
|
} from "@blackbelt-technology/pi-dashboard-shared/bridge-register.js";
|
|
55
|
-
import type { DashboardServer } from "./server.js";
|
|
56
|
-
import { updateBootstrapCompatibility } from "./pi-version-skew.js";
|
|
57
|
-
import type { BootstrapStateStore } from "./bootstrap-state.js";
|
|
58
63
|
import { parseDashboardStarter } from "@blackbelt-technology/pi-dashboard-shared/dashboard-starter.js";
|
|
59
|
-
import { bootstrapInstallFromList } from "./bootstrap-install-from-list.js";
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Emit a stderr warning at CLI startup when the resolved pi version is
|
|
63
|
-
* below `piCompatibility.minimum` (blocking) or below `.recommended`
|
|
64
|
-
* (advisory). Reads from the already-populated `bootstrapState` so no
|
|
65
|
-
* additional I/O happens here. See change: warn-pi-version-skew-in-cli.
|
|
66
|
-
*/
|
|
67
|
-
function logCompatibilityWarning(store: BootstrapStateStore): void {
|
|
68
|
-
const s = store.get();
|
|
69
|
-
const c = s.compatibility;
|
|
70
|
-
if (!c || !c.current) return;
|
|
71
|
-
// Below minimum: `updateBootstrapCompatibility` sets `error.message`.
|
|
72
|
-
// We treat the presence of a blocking error + upgradeRecommended as the
|
|
73
|
-
// below-minimum signal; `upgradeRecommended` alone means below-recommended.
|
|
74
|
-
if (s.error?.message && c.upgradeRecommended) {
|
|
75
|
-
console.error(
|
|
76
|
-
`[bootstrap] ⚠ pi ${c.current} is below the required minimum ${c.minimum}.`,
|
|
77
|
-
);
|
|
78
|
-
console.error(
|
|
79
|
-
`[bootstrap] All pi-dependent features (sessions, resources, openspec) will return 503.`,
|
|
80
|
-
);
|
|
81
|
-
console.error(`[bootstrap] Run: pi-dashboard upgrade-pi`);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
if (c.upgradeRecommended) {
|
|
85
|
-
console.warn(
|
|
86
|
-
`[bootstrap] pi ${c.current} is below the recommended ${c.recommended} — consider running \`pi-dashboard upgrade-pi\``,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
64
|
|
|
91
|
-
const SUBCOMMANDS = ["start", "stop", "restart", "status"
|
|
65
|
+
const SUBCOMMANDS = ["start", "stop", "restart", "status"] as const;
|
|
92
66
|
type Subcommand = (typeof SUBCOMMANDS)[number];
|
|
93
67
|
|
|
94
68
|
export interface ParsedArgs {
|
|
@@ -157,184 +131,83 @@ export function buildConfig(flags: Partial<ServerConfig>): ServerConfig {
|
|
|
157
131
|
}
|
|
158
132
|
|
|
159
133
|
/**
|
|
160
|
-
* Run the server in the foreground
|
|
134
|
+
* Run the server in the foreground.
|
|
161
135
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* queue or 503 during this window (see change tasks §5).
|
|
167
|
-
*
|
|
168
|
-
* See change: unified-bootstrap-install.
|
|
136
|
+
* Pi/openspec/tsx ship as regular npm deps of this package, so the
|
|
137
|
+
* ToolRegistry resolve of "pi" at startup either succeeds (regular
|
|
138
|
+
* path) or signals a corrupted install (hard error). See change:
|
|
139
|
+
* eliminate-electron-runtime-install.
|
|
169
140
|
*/
|
|
170
141
|
async function runForeground(config: ServerConfig): Promise<void> {
|
|
171
142
|
assertNodeVersionSupported();
|
|
172
|
-
const server = await createServer(config);
|
|
173
|
-
|
|
174
|
-
// Stamp the bootstrap state with who started this server process.
|
|
175
|
-
// parseDashboardStarter defaults to "Standalone" when DASHBOARD_STARTER is unset.
|
|
176
|
-
const starter = parseDashboardStarter(process.env);
|
|
177
|
-
server.bootstrapState.set({ starter });
|
|
178
|
-
console.log(`[bootstrap] starter=${starter}`);
|
|
179
|
-
|
|
180
|
-
let shuttingDown = false;
|
|
181
|
-
const shutdown = async () => {
|
|
182
|
-
if (shuttingDown) {
|
|
183
|
-
console.log("Force exit.");
|
|
184
|
-
process.exit(1);
|
|
185
|
-
}
|
|
186
|
-
shuttingDown = true;
|
|
187
|
-
console.log("\nShutting down...");
|
|
188
|
-
await server.stop();
|
|
189
|
-
process.exit(0);
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
process.on("SIGINT", shutdown);
|
|
193
|
-
process.on("SIGTERM", shutdown);
|
|
194
143
|
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
144
|
+
// Dynamic-import boundary for the main server module. If a top-level
|
|
145
|
+
// dependency (fastify, toad-cache, readable-stream, …) is missing, the
|
|
146
|
+
// import throws ERR_MODULE_NOT_FOUND here — caught and degraded to the
|
|
147
|
+
// recovery HTTP server bound to the same port.
|
|
148
|
+
let createServer: typeof _CreateServerType;
|
|
200
149
|
try {
|
|
201
|
-
await
|
|
150
|
+
({ createServer } = await import("./server.js"));
|
|
202
151
|
} catch (err) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
152
|
+
if (isModuleNotFoundError(err)) {
|
|
153
|
+
await startRecoveryServer({
|
|
154
|
+
port: config.port,
|
|
155
|
+
error: err as Error,
|
|
156
|
+
missingModule: parseModuleNotFoundError(err),
|
|
157
|
+
});
|
|
158
|
+
// startRecoveryServer never returns — its HTTP server keeps the
|
|
159
|
+
// event loop alive until the user clicks Retry (which respawns and
|
|
160
|
+
// process.exits) or the process is killed externally.
|
|
161
|
+
return new Promise<void>(() => { /* unreachable */ });
|
|
162
|
+
}
|
|
163
|
+
throw err;
|
|
206
164
|
}
|
|
207
165
|
|
|
208
|
-
await
|
|
209
|
-
|
|
210
|
-
// Kick off the degraded-mode first-run bootstrap if pi is unresolvable.
|
|
211
|
-
// Runs async — server is already listening, so UI + non-pi endpoints
|
|
212
|
-
// remain fully operational during the ~30s install window.
|
|
213
|
-
// TODO(single-dashboard-per-home): when home-lock wiring lands, wrap
|
|
214
|
-
// this inside the acquired lock to serialize concurrent first-run
|
|
215
|
-
// installs from multiple dashboard invocations on the same HOME.
|
|
216
|
-
runDegradedModeBootstrap(server).catch((err) => {
|
|
217
|
-
console.error("[bootstrap] unexpected failure in bootstrap orchestrator:", err);
|
|
218
|
-
});
|
|
219
|
-
}
|
|
166
|
+
const server = await createServer(config);
|
|
220
167
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const initial = registry.resolve("pi");
|
|
238
|
-
|
|
239
|
-
if (initial.ok) {
|
|
240
|
-
// Default state is "ready" — no change needed. Log once for clarity.
|
|
241
|
-
console.log(`[bootstrap] ready (pi resolved via ${initial.source})`);
|
|
242
|
-
// Populate version-skew compatibility info even when no install was
|
|
243
|
-
// needed — the UI banner renders upgradeRecommended hints.
|
|
244
|
-
try {
|
|
245
|
-
const serverPkg = path.resolve(
|
|
246
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
247
|
-
"..",
|
|
248
|
-
"package.json",
|
|
168
|
+
// Tool-registry resolve confirms pi is reachable from the bundled
|
|
169
|
+
// node_modules/ — under change: eliminate-electron-runtime-install,
|
|
170
|
+
// pi/openspec/tsx ship as regular deps so the registry must resolve
|
|
171
|
+
// at startup. A miss here means the install tree is corrupted.
|
|
172
|
+
{
|
|
173
|
+
const registry = getDefaultRegistry();
|
|
174
|
+
const res = registry.resolve("pi");
|
|
175
|
+
if (res.ok) {
|
|
176
|
+
console.log(`[bootstrap] ready (pi resolved via ${res.source})`);
|
|
177
|
+
} else {
|
|
178
|
+
const tried = res.tried?.map((t: any) => t.strategy).join(", ") ?? "(no strategies)";
|
|
179
|
+
throw new Error(
|
|
180
|
+
`[bootstrap] pi is not resolvable from the dashboard install. ` +
|
|
181
|
+
`This indicates a corrupted node_modules/ tree. Tried: ${tried}. ` +
|
|
182
|
+
`Reinstall the dashboard (npm i -g @blackbelt-technology/pi-agent-dashboard) ` +
|
|
183
|
+
`or reinstall the Electron app.`,
|
|
249
184
|
);
|
|
250
|
-
updateBootstrapCompatibility(server.bootstrapState, serverPkg);
|
|
251
|
-
logCompatibilityWarning(server.bootstrapState);
|
|
252
|
-
} catch (err) {
|
|
253
|
-
console.warn("[bootstrap] version-skew check failed (non-fatal):", err);
|
|
254
185
|
}
|
|
255
|
-
return;
|
|
256
186
|
}
|
|
257
187
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
status: "installing",
|
|
263
|
-
progress: { step: "pi", output: "starting install…" },
|
|
264
|
-
error: undefined,
|
|
265
|
-
});
|
|
266
|
-
|
|
188
|
+
// One-time advisory: legacy `~/.pi-dashboard/` directory left behind
|
|
189
|
+
// from pre-R3 versions. Nothing reads or writes it now — surface a
|
|
190
|
+
// single log line so the user knows it's safe to delete. Doctor UI
|
|
191
|
+
// shows the same advisory more visibly.
|
|
267
192
|
try {
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (!res.ok) {
|
|
278
|
-
console.error(`[bootstrap] failed: ${res.error}`);
|
|
279
|
-
server.bootstrapState.set({
|
|
280
|
-
status: "failed",
|
|
281
|
-
error: { message: res.error },
|
|
282
|
-
progress: undefined,
|
|
283
|
-
});
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Post-install registry rescan + openspec/pi-resources force-refresh
|
|
288
|
-
// are now centralized in server.ts's bootstrapState.subscribe hook,
|
|
289
|
-
// which fires on every installing → ready transition (this caller +
|
|
290
|
-
// triggerUpgradePi + triggerRetry).
|
|
291
|
-
// See change: fix-openspec-buttons-after-bootstrap-install.
|
|
292
|
-
|
|
293
|
-
// Attempt bridge registration. Failures are non-fatal per spec §10.3.
|
|
294
|
-
let bridgeErr: string | undefined;
|
|
295
|
-
try {
|
|
296
|
-
const extPath = findBundledExtension(process.cwd());
|
|
297
|
-
if (extPath) {
|
|
298
|
-
registerBridgeExtension(extPath);
|
|
299
|
-
} else {
|
|
300
|
-
bridgeErr = "bundled extension not found after install";
|
|
301
|
-
}
|
|
302
|
-
} catch (err) {
|
|
303
|
-
bridgeErr = err instanceof Error ? err.message : String(err);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
server.bootstrapState.set({
|
|
307
|
-
status: "ready",
|
|
308
|
-
progress: undefined,
|
|
309
|
-
error: undefined,
|
|
310
|
-
bridgeRegistrationError: bridgeErr,
|
|
311
|
-
});
|
|
312
|
-
// Populate compatibility info after a successful install.
|
|
313
|
-
try {
|
|
314
|
-
const serverPkg = path.resolve(
|
|
315
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
316
|
-
"..",
|
|
317
|
-
"package.json",
|
|
193
|
+
const { detectLegacyManagedDir } = await import(
|
|
194
|
+
"@blackbelt-technology/pi-dashboard-shared/legacy-managed-dir.js"
|
|
195
|
+
);
|
|
196
|
+
const legacy = detectLegacyManagedDir();
|
|
197
|
+
if (legacy.present) {
|
|
198
|
+
console.log(
|
|
199
|
+
`[legacy] legacy install directory detected at ${legacy.path} ` +
|
|
200
|
+
`(${legacy.pkgCount} packages, ~${legacy.sizeMb} MB). No longer used — safe to delete.`,
|
|
318
201
|
);
|
|
319
|
-
updateBootstrapCompatibility(server.bootstrapState, serverPkg);
|
|
320
|
-
logCompatibilityWarning(server.bootstrapState);
|
|
321
|
-
} catch (err) {
|
|
322
|
-
console.warn("[bootstrap] version-skew check failed (non-fatal):", err);
|
|
323
202
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
);
|
|
327
|
-
} catch (err) {
|
|
328
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
329
|
-
console.error(`[bootstrap] failed: ${message}`);
|
|
330
|
-
server.bootstrapState.set({
|
|
331
|
-
status: "failed",
|
|
332
|
-
error: { message },
|
|
333
|
-
progress: undefined,
|
|
334
|
-
});
|
|
203
|
+
} catch {
|
|
204
|
+
/* advisory only — never block startup */
|
|
335
205
|
}
|
|
206
|
+
|
|
207
|
+
await server.start();
|
|
336
208
|
}
|
|
337
209
|
|
|
210
|
+
|
|
338
211
|
/**
|
|
339
212
|
* Start the server as a detached background daemon.
|
|
340
213
|
*/
|
|
@@ -376,7 +249,6 @@ async function cmdStart(config: ServerConfig): Promise<void> {
|
|
|
376
249
|
starter: "Standalone",
|
|
377
250
|
healthTimeoutMs: 30_000,
|
|
378
251
|
port: config.port,
|
|
379
|
-
env: { ...process.env },
|
|
380
252
|
});
|
|
381
253
|
const reportedPid = result.reportedPid ?? readPid() ?? result.childPid;
|
|
382
254
|
console.log(`Dashboard server started (pid ${reportedPid}) at http://localhost:${config.port}`);
|
|
@@ -523,57 +395,6 @@ async function cmdRestartImpl(
|
|
|
523
395
|
/**
|
|
524
396
|
* Show server status.
|
|
525
397
|
*/
|
|
526
|
-
/**
|
|
527
|
-
* `pi-dashboard upgrade-pi` — upgrade pi-coding-agent via bootstrap.
|
|
528
|
-
*
|
|
529
|
-
* If a dashboard is currently running, POST to /api/bootstrap/upgrade-pi
|
|
530
|
-
* (so the running server owns the install, broadcasts state, and reloads
|
|
531
|
-
* connected sessions). Otherwise run `bootstrapInstall` directly with a
|
|
532
|
-
* streaming progress formatter and exit when done.
|
|
533
|
-
*
|
|
534
|
-
* See change: unified-bootstrap-install §8.
|
|
535
|
-
*/
|
|
536
|
-
async function cmdUpgradePi(config: ServerConfig): Promise<void> {
|
|
537
|
-
const status = await isDashboardRunning(config.port);
|
|
538
|
-
if (status.running) {
|
|
539
|
-
console.log(
|
|
540
|
-
`[upgrade-pi] dashboard running at http://localhost:${config.port}, delegating to server`,
|
|
541
|
-
);
|
|
542
|
-
try {
|
|
543
|
-
const res = await fetch(`http://localhost:${config.port}/api/bootstrap/upgrade-pi`, {
|
|
544
|
-
method: "POST",
|
|
545
|
-
});
|
|
546
|
-
if (!res.ok) {
|
|
547
|
-
const body = await res.text();
|
|
548
|
-
console.error(`[upgrade-pi] server rejected upgrade: HTTP ${res.status} ${body}`);
|
|
549
|
-
process.exit(1);
|
|
550
|
-
}
|
|
551
|
-
const body = (await res.json()) as { ticketId?: string };
|
|
552
|
-
console.log(`[upgrade-pi] queued (ticketId=${body.ticketId ?? "?"})`);
|
|
553
|
-
console.log("[upgrade-pi] progress is streamed to open dashboard tabs; CLI exits now.");
|
|
554
|
-
return;
|
|
555
|
-
} catch (err) {
|
|
556
|
-
console.error("[upgrade-pi] failed to reach server:", err);
|
|
557
|
-
process.exit(1);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
console.log("[upgrade-pi] no dashboard running — installing directly");
|
|
562
|
-
const res = await bootstrapInstall({
|
|
563
|
-
packages: ["@earendil-works/pi-coding-agent"],
|
|
564
|
-
progress: (p) => {
|
|
565
|
-
const line = p.output
|
|
566
|
-
? `[upgrade-pi] ${p.step} ${p.status}: ${p.output}`
|
|
567
|
-
: `[upgrade-pi] ${p.step} ${p.status}`;
|
|
568
|
-
console.log(line);
|
|
569
|
-
},
|
|
570
|
-
});
|
|
571
|
-
if (!res.ok) {
|
|
572
|
-
console.error(`[upgrade-pi] failed: ${res.error}`);
|
|
573
|
-
process.exit(1);
|
|
574
|
-
}
|
|
575
|
-
console.log(`[upgrade-pi] ✓ installed ${res.installed.join(", ")}`);
|
|
576
|
-
}
|
|
577
398
|
|
|
578
399
|
async function cmdStatus(port: number): Promise<void> {
|
|
579
400
|
// 1. Try mDNS discovery first
|
|
@@ -665,9 +486,6 @@ async function main() {
|
|
|
665
486
|
case "status":
|
|
666
487
|
await cmdStatus(config.port);
|
|
667
488
|
break;
|
|
668
|
-
case "upgrade-pi":
|
|
669
|
-
await cmdUpgradePi(config);
|
|
670
|
-
break;
|
|
671
489
|
default:
|
|
672
490
|
// No subcommand — run in foreground (backward compatible)
|
|
673
491
|
await runForeground(config);
|
|
@@ -2,16 +2,12 @@
|
|
|
2
2
|
* Extract session status/tool updates from forwarded events.
|
|
3
3
|
* Returns partial DashboardSession updates, or null if the event is not relevant.
|
|
4
4
|
*/
|
|
5
|
-
import type { DashboardEvent, DashboardSession,
|
|
5
|
+
import type { DashboardEvent, DashboardSession, SessionStatus } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
6
6
|
|
|
7
7
|
// Use null (not undefined) for fields that must be cleared — undefined is
|
|
8
8
|
// dropped during JSON serialisation so the browser would keep the stale value.
|
|
9
9
|
type SessionUpdates = Partial<Pick<DashboardSession, "status" | "model" | "thinkingLevel">> & {
|
|
10
10
|
currentTool?: string | null;
|
|
11
|
-
activeFlowName?: string | null;
|
|
12
|
-
flowAgentsDone?: number;
|
|
13
|
-
flowAgentsTotal?: number;
|
|
14
|
-
flowStatus?: FlowStatus | null;
|
|
15
11
|
};
|
|
16
12
|
|
|
17
13
|
/**
|
|
@@ -83,54 +79,10 @@ export function extractSessionUpdates(event: DashboardEvent): SessionUpdates | n
|
|
|
83
79
|
return null;
|
|
84
80
|
}
|
|
85
81
|
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const agentCount = steps?.filter(s => s.stepType === "agent").length ?? 0;
|
|
91
|
-
return {
|
|
92
|
-
activeFlowName: (d.flowName as string) ?? null,
|
|
93
|
-
flowAgentsTotal: agentCount,
|
|
94
|
-
flowAgentsDone: 0,
|
|
95
|
-
flowStatus: "running" as FlowStatus,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
case "flow_agent_complete":
|
|
100
|
-
// Increment is handled by the caller — we return a marker
|
|
101
|
-
return { flowAgentsDone: -1 }; // sentinel: caller must increment
|
|
102
|
-
|
|
103
|
-
case "flow_complete": {
|
|
104
|
-
const result = event.data;
|
|
105
|
-
const status = (result.status as string) ?? "success";
|
|
106
|
-
return {
|
|
107
|
-
flowStatus: status as FlowStatus,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ── Architect events ──
|
|
112
|
-
case "architect_started": {
|
|
113
|
-
const mode = (event.data.mode as string) || "new";
|
|
114
|
-
return {
|
|
115
|
-
activeFlowName: mode === "edit" ? "Editing flow..." : "Designing flow...",
|
|
116
|
-
flowStatus: "running" as FlowStatus,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
case "flow_summary_dismissed": {
|
|
121
|
-
return {
|
|
122
|
-
activeFlowName: null,
|
|
123
|
-
flowStatus: null,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
case "architect_complete":
|
|
128
|
-
case "architect_cancelled": {
|
|
129
|
-
return {
|
|
130
|
-
activeFlowName: null,
|
|
131
|
-
flowStatus: null,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
82
|
+
// Flow / architect events are NOT extracted here. Per change
|
|
83
|
+
// pluginize-flows-via-registry, flows-plugin owns its own state
|
|
84
|
+
// derivation in the browser via useSessionEvents + plugin-internal
|
|
85
|
+
// contexts. The dashboard server has zero flow knowledge.
|
|
134
86
|
|
|
135
87
|
default:
|
|
136
88
|
return null;
|
|
@@ -164,16 +116,16 @@ const ACTIVITY_EVENT_TYPES: ReadonlySet<string> = new Set([
|
|
|
164
116
|
"agent_end",
|
|
165
117
|
// Bash command output
|
|
166
118
|
"bash_output",
|
|
167
|
-
// Flow lifecycle / agent steps
|
|
168
|
-
"flow_started",
|
|
169
|
-
"flow_complete",
|
|
170
|
-
"flow_agent_started",
|
|
171
|
-
"flow_agent_complete",
|
|
172
|
-
// Architect (flow design) lifecycle
|
|
173
|
-
"architect_started",
|
|
174
|
-
"architect_complete",
|
|
175
|
-
"architect_cancelled",
|
|
176
119
|
]);
|
|
120
|
+
// Note: flow / architect events used to live in this allowlist but the
|
|
121
|
+
// classification of "is this user-visible activity?" is plugin business.
|
|
122
|
+
// The plugin marks activity via its own session-state-derived signal
|
|
123
|
+
// (e.g. lastActivityAt stamping based on flowState changes). For now,
|
|
124
|
+
// the simpler tool/agent/message events are sufficient to keep
|
|
125
|
+
// `lastActivityAt` accurate; if a flow that does no other tool calls
|
|
126
|
+
// fails to bump activity, the user can re-add the events here behind a
|
|
127
|
+
// generic predicate like `isPluginActivityEvent` exposed by plugin-runtime.
|
|
128
|
+
// See change: pluginize-flows-via-registry.
|
|
177
129
|
|
|
178
130
|
export function isActivityEvent(eventType: string): boolean {
|
|
179
131
|
return ACTIVITY_EVENT_TYPES.has(eventType);
|
|
@@ -138,16 +138,15 @@ export function wireEvents(deps: EventWiringDeps): void {
|
|
|
138
138
|
|
|
139
139
|
piGateway.onEvent = (sessionId, msg) => {
|
|
140
140
|
if (msg.type === "event_forward") {
|
|
141
|
+
// Legacy queue_state event no longer emitted (bridge removed PromptQueue).
|
|
142
|
+
// See change: add-followup-edit-and-steer-cancel.
|
|
143
|
+
if (msg.event.eventType === "queue_state") return;
|
|
141
144
|
// When canSkipWipe was true, the event store already has all events —
|
|
142
145
|
// don't insert replayed events again (would cause exponential duplication)
|
|
143
146
|
if (replayingSessions.has(sessionId) && skipReplayInsert.has(sessionId)) {
|
|
144
147
|
// Still process status updates so session state stays accurate
|
|
145
148
|
const updates = extractSessionUpdates(msg.event);
|
|
146
149
|
if (updates) {
|
|
147
|
-
if (updates.flowAgentsDone === -1) {
|
|
148
|
-
const session = sessionManager.get(sessionId);
|
|
149
|
-
updates.flowAgentsDone = (session?.flowAgentsDone ?? 0) + 1;
|
|
150
|
-
}
|
|
151
150
|
sessionManager.update(sessionId, updates as Partial<DashboardSession>);
|
|
152
151
|
}
|
|
153
152
|
// Skip insert + broadcast — events are already in store
|
|
@@ -173,10 +172,6 @@ export function wireEvents(deps: EventWiringDeps): void {
|
|
|
173
172
|
|
|
174
173
|
const updates = extractSessionUpdates(msg.event);
|
|
175
174
|
if (updates) {
|
|
176
|
-
if (updates.flowAgentsDone === -1) {
|
|
177
|
-
const session = sessionManager.get(sessionId);
|
|
178
|
-
updates.flowAgentsDone = (session?.flowAgentsDone ?? 0) + 1;
|
|
179
|
-
}
|
|
180
175
|
sessionManager.update(sessionId, updates as Partial<DashboardSession>);
|
|
181
176
|
// During replay, accumulate in sessionManager but don't broadcast
|
|
182
177
|
// to avoid rapid status flickers on the session card
|
|
@@ -573,11 +568,29 @@ export function wireEvents(deps: EventWiringDeps): void {
|
|
|
573
568
|
text: pendingResume.text,
|
|
574
569
|
images: pendingResume.images,
|
|
575
570
|
});
|
|
576
|
-
|
|
577
|
-
|
|
571
|
+
// Clear `resuming` on the OLD session that triggered the auto-resume,
|
|
572
|
+
// not the new session that just registered. The new session never had
|
|
573
|
+
// `resuming: true`; clearing it there was a no-op and left the old
|
|
574
|
+
// session permanently stuck. The 30s onTimeout was also cancelled by
|
|
575
|
+
// `consume()`, so without this fix the old session stays frozen forever.
|
|
576
|
+
sessionManager.update(pendingResume.oldSessionId, { resuming: false });
|
|
577
|
+
browserGateway.broadcastSessionUpdated(pendingResume.oldSessionId, { resuming: false });
|
|
578
578
|
}
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
+
// Pi's queue mirror (steer + follow-up) forwarded from the bridge.
|
|
582
|
+
// Caches `pendingQueues` on the session and broadcasts to subscribed browsers.
|
|
583
|
+
// See change: add-followup-edit-and-steer-cancel.
|
|
584
|
+
if (msg.type === "queue_update") {
|
|
585
|
+
const steering = Array.isArray(msg.steering) ? msg.steering : [];
|
|
586
|
+
const followUp = Array.isArray(msg.followUp) ? msg.followUp : [];
|
|
587
|
+
const update = { pendingQueues: { steering, followUp } } as Partial<DashboardSession>;
|
|
588
|
+
sessionManager.update(sessionId, update);
|
|
589
|
+
if (!replayingSessions.has(sessionId)) {
|
|
590
|
+
browserGateway.broadcastSessionUpdated(sessionId, update);
|
|
591
|
+
}
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
581
594
|
if (msg.type === "first_message_update") {
|
|
582
595
|
sessionManager.update(sessionId, { firstMessage: msg.firstMessage });
|
|
583
596
|
browserGateway.broadcastSessionUpdated(sessionId, { firstMessage: msg.firstMessage });
|
|
@@ -106,6 +106,10 @@ export function createMemorySessionManager(): SessionManager {
|
|
|
106
106
|
firstMessage: params.firstMessage ?? existing?.firstMessage,
|
|
107
107
|
dataUnavailable: false,
|
|
108
108
|
pid: params.pid,
|
|
109
|
+
// Pi-native queue mirror: reset to empty on register / re-register;
|
|
110
|
+
// a fresh `queue_update` from the bridge populates it.
|
|
111
|
+
// See change: add-followup-edit-and-steer-cancel.
|
|
112
|
+
pendingQueues: { steering: [], followUp: [] },
|
|
109
113
|
};
|
|
110
114
|
sessions.set(params.id, session);
|
|
111
115
|
mgr.onChange?.(params.id, {
|
|
@@ -92,7 +92,7 @@ export function _resetDynamicPiAliases(): void {
|
|
|
92
92
|
* tooling if and only if its name is in `CORE_PACKAGE_NAMES` OR a
|
|
93
93
|
* pi.dev-declared alias. The previous `pi-*` name-prefix heuristic was
|
|
94
94
|
* removed because it caused recommended-extension packages (e.g.
|
|
95
|
-
* `pi-agent-browser`,
|
|
95
|
+
* `pi-agent-browser`, `pi-dashboard-subagents`) to appear in BOTH the
|
|
96
96
|
* Core ecosystem panel and the Installed Packages panel. Recommended
|
|
97
97
|
* extensions are now surfaced exclusively through
|
|
98
98
|
* `/api/packages/installed`. See change: consolidate-packages-settings-ui.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* pi.dev version-check client. Mirrors the implementation pi itself
|
|
3
|
-
* uses for self-update checks (see `@
|
|
3
|
+
* uses for self-update checks (see `@earendil-works/pi-coding-agent/dist/
|
|
4
4
|
* utils/version-check.js`). Returns `{ version, packageName? }` so the
|
|
5
5
|
* dashboard can:
|
|
6
6
|
* 1. Detect the genuinely-newest pi without npm-registry lag.
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
import fs from "node:fs";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { createRequire } from "node:module";
|
|
14
|
-
import type { BootstrapCompatibility, BootstrapStateStore } from "./bootstrap-state.js";
|
|
15
14
|
import { getDefaultRegistry, type ToolRegistry } from "@blackbelt-technology/pi-dashboard-shared/tool-registry/index.js";
|
|
16
15
|
|
|
17
16
|
/**
|
|
@@ -62,6 +61,30 @@ export function isAbove(version: string, threshold: string): boolean {
|
|
|
62
61
|
return compareVersions(version, thresholdClean) > 0;
|
|
63
62
|
}
|
|
64
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Pi version compatibility snapshot.
|
|
66
|
+
*
|
|
67
|
+
* Previously declared in `./bootstrap-state.js`; moved here under change:
|
|
68
|
+
* eliminate-electron-runtime-install (task 3.6) once the bootstrap-state
|
|
69
|
+
* store was removed. The shape stays stable so consumers (CLI version
|
|
70
|
+
* skew log, future UI version-skew banner for standalone arm) keep
|
|
71
|
+
* compiling.
|
|
72
|
+
*/
|
|
73
|
+
export interface BootstrapCompatibility {
|
|
74
|
+
/** Minimum pi version supported by this dashboard server. */
|
|
75
|
+
minimum: string;
|
|
76
|
+
/** Recommended pi version; below = soft warning, above = OK. */
|
|
77
|
+
recommended: string;
|
|
78
|
+
/** Maximum supported pi version, or `null` for unbounded. */
|
|
79
|
+
maximum: string | null;
|
|
80
|
+
/** Currently-resolved pi version (or `undefined` if pi unresolvable). */
|
|
81
|
+
current?: string;
|
|
82
|
+
/** Set when `current < recommended`. */
|
|
83
|
+
upgradeRecommended?: boolean;
|
|
84
|
+
/** Set when `current > maximum`. */
|
|
85
|
+
upgradeDashboard?: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
65
88
|
/**
|
|
66
89
|
* Read the server's declared compatibility range from its own package.json.
|
|
67
90
|
* Falls back to the hard-coded defaults when the field is missing or
|
|
@@ -168,48 +191,3 @@ export function computeCompatibility(
|
|
|
168
191
|
return out;
|
|
169
192
|
}
|
|
170
193
|
|
|
171
|
-
interface CacheEntry {
|
|
172
|
-
value: BootstrapCompatibility;
|
|
173
|
-
/** Milliseconds epoch when this entry should be discarded. */
|
|
174
|
-
expiresAt: number;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
let cached: CacheEntry | undefined;
|
|
178
|
-
const CACHE_TTL_MS = 60_000;
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Convenience wrapper: read range + current version, compute result,
|
|
182
|
-
* cache for 60 s. `store` is called with a structured compatibility
|
|
183
|
-
* update and (when minimum is violated) a blocking `error` message.
|
|
184
|
-
*/
|
|
185
|
-
export function updateBootstrapCompatibility(
|
|
186
|
-
store: BootstrapStateStore,
|
|
187
|
-
serverPkgJsonPath: string,
|
|
188
|
-
registry: ToolRegistry = getDefaultRegistry(),
|
|
189
|
-
now: () => number = Date.now,
|
|
190
|
-
): BootstrapCompatibility {
|
|
191
|
-
const t = now();
|
|
192
|
-
if (cached && t < cached.expiresAt) {
|
|
193
|
-
store.set({ compatibility: cached.value });
|
|
194
|
-
return cached.value;
|
|
195
|
-
}
|
|
196
|
-
const range = readPiCompatibility(serverPkgJsonPath);
|
|
197
|
-
const current = readCurrentPiVersion(registry);
|
|
198
|
-
const computed = computeCompatibility(range, current);
|
|
199
|
-
cached = { value: computed, expiresAt: t + CACHE_TTL_MS };
|
|
200
|
-
store.set({ compatibility: computed });
|
|
201
|
-
// Minimum-violated → block pi-dependent ops by setting `error`.
|
|
202
|
-
if (current && isBelow(current, range.minimum)) {
|
|
203
|
-
store.set({
|
|
204
|
-
error: {
|
|
205
|
-
message: `pi version ${current} is below minimum ${range.minimum}. Please run \`pi-dashboard upgrade-pi\`.`,
|
|
206
|
-
},
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
return computed;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/** Test helper: clear the 60-second cache between runs. */
|
|
213
|
-
export function _resetVersionSkewCache(): void {
|
|
214
|
-
cached = undefined;
|
|
215
|
-
}
|