@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
|
@@ -14,6 +14,7 @@ import { createMemoryEventStore, type EventStore } from "./memory-event-store.js
|
|
|
14
14
|
import { createMemorySessionManager, type SessionManager } from "./memory-session-manager.js";
|
|
15
15
|
import { createPiGateway, type PiGateway } from "./pi-gateway.js";
|
|
16
16
|
import { createBrowserGateway, type BrowserGateway } from "./browser-gateway.js";
|
|
17
|
+
import { pluginIntentCache } from "./plugin-intent-cache.js";
|
|
17
18
|
import { createPreferencesStore, type PreferencesStore } from "./preferences-store.js";
|
|
18
19
|
import { createMetaPersistence, type MetaPersistence } from "./meta-persistence.js";
|
|
19
20
|
import { createSessionOrderManager, type SessionOrderManager } from "./session-order-manager.js";
|
|
@@ -24,7 +25,7 @@ import { createPendingResumeIntentRegistry } from "./pending-resume-intent-regis
|
|
|
24
25
|
import { applyReattachPolicy } from "./reattach-placement.js";
|
|
25
26
|
|
|
26
27
|
// pending-load-manager removed — server loads sessions directly via DirectoryService
|
|
27
|
-
import { createDirectoryService,
|
|
28
|
+
import { createDirectoryService, type DirectoryService } from "./directory-service.js";
|
|
28
29
|
import { createTerminalManager, type TerminalManager } from "./terminal-manager.js";
|
|
29
30
|
import { createTerminalGateway, type TerminalGateway } from "./terminal-gateway.js";
|
|
30
31
|
import { writePid, removePid } from "./server-pid.js";
|
|
@@ -42,6 +43,7 @@ import { createNetworkGuard, isLoopback, isBypassedHost } from "./localhost-guar
|
|
|
42
43
|
import type { AuthConfig } from "@blackbelt-technology/pi-dashboard-shared/config.js";
|
|
43
44
|
import { loadConfig, CONFIG_FILE } from "@blackbelt-technology/pi-dashboard-shared/config.js";
|
|
44
45
|
import { registerSessionApi } from "./session-api.js";
|
|
46
|
+
import { registerManifestRoute } from "./routes/manifest-route.js";
|
|
45
47
|
import { registerSessionRoutes } from "./routes/session-routes.js";
|
|
46
48
|
import { registerGitRoutes } from "./routes/git-routes.js";
|
|
47
49
|
import { registerFileRoutes } from "./routes/file-routes.js";
|
|
@@ -59,11 +61,6 @@ import { PiCoreChecker } from "./pi-core-checker.js";
|
|
|
59
61
|
import { PiCoreUpdater } from "./pi-core-updater.js";
|
|
60
62
|
import { registerToolRoutes } from "./routes/tool-routes.js";
|
|
61
63
|
import { registerJjRoutes } from "./routes/jj-routes.js";
|
|
62
|
-
import { registerBootstrapRoutes } from "./routes/bootstrap-routes.js";
|
|
63
|
-
import { createBootstrapState, type BootstrapStateStore } from "./bootstrap-state.js";
|
|
64
|
-
import { detectLegacyPiInstalls } from "./legacy-pi-cleanup.js";
|
|
65
|
-
import { createBootstrapQueue } from "./bootstrap-queue.js";
|
|
66
|
-
import { bootstrapInstall } from "@blackbelt-technology/pi-dashboard-shared/bootstrap-install.js";
|
|
67
64
|
import { getDefaultRegistry } from "@blackbelt-technology/pi-dashboard-shared/tool-registry/index.js";
|
|
68
65
|
import { registerProviderRoutes } from "./routes/provider-routes.js";
|
|
69
66
|
import { PackageManagerWrapper } from "./package-manager-wrapper.js";
|
|
@@ -72,16 +69,20 @@ import { createEditorPidRegistry } from "./editor-pid-registry.js";
|
|
|
72
69
|
import { registerEditorRoutes } from "./routes/editor-routes.js";
|
|
73
70
|
import { registerKnownServersRoutes } from "./routes/known-servers-routes.js";
|
|
74
71
|
import { registerPluginConfigRoutes } from "./routes/plugin-config-routes.js";
|
|
72
|
+
import { registerPluginActivationRoutes } from "./routes/plugin-activation-routes.js";
|
|
75
73
|
import { createModelProxyAuthGate } from "./model-proxy/auth-gate.js";
|
|
76
74
|
import { registerModelProxyRoutes } from "./routes/model-proxy-routes.js";
|
|
77
75
|
import { registerModelProxyApiKeyRoutes } from "./routes/model-proxy-api-key-routes.js";
|
|
78
76
|
import { registerModelProxyRefreshRoutes } from "./routes/model-proxy-refresh-routes.js";
|
|
79
77
|
import { getModelRegistry, getStreamSimpleFn } from "./model-proxy/registry-singleton.js";
|
|
80
78
|
import { writeConfigPartial } from "./config-api.js";
|
|
81
|
-
import { loadServerEntries, discoverPlugins, getPluginStatusStore } from "@blackbelt-technology/dashboard-plugin-runtime/server";
|
|
79
|
+
import { loadServerEntries, discoverPlugins, getPluginStatusStore, refreshRequirementProbesFor } from "@blackbelt-technology/dashboard-plugin-runtime/server";
|
|
82
80
|
import { createServerPluginContext } from "@blackbelt-technology/dashboard-plugin-runtime/server";
|
|
83
81
|
import { getPluginConfig as getPluginConfigFromFile } from "@blackbelt-technology/pi-dashboard-shared/config.js";
|
|
84
|
-
import {
|
|
82
|
+
import {
|
|
83
|
+
registerAllPluginBridges,
|
|
84
|
+
reconcilePluginBridgePackages,
|
|
85
|
+
} from "@blackbelt-technology/pi-dashboard-shared/plugin-bridge-register.js";
|
|
85
86
|
import { registerEditorProxy, handleEditorUpgrade } from "./editor-proxy.js";
|
|
86
87
|
import { detectCodeServerBinary } from "./editor-detection.js";
|
|
87
88
|
|
|
@@ -126,138 +127,12 @@ export interface DashboardServer {
|
|
|
126
127
|
sessionManager: SessionManager;
|
|
127
128
|
eventStore: EventStore;
|
|
128
129
|
browserGateway: BrowserGateway;
|
|
129
|
-
/**
|
|
130
|
-
* Bootstrap state store. Exposed so the CLI can flip status during
|
|
131
|
-
* degraded-mode first-run (`pi-dashboard` without pi installed) and
|
|
132
|
-
* so the REST handler for `/api/bootstrap/upgrade-pi` can orchestrate
|
|
133
|
-
* async installs without reaching back through closures.
|
|
134
|
-
* See change: unified-bootstrap-install.
|
|
135
|
-
*/
|
|
136
|
-
bootstrapState: BootstrapStateStore;
|
|
137
130
|
/** Resolved HTTP port after start() (useful for port:0 in tests). Returns null if not listening. */
|
|
138
131
|
httpPort(): number | null;
|
|
139
132
|
/** Resolved pi gateway port after start(). Returns null if not listening. */
|
|
140
133
|
piPort(): number | null;
|
|
141
134
|
}
|
|
142
135
|
|
|
143
|
-
// ── Post-install repair (centralized hook) ─────────────────────────
|
|
144
|
-
// On every `installing → ready` bootstrap-state transition the server
|
|
145
|
-
// re-runs the full ToolRegistry rescan, force-refreshes OpenSpec data
|
|
146
|
-
// for every known directory, and refreshes pi-resources. Without this
|
|
147
|
-
// the OpenSpec session-card buttons stay hidden until the next gated
|
|
148
|
-
// poll tick (or never, if the gate's mtime heuristic declines).
|
|
149
|
-
// See change: fix-openspec-buttons-after-bootstrap-install.
|
|
150
|
-
|
|
151
|
-
import type { OpenSpecData } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
152
|
-
import type { ServerToBrowserMessage } from "@blackbelt-technology/pi-dashboard-shared/browser-protocol.js";
|
|
153
|
-
|
|
154
|
-
export interface PostInstallRepairDeps {
|
|
155
|
-
registry: { rescan(name?: string): void };
|
|
156
|
-
directoryService: {
|
|
157
|
-
knownDirectories(): string[];
|
|
158
|
-
getOpenSpecData(cwd: string): OpenSpecData | undefined;
|
|
159
|
-
refreshOpenSpec(cwd: string): Promise<OpenSpecData>;
|
|
160
|
-
refreshPiResources(cwd: string): Promise<unknown>;
|
|
161
|
-
};
|
|
162
|
-
browserGateway: { broadcastToAll(msg: ServerToBrowserMessage): void };
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Centralized post-install repair work fired on every `installing → ready`
|
|
168
|
-
* bootstrap-state transition. Idempotent. Failures per-cwd are isolated.
|
|
169
|
-
*
|
|
170
|
-
* Steps:
|
|
171
|
-
* 1) `registry.rescan()` (no arg — full registry invalidate). Restores
|
|
172
|
-
* the literal contract from `unified-bootstrap-install` task 4.3.
|
|
173
|
-
* 2) For every `directoryService.knownDirectories()` cwd, force-refresh
|
|
174
|
-
* OpenSpec (bypassing the mtime gate). If the prior cache was empty
|
|
175
|
-
* or the refreshed payload differs, broadcast `openspec_update`.
|
|
176
|
-
* 3) For every cwd, force-refresh pi-resources (silent on failure).
|
|
177
|
-
*
|
|
178
|
-
* The DEBUG=pi-dashboard|openspec-poll envvar enables a single-line
|
|
179
|
-
* diagnostic log on completion, matching the existing daemon-log style.
|
|
180
|
-
*/
|
|
181
|
-
export async function runPostInstallRepair(deps: PostInstallRepairDeps): Promise<void> {
|
|
182
|
-
const debug =
|
|
183
|
-
typeof process !== "undefined" &&
|
|
184
|
-
typeof process.env?.DEBUG === "string" &&
|
|
185
|
-
/pi-dashboard|openspec-poll/.test(process.env.DEBUG);
|
|
186
|
-
|
|
187
|
-
// 1) full registry rescan
|
|
188
|
-
deps.registry.rescan();
|
|
189
|
-
if (debug) console.log("[bootstrap] post-install: rescanned tool registry");
|
|
190
|
-
|
|
191
|
-
const cwds = deps.directoryService.knownDirectories();
|
|
192
|
-
|
|
193
|
-
// 2) per-cwd OpenSpec force-refresh + selective broadcast
|
|
194
|
-
await Promise.all(
|
|
195
|
-
cwds.map(async (cwd) => {
|
|
196
|
-
try {
|
|
197
|
-
const prior = deps.directoryService.getOpenSpecData(cwd);
|
|
198
|
-
const fresh = await deps.directoryService.refreshOpenSpec(cwd);
|
|
199
|
-
const priorEmpty = isOpenSpecDataEmpty(prior);
|
|
200
|
-
const dataDiffers = JSON.stringify(prior) !== JSON.stringify(fresh);
|
|
201
|
-
if (priorEmpty || dataDiffers) {
|
|
202
|
-
deps.browserGateway.broadcastToAll({ type: "openspec_update", cwd, data: fresh });
|
|
203
|
-
}
|
|
204
|
-
} catch (err) {
|
|
205
|
-
console.error(
|
|
206
|
-
`[bootstrap] post-install openspec refresh failed for ${cwd}:`,
|
|
207
|
-
err,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}),
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
// 3) per-cwd pi-resources force-refresh (silent fail)
|
|
214
|
-
await Promise.all(
|
|
215
|
-
cwds.map(async (cwd) => {
|
|
216
|
-
try {
|
|
217
|
-
await deps.directoryService.refreshPiResources(cwd);
|
|
218
|
-
} catch {
|
|
219
|
-
// matches existing pattern in directory-service.ts::schedulePiResourcesTick
|
|
220
|
-
}
|
|
221
|
-
}),
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
if (debug) console.log("[bootstrap] post-install: openspec + pi-resources refresh complete");
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export interface BootstrapTransitionHandlerDeps {
|
|
228
|
-
/** Invoked once per `installing → ready` transition, fire-and-forget. */
|
|
229
|
-
onTransitionToReady: () => Promise<void> | void;
|
|
230
|
-
/** Existing queue flush invoked on the same transition. */
|
|
231
|
-
flushQueue: () => Promise<void> | void;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Returns a stateful handler that drives `onTransitionToReady` and
|
|
236
|
-
* `flushQueue` once per `installing → ready` (or `failed → ready`)
|
|
237
|
-
* transition. The handler ignores the very first ready snapshot
|
|
238
|
-
* because the bootstrap state defaults to ready.
|
|
239
|
-
*
|
|
240
|
-
* Both callbacks run fire-and-forget so the subscribe callback returns
|
|
241
|
-
* synchronously — matches the existing `void bootstrapQueue.flushAll()`
|
|
242
|
-
* pattern in the inline subscribe call site.
|
|
243
|
-
*/
|
|
244
|
-
export function makeBootstrapTransitionHandler(
|
|
245
|
-
deps: BootstrapTransitionHandlerDeps,
|
|
246
|
-
): (snapshot: { status: "ready" | "installing" | "failed" }) => void {
|
|
247
|
-
let last: "ready" | "installing" | "failed" = "ready";
|
|
248
|
-
return (snapshot) => {
|
|
249
|
-
if (last !== "ready" && snapshot.status === "ready") {
|
|
250
|
-
void Promise.resolve(deps.flushQueue()).catch((err) => {
|
|
251
|
-
console.error("[bootstrap] flushQueue failed:", err);
|
|
252
|
-
});
|
|
253
|
-
void Promise.resolve(deps.onTransitionToReady()).catch((err) => {
|
|
254
|
-
console.error("[bootstrap] post-install repair failed:", err);
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
last = snapshot.status;
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
136
|
export async function createServer(config: ServerConfig): Promise<DashboardServer> {
|
|
262
137
|
// Ensure bridge extension is registered in pi's global settings
|
|
263
138
|
// (needed for bundled installs where pi can't discover it from package.json)
|
|
@@ -689,48 +564,6 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
689
564
|
fastify.get("/auth/status", async () => ({ authenticated: true, authEnabled: false }));
|
|
690
565
|
}
|
|
691
566
|
|
|
692
|
-
// ── Bootstrap state + queue ──────────────────────────────────────
|
|
693
|
-
// Declared here (before session-api registration) so the session
|
|
694
|
-
// routes can gate spawn operations on bootstrap status.
|
|
695
|
-
// See change: unified-bootstrap-install.
|
|
696
|
-
const bootstrapState = createBootstrapState();
|
|
697
|
-
// Scan for legacy `@mariozechner/pi-coding-agent` installs so the UI can
|
|
698
|
-
// offer a one-click cleanup. See: legacy-pi-cleanup.ts. Best-effort —
|
|
699
|
-
// detection failure is non-fatal (returns []).
|
|
700
|
-
try {
|
|
701
|
-
bootstrapState.set({ legacyPiInstalls: detectLegacyPiInstalls() });
|
|
702
|
-
} catch (err: any) {
|
|
703
|
-
console.warn("[legacy-pi] detection failed:", err?.message ?? err);
|
|
704
|
-
}
|
|
705
|
-
const bootstrapQueue = createBootstrapQueue();
|
|
706
|
-
// Centralized post-install repair: full ToolRegistry rescan +
|
|
707
|
-
// OpenSpec / pi-resources force-refresh on every `installing → ready`
|
|
708
|
-
// transition. See change: fix-openspec-buttons-after-bootstrap-install.
|
|
709
|
-
const handleBootstrapTransition = makeBootstrapTransitionHandler({
|
|
710
|
-
flushQueue: () => bootstrapQueue.flushAll(),
|
|
711
|
-
onTransitionToReady: () =>
|
|
712
|
-
runPostInstallRepair({
|
|
713
|
-
registry: getDefaultRegistry(),
|
|
714
|
-
directoryService,
|
|
715
|
-
browserGateway,
|
|
716
|
-
}),
|
|
717
|
-
});
|
|
718
|
-
const unsubscribeBootstrap = bootstrapState.subscribe((snapshot) => {
|
|
719
|
-
browserGateway.broadcastToAll({
|
|
720
|
-
type: "bootstrap_status_update",
|
|
721
|
-
state: snapshot,
|
|
722
|
-
});
|
|
723
|
-
handleBootstrapTransition(snapshot);
|
|
724
|
-
});
|
|
725
|
-
const unsubscribeQueueComplete = bootstrapQueue.onTicketComplete((evt) => {
|
|
726
|
-
browserGateway.broadcastToAll({
|
|
727
|
-
type: "bootstrap_ticket_complete",
|
|
728
|
-
ticketId: evt.ticketId,
|
|
729
|
-
success: evt.success,
|
|
730
|
-
error: evt.error,
|
|
731
|
-
});
|
|
732
|
-
});
|
|
733
|
-
|
|
734
567
|
// Session control REST API (wraps WebSocket-only operations)
|
|
735
568
|
registerSessionApi(fastify, {
|
|
736
569
|
sessionManager,
|
|
@@ -738,8 +571,6 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
738
571
|
browserGateway,
|
|
739
572
|
pendingForkRegistry,
|
|
740
573
|
pendingDashboardSpawns,
|
|
741
|
-
bootstrapState,
|
|
742
|
-
bootstrapQueue,
|
|
743
574
|
pendingResumeIntents,
|
|
744
575
|
pendingAttachRegistry,
|
|
745
576
|
});
|
|
@@ -756,7 +587,6 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
756
587
|
preferencesStore,
|
|
757
588
|
directoryService,
|
|
758
589
|
networkGuard,
|
|
759
|
-
bootstrapState,
|
|
760
590
|
onOpenSpecChanged: (cwd) => {
|
|
761
591
|
const data = directoryService.getOpenSpecData(cwd);
|
|
762
592
|
if (data) browserGateway.broadcastToAll({ type: "openspec_update", cwd, data });
|
|
@@ -785,115 +615,16 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
785
615
|
networkGuard,
|
|
786
616
|
store: openspecGroupStore,
|
|
787
617
|
});
|
|
788
|
-
registerSystemRoutes(fastify, { sessionManager, preferencesStore, metaPersistence, config, networkGuard, version: pkgVersion, directoryService, piGateway
|
|
618
|
+
registerSystemRoutes(fastify, { sessionManager, preferencesStore, metaPersistence, config, networkGuard, version: pkgVersion, directoryService, piGateway });
|
|
789
619
|
// GET /api/doctor — see change: doctor-rich-output (task 4.2). Auth-gated identically to /api/config.
|
|
790
620
|
registerDoctorRoutes(fastify);
|
|
791
621
|
registerToolRoutes(fastify, { registry: getDefaultRegistry(), networkGuard });
|
|
792
622
|
registerJjRoutes(fastify, { browserGateway, pendingAttachRegistry, networkGuard });
|
|
793
623
|
|
|
794
|
-
//
|
|
795
|
-
//
|
|
796
|
-
//
|
|
797
|
-
|
|
798
|
-
bootstrapState,
|
|
799
|
-
networkGuard,
|
|
800
|
-
triggerUpgradePi: async () => {
|
|
801
|
-
const packages = ["@earendil-works/pi-coding-agent"];
|
|
802
|
-
bootstrapState.setLastInstallPackages(packages);
|
|
803
|
-
bootstrapState.set({
|
|
804
|
-
status: "installing",
|
|
805
|
-
progress: { step: "@earendil-works/pi-coding-agent", output: "starting upgrade…" },
|
|
806
|
-
error: undefined,
|
|
807
|
-
});
|
|
808
|
-
try {
|
|
809
|
-
const res = await bootstrapInstall({
|
|
810
|
-
packages,
|
|
811
|
-
progress: (p) => {
|
|
812
|
-
bootstrapState.set({
|
|
813
|
-
progress: { step: p.step, output: p.output },
|
|
814
|
-
});
|
|
815
|
-
},
|
|
816
|
-
});
|
|
817
|
-
if (res.ok) {
|
|
818
|
-
bootstrapState.set({
|
|
819
|
-
status: "ready",
|
|
820
|
-
progress: undefined,
|
|
821
|
-
error: undefined,
|
|
822
|
-
});
|
|
823
|
-
// Broadcast /reload to connected sessions so they pick up the
|
|
824
|
-
// new pi version. Mirrors the pi-core update pattern above.
|
|
825
|
-
const connectedIds = piGateway.getConnectedSessionIds();
|
|
826
|
-
for (const sid of connectedIds) {
|
|
827
|
-
const session = sessionManager.get(sid);
|
|
828
|
-
if (session && session.status !== "ended") {
|
|
829
|
-
piGateway.sendToSession(sid, {
|
|
830
|
-
type: "send_prompt",
|
|
831
|
-
sessionId: sid,
|
|
832
|
-
text: "/reload",
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
} else {
|
|
837
|
-
bootstrapState.set({
|
|
838
|
-
status: "failed",
|
|
839
|
-
error: { message: res.error },
|
|
840
|
-
progress: undefined,
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
} catch (err) {
|
|
844
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
845
|
-
bootstrapState.set({
|
|
846
|
-
status: "failed",
|
|
847
|
-
error: { message },
|
|
848
|
-
progress: undefined,
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
},
|
|
852
|
-
triggerRetry: async () => {
|
|
853
|
-
// Retry re-runs the EXACT package set from the last failed install.
|
|
854
|
-
// Falls back to the default first-run set if no prior install was
|
|
855
|
-
// recorded (edge case: manual retry before any install attempt).
|
|
856
|
-
const prev = bootstrapState.getLastInstallPackages();
|
|
857
|
-
const packages = prev.length > 0
|
|
858
|
-
? prev
|
|
859
|
-
: ["@earendil-works/pi-coding-agent", "@fission-ai/openspec"];
|
|
860
|
-
bootstrapState.set({
|
|
861
|
-
status: "installing",
|
|
862
|
-
progress: { step: "retry", output: `restarting install (${packages.length} pkg${packages.length === 1 ? "" : "s"})…` },
|
|
863
|
-
error: undefined,
|
|
864
|
-
});
|
|
865
|
-
try {
|
|
866
|
-
const res = await bootstrapInstall({
|
|
867
|
-
packages,
|
|
868
|
-
progress: (p) => {
|
|
869
|
-
bootstrapState.set({
|
|
870
|
-
progress: { step: p.step, output: p.output },
|
|
871
|
-
});
|
|
872
|
-
},
|
|
873
|
-
});
|
|
874
|
-
if (res.ok) {
|
|
875
|
-
bootstrapState.set({
|
|
876
|
-
status: "ready",
|
|
877
|
-
progress: undefined,
|
|
878
|
-
error: undefined,
|
|
879
|
-
});
|
|
880
|
-
} else {
|
|
881
|
-
bootstrapState.set({
|
|
882
|
-
status: "failed",
|
|
883
|
-
error: { message: res.error },
|
|
884
|
-
progress: undefined,
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
} catch (err) {
|
|
888
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
889
|
-
bootstrapState.set({
|
|
890
|
-
status: "failed",
|
|
891
|
-
error: { message },
|
|
892
|
-
progress: undefined,
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
|
-
},
|
|
896
|
-
});
|
|
624
|
+
// /api/bootstrap/* routes removed under change:
|
|
625
|
+
// eliminate-electron-runtime-install (task 3.4). pi-core in-place
|
|
626
|
+
// updates flow through /api/pi-core/update for standalone + bridge
|
|
627
|
+
// arms; Electron arm uses electron-updater whole-app replacement.
|
|
897
628
|
// Package management
|
|
898
629
|
const packageManagerWrapper = new PackageManagerWrapper();
|
|
899
630
|
|
|
@@ -925,6 +656,24 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
925
656
|
...(result.partialSuccess ? { partialSuccess: result.partialSuccess } : {}),
|
|
926
657
|
} as any);
|
|
927
658
|
if (result.success) invalidateRecommendedCache();
|
|
659
|
+
// A successful package operation may have changed plugin requirement
|
|
660
|
+
// satisfaction. Refresh probes and broadcast plugin_config_update for
|
|
661
|
+
// any plugin whose `missingRequirements` flipped.
|
|
662
|
+
// See change: add-plugin-activation-ui.
|
|
663
|
+
if (result.success) {
|
|
664
|
+
void refreshRequirementProbesFor(null, {
|
|
665
|
+
listInstalled: () => packageManagerWrapper.listInstalled("global"),
|
|
666
|
+
}).then((changed) => {
|
|
667
|
+
for (const id of changed) {
|
|
668
|
+
const status = getPluginStatusStore().getStatus(id);
|
|
669
|
+
browserGateway.broadcast({
|
|
670
|
+
type: "plugin_config_update",
|
|
671
|
+
id,
|
|
672
|
+
config: status ?? {},
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
928
677
|
});
|
|
929
678
|
|
|
930
679
|
// Reload all active sessions after a successful package operation
|
|
@@ -977,12 +726,11 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
977
726
|
message: event.message,
|
|
978
727
|
});
|
|
979
728
|
});
|
|
980
|
-
registerPiChangelogRoutes(fastify, {
|
|
729
|
+
registerPiChangelogRoutes(fastify, {});
|
|
981
730
|
|
|
982
731
|
registerPiCoreRoutes(fastify, {
|
|
983
732
|
piCoreChecker,
|
|
984
733
|
piCoreUpdater,
|
|
985
|
-
bootstrapState,
|
|
986
734
|
onUpdateComplete: (payload) => {
|
|
987
735
|
browserGateway.broadcastToAll({
|
|
988
736
|
type: "pi_core_update_complete",
|
|
@@ -1017,6 +765,10 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
1017
765
|
networkGuard,
|
|
1018
766
|
broadcast: (msg) => browserGateway.broadcast(msg),
|
|
1019
767
|
});
|
|
768
|
+
registerPluginActivationRoutes(fastify, {
|
|
769
|
+
networkGuard,
|
|
770
|
+
broadcast: (msg) => browserGateway.broadcast(msg),
|
|
771
|
+
});
|
|
1020
772
|
registerProviderRoutes(fastify, { networkGuard, piGateway, browserGateway, port: config.port });
|
|
1021
773
|
|
|
1022
774
|
// ── Model Proxy ───────────────────────────────────────────────────
|
|
@@ -1077,30 +829,44 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
1077
829
|
// that works in the dev repo silently returns wrong paths in the
|
|
1078
830
|
// installed node_modules layout. require.resolve identifies packages
|
|
1079
831
|
// by name, which is the only canonical identity across layouts.
|
|
832
|
+
// Client-dir resolution — single strategy under change:
|
|
833
|
+
// eliminate-electron-runtime-install. The legacy 5-strategy chain
|
|
834
|
+
// (sibling/hoisted/monorepo/legacy paths) defended against runtime
|
|
835
|
+
// re-extraction wiping the bundled tree. Under the immutable bundle
|
|
836
|
+
// architecture that scenario cannot occur; the npm-resolver-anchored
|
|
837
|
+
// path is the only durable identity across install layouts.
|
|
838
|
+
//
|
|
839
|
+
// Dev / monorepo fallbacks are still allowed when require.resolve
|
|
840
|
+
// misses (e.g. running from a checked-out workspace where the web
|
|
841
|
+
// package hasn't been linked yet).
|
|
1080
842
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
1081
|
-
|
|
843
|
+
let clientDir = "";
|
|
1082
844
|
try {
|
|
1083
|
-
const webPkgJson = createRequire(import.meta.url).resolve(
|
|
1084
|
-
|
|
845
|
+
const webPkgJson = createRequire(import.meta.url).resolve(
|
|
846
|
+
"@blackbelt-technology/pi-dashboard-web/package.json",
|
|
847
|
+
);
|
|
848
|
+
const candidate = path.join(path.dirname(webPkgJson), "dist");
|
|
849
|
+
if (existsSync(path.join(candidate, "index.html"))) clientDir = candidate;
|
|
1085
850
|
} catch {
|
|
1086
|
-
// Web package not resolvable —
|
|
851
|
+
// Web package not resolvable — try dev-monorepo sibling.
|
|
852
|
+
const devCandidate = path.join(__dirname, "../../client/dist");
|
|
853
|
+
if (existsSync(path.join(devCandidate, "index.html"))) clientDir = devCandidate;
|
|
1087
854
|
}
|
|
1088
|
-
clientSearchPaths.push(
|
|
1089
|
-
// Installed as scoped sibling of server
|
|
1090
|
-
path.join(__dirname, "..", "..", "pi-dashboard-web", "dist"),
|
|
1091
|
-
// Installed in a parent node_modules (hoisted)
|
|
1092
|
-
path.join(__dirname, "..", "..", "..", "@blackbelt-technology", "pi-dashboard-web", "dist"),
|
|
1093
|
-
// Monorepo workspace sibling
|
|
1094
|
-
path.join(__dirname, "../../client/dist"),
|
|
1095
|
-
// Legacy path
|
|
1096
|
-
path.join(__dirname, "../../dist/client"),
|
|
1097
|
-
);
|
|
1098
|
-
const clientDir = clientSearchPaths.find(p => existsSync(path.join(p, "index.html"))) ?? "";
|
|
1099
855
|
const hasProductionBuild = !!clientDir;
|
|
1100
856
|
if (!hasProductionBuild) {
|
|
1101
857
|
console.log("[dashboard] No client build found — running in API-only mode");
|
|
1102
858
|
}
|
|
1103
859
|
|
|
860
|
+
// Dynamic PWA manifest — MUST be registered before fastify-static so
|
|
861
|
+
// explicit route matching wins over the static asset. See change:
|
|
862
|
+
// add-dynamic-pwa-manifest-naming.
|
|
863
|
+
registerManifestRoute(fastify, {
|
|
864
|
+
clientDir,
|
|
865
|
+
// Re-read config per request so Settings panel changes propagate
|
|
866
|
+
// without a server restart. loadConfig() is fs-cheap (<1ms).
|
|
867
|
+
getDashboardName: () => loadConfig().dashboardName,
|
|
868
|
+
});
|
|
869
|
+
|
|
1104
870
|
// Register static file serving for production build.
|
|
1105
871
|
// Always enabled — in dev mode, Vite handles most requests via the
|
|
1106
872
|
// not-found proxy, but asset files (JS/CSS with hashed names) must be
|
|
@@ -1179,7 +945,6 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
1179
945
|
sessionManager,
|
|
1180
946
|
eventStore,
|
|
1181
947
|
browserGateway,
|
|
1182
|
-
bootstrapState,
|
|
1183
948
|
|
|
1184
949
|
httpPort() {
|
|
1185
950
|
const addr = fastify.server.address();
|
|
@@ -1224,6 +989,9 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
1224
989
|
const pluginCfg = getPluginConfigFromFile(cfg, pluginId) as Record<string, unknown>;
|
|
1225
990
|
return pluginCfg.enabled !== false;
|
|
1226
991
|
},
|
|
992
|
+
requirementDeps: {
|
|
993
|
+
listInstalled: () => packageManagerWrapper.listInstalled("global"),
|
|
994
|
+
},
|
|
1227
995
|
createContext: (plugin) => createServerPluginContext(
|
|
1228
996
|
{
|
|
1229
997
|
fastify,
|
|
@@ -1239,9 +1007,26 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
1239
1007
|
return events.length > 0 ? events[events.length - 1] : undefined;
|
|
1240
1008
|
},
|
|
1241
1009
|
},
|
|
1242
|
-
broadcastToSubscribers: (msg) =>
|
|
1010
|
+
broadcastToSubscribers: (msg) => {
|
|
1011
|
+
// Intercept plugin_intents broadcasts and cache them so
|
|
1012
|
+
// reconnecting clients can replay the current intent state.
|
|
1013
|
+
// See change: adopt-server-driven-intent-rendering.
|
|
1014
|
+
const m = msg as { type?: string; pluginId?: string; sessionId?: string | null; slot?: string; intent?: unknown } | undefined;
|
|
1015
|
+
if (m && m.type === "plugin_intents" && typeof m.pluginId === "string" && typeof m.slot === "string") {
|
|
1016
|
+
pluginIntentCache.set(
|
|
1017
|
+
m.pluginId,
|
|
1018
|
+
m.sessionId ?? null,
|
|
1019
|
+
m.slot as Parameters<typeof pluginIntentCache.set>[2],
|
|
1020
|
+
(m.intent ?? null) as Parameters<typeof pluginIntentCache.set>[3],
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
browserGateway.broadcast(msg as any);
|
|
1024
|
+
},
|
|
1243
1025
|
registerPiHandler: (_type, _handler) => {},
|
|
1244
|
-
registerBrowserHandler: (
|
|
1026
|
+
registerBrowserHandler: (type, handler) =>
|
|
1027
|
+
browserGateway.registerHandler(type, (msg, ws) =>
|
|
1028
|
+
handler(msg, ws as unknown),
|
|
1029
|
+
),
|
|
1245
1030
|
getPluginConfig: (id) => {
|
|
1246
1031
|
const cfg = loadConfig();
|
|
1247
1032
|
return getPluginConfigFromFile(cfg, id);
|
|
@@ -1415,6 +1200,7 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
1415
1200
|
const existing = store.getStatus(id);
|
|
1416
1201
|
store.setStatus({
|
|
1417
1202
|
id,
|
|
1203
|
+
displayName: existing?.displayName ?? id,
|
|
1418
1204
|
enabled: existing?.enabled ?? true,
|
|
1419
1205
|
loaded: existing?.loaded ?? false,
|
|
1420
1206
|
error: `Bridge path conflict: existing=${result.existingPath}, new=${result.newPath}`,
|
|
@@ -1424,6 +1210,22 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
1424
1210
|
}
|
|
1425
1211
|
}
|
|
1426
1212
|
|
|
1213
|
+
// One-shot reconciliation: heal pre-existing installs where the bridge
|
|
1214
|
+
// was registered only in `dashboardPluginBridges` (pi ignores that key).
|
|
1215
|
+
// See change: fix-pi-flows-end-to-end (Group 1, task 1.5).
|
|
1216
|
+
try {
|
|
1217
|
+
const summary = reconcilePluginBridgePackages();
|
|
1218
|
+
for (const entry of summary) {
|
|
1219
|
+
if (entry.action === "added") {
|
|
1220
|
+
console.info(
|
|
1221
|
+
`[plugin-bridge] Reconciled packages[] for plugin "${entry.pluginId}": ${entry.bridgePath}`,
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
} catch (err) {
|
|
1226
|
+
console.warn("[plugin-bridge] Reconciliation failed (non-fatal):", err);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1427
1229
|
idleTimer.start();
|
|
1428
1230
|
},
|
|
1429
1231
|
|
|
@@ -1443,10 +1245,6 @@ export async function createServer(config: ServerConfig): Promise<DashboardServe
|
|
|
1443
1245
|
preferencesStore.flush();
|
|
1444
1246
|
preferencesStore.dispose();
|
|
1445
1247
|
|
|
1446
|
-
unsubscribeBootstrap();
|
|
1447
|
-
unsubscribeQueueComplete();
|
|
1448
|
-
bootstrapState.dispose();
|
|
1449
|
-
bootstrapQueue.clear("server shutting down");
|
|
1450
1248
|
stopTunnelWatchdog();
|
|
1451
1249
|
await deleteTunnel(config.port);
|
|
1452
1250
|
piGateway.stop();
|
|
@@ -13,8 +13,6 @@ import { spawnPiSession } from "./process-manager.js";
|
|
|
13
13
|
import { loadConfig } from "@blackbelt-technology/pi-dashboard-shared/config.js";
|
|
14
14
|
import type { PendingForkRegistry } from "./pending-fork-registry.js";
|
|
15
15
|
import type { PendingResumeIntentRegistry } from "./pending-resume-intent-registry.js";
|
|
16
|
-
import type { BootstrapStateStore } from "./bootstrap-state.js";
|
|
17
|
-
import type { BootstrapQueue } from "./bootstrap-queue.js";
|
|
18
16
|
import { attachRenameTarget, detachShouldClearName } from "./proposal-attach-naming.js";
|
|
19
17
|
import { FORK_DEGRADED_TO_NEW_MESSAGE, FORK_DEGRADED_TO_NEW_CODE } from "./browser-handlers/session-action-handler.js";
|
|
20
18
|
import { keeperOptsFromSpawnResult } from "./headless-pid-registry.js";
|
|
@@ -25,13 +23,6 @@ export interface SessionApiDeps {
|
|
|
25
23
|
browserGateway: BrowserGateway;
|
|
26
24
|
pendingForkRegistry?: PendingForkRegistry;
|
|
27
25
|
pendingDashboardSpawns?: Map<string, number>;
|
|
28
|
-
/**
|
|
29
|
-
* Bootstrap state + queue for degraded-mode gating. When omitted,
|
|
30
|
-
* session operations run normally (legacy behavior for tests that
|
|
31
|
-
* don't exercise the bootstrap flow). See change: unified-bootstrap-install.
|
|
32
|
-
*/
|
|
33
|
-
bootstrapState?: BootstrapStateStore;
|
|
34
|
-
bootstrapQueue?: BootstrapQueue;
|
|
35
26
|
/**
|
|
36
27
|
* User-resume-intent registry. Tagged in the resume endpoint so the
|
|
37
28
|
* `sessionManager.onChange` ended→alive branch can distinguish a
|
|
@@ -58,54 +49,12 @@ function getSessionOrFail(sessionManager: SessionManager, id: string): { session
|
|
|
58
49
|
}
|
|
59
50
|
|
|
60
51
|
export function registerSessionApi(fastify: FastifyInstance, deps: SessionApiDeps) {
|
|
61
|
-
const { sessionManager, piGateway, browserGateway, pendingForkRegistry, pendingDashboardSpawns,
|
|
52
|
+
const { sessionManager, piGateway, browserGateway, pendingForkRegistry, pendingDashboardSpawns, pendingResumeIntents, pendingAttachRegistry } = deps;
|
|
62
53
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
* the operation is enqueued and will run once status flips to "ready".
|
|
68
|
-
* - `{ code: 503, body: { error } }` when failed.
|
|
69
|
-
* See change: unified-bootstrap-install §5.
|
|
70
|
-
*/
|
|
71
|
-
function gateOrEnqueue<T>(handler: () => Promise<T>):
|
|
72
|
-
| null
|
|
73
|
-
| { code: 202; body: { status: "queued"; ticketId: string } }
|
|
74
|
-
| { code: 503; body: { error: string; bootstrap: "failed" | "version-too-old" } } {
|
|
75
|
-
if (!bootstrapState) return null;
|
|
76
|
-
const snap = bootstrapState.get();
|
|
77
|
-
// Block when pi version is below the configured minimum —
|
|
78
|
-
// even when status is "ready", a too-old pi must not run sessions.
|
|
79
|
-
// See change: unified-bootstrap-install §9.3.
|
|
80
|
-
if (
|
|
81
|
-
snap.status === "ready"
|
|
82
|
-
&& snap.error?.message?.startsWith("pi version ")
|
|
83
|
-
) {
|
|
84
|
-
return {
|
|
85
|
-
code: 503,
|
|
86
|
-
body: { error: snap.error.message, bootstrap: "version-too-old" },
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
if (snap.status === "ready") return null;
|
|
90
|
-
if (snap.status === "installing") {
|
|
91
|
-
if (!bootstrapQueue) {
|
|
92
|
-
return {
|
|
93
|
-
code: 202,
|
|
94
|
-
body: { status: "queued", ticketId: "" },
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
const ticket = bootstrapQueue.enqueue(handler);
|
|
98
|
-
return {
|
|
99
|
-
code: 202,
|
|
100
|
-
body: { status: "queued", ticketId: ticket.ticketId },
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
// status === "failed"
|
|
104
|
-
return {
|
|
105
|
-
code: 503,
|
|
106
|
-
body: { error: "pi not installed (bootstrap failed)", bootstrap: "failed" },
|
|
107
|
-
};
|
|
108
|
-
}
|
|
54
|
+
// Bootstrap gate + queue removed under change: eliminate-electron-runtime-install
|
|
55
|
+
// (task 3.5). pi/openspec/tsx ship as regular npm deps so pi is always
|
|
56
|
+
// resolvable at startup; queueing pi-dependent operations during an
|
|
57
|
+
// install window is no longer needed.
|
|
109
58
|
|
|
110
59
|
// POST /api/session/:id/prompt
|
|
111
60
|
fastify.post<IdParams & { Body: { text?: string; images?: any[] } }>(
|
|
@@ -254,13 +203,6 @@ export function registerSessionApi(fastify: FastifyInstance, deps: SessionApiDep
|
|
|
254
203
|
return spawnResult;
|
|
255
204
|
};
|
|
256
205
|
|
|
257
|
-
// Bootstrap gate: if pi isn't ready, queue the spawn and return 202.
|
|
258
|
-
const gate = gateOrEnqueue(doSpawn);
|
|
259
|
-
if (gate) {
|
|
260
|
-
reply.code(gate.code);
|
|
261
|
-
return gate.body;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
206
|
const spawnResult = await doSpawn();
|
|
265
207
|
if (!spawnResult.success) {
|
|
266
208
|
reply.code(500);
|