@blackbelt-technology/pi-agent-dashboard 0.5.3 → 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.
Files changed (212) hide show
  1. package/AGENTS.md +19 -30
  2. package/README.md +69 -1
  3. package/docs/architecture.md +89 -165
  4. package/package.json +10 -7
  5. package/packages/extension/package.json +2 -2
  6. package/packages/extension/src/__tests__/bridge-default-model-gate.test.ts +47 -0
  7. package/packages/extension/src/__tests__/bridge-followup-chat-order.test.ts +215 -0
  8. package/packages/extension/src/__tests__/bridge-followup-multi-entry.test.ts +202 -0
  9. package/packages/extension/src/__tests__/bridge-queue-update-forward.test.ts +77 -0
  10. package/packages/extension/src/__tests__/bridge-retry-ordering.test.ts +148 -0
  11. package/packages/extension/src/__tests__/bridge-shadow-queue-drain.test.ts +221 -0
  12. package/packages/extension/src/__tests__/bridge-shadow-queue-gate.test.ts +299 -0
  13. package/packages/extension/src/__tests__/bridge-shutdown-reset.test.ts +238 -0
  14. package/packages/extension/src/__tests__/bridge-slash-command-routing.test.ts +127 -31
  15. package/packages/extension/src/__tests__/command-handler.test.ts +105 -3
  16. package/packages/extension/src/__tests__/fixtures/usage-limit-error-strings.ts +127 -0
  17. package/packages/extension/src/__tests__/source-detector.test.ts +15 -0
  18. package/packages/extension/src/__tests__/usage-limit-orderer.test.ts +12 -0
  19. package/packages/extension/src/bridge-default-model-gate.ts +32 -0
  20. package/packages/extension/src/bridge.ts +299 -20
  21. package/packages/extension/src/command-handler.ts +53 -7
  22. package/packages/extension/src/dashboard-default-adapter.ts +5 -0
  23. package/packages/extension/src/prompt-bus.ts +15 -0
  24. package/packages/extension/src/slash-dispatch.ts +30 -15
  25. package/packages/extension/src/source-detector.ts +13 -5
  26. package/packages/extension/src/usage-limit-orderer.ts +18 -1
  27. package/packages/server/bin/pi-dashboard.mjs +62 -14
  28. package/packages/server/package.json +9 -5
  29. package/packages/server/src/__tests__/browser-gateway-register-handler.test.ts +69 -0
  30. package/packages/server/src/__tests__/cli-env-no-clobber.test.ts +46 -0
  31. package/packages/server/src/__tests__/cli-no-bootstrap-references.test.ts +69 -0
  32. package/packages/server/src/__tests__/cli-parse.test.ts +9 -10
  33. package/packages/server/src/__tests__/cli-version.test.ts +151 -0
  34. package/packages/server/src/__tests__/directory-service-openspec-enabled.test.ts +9 -0
  35. package/packages/server/src/__tests__/directory-service-refresh-force.test.ts +9 -0
  36. package/packages/server/src/__tests__/directory-service-specs-mtime.test.ts +9 -0
  37. package/packages/server/src/__tests__/directory-service-toctou.test.ts +9 -0
  38. package/packages/server/src/__tests__/directory-service.test.ts +9 -0
  39. package/packages/server/src/__tests__/doctor-route.test.ts +53 -0
  40. package/packages/server/src/__tests__/event-wiring-queue-state.test.ts +156 -0
  41. package/packages/server/src/__tests__/event-wiring-resume-clear.test.ts +105 -0
  42. package/packages/server/src/__tests__/health-shape.test.ts +35 -12
  43. package/packages/server/src/__tests__/installed-package-enricher.test.ts +12 -12
  44. package/packages/server/src/__tests__/is-activity-event.test.ts +4 -7
  45. package/packages/server/src/__tests__/package-routes.test.ts +6 -2
  46. package/packages/server/src/__tests__/pi-changelog-routes.test.ts +10 -13
  47. package/packages/server/src/__tests__/pi-core-checker.test.ts +2 -2
  48. package/packages/server/src/__tests__/pi-version-skew.test.ts +3 -2
  49. package/packages/server/src/__tests__/plugin-activation-routes.test.ts +267 -0
  50. package/packages/server/src/__tests__/plugin-intent-cache.test.ts +75 -0
  51. package/packages/server/src/__tests__/preferences-store.test.ts +196 -0
  52. package/packages/server/src/__tests__/reattach-placement.test.ts +9 -0
  53. package/packages/server/src/__tests__/recommended-routes.test.ts +2 -2
  54. package/packages/server/src/__tests__/recovery-server.test.ts +203 -0
  55. package/packages/server/src/__tests__/session-action-handler-clear-queue.test.ts +153 -0
  56. package/packages/server/src/__tests__/session-action-handler-headless-reload.test.ts +43 -0
  57. package/packages/server/src/__tests__/session-order-manager.test.ts +9 -0
  58. package/packages/server/src/__tests__/session-order-reboot.test.ts +9 -0
  59. package/packages/server/src/__tests__/session-ordering-integration.test.ts +9 -0
  60. package/packages/server/src/browser-gateway.ts +83 -5
  61. package/packages/server/src/browser-handlers/directory-handler.ts +69 -0
  62. package/packages/server/src/browser-handlers/session-action-handler.ts +89 -0
  63. package/packages/server/src/browser-handlers/subscription-handler.ts +23 -0
  64. package/packages/server/src/changelog-parser.ts +1 -1
  65. package/packages/server/src/cli.ts +68 -250
  66. package/packages/server/src/event-status-extraction.ts +14 -62
  67. package/packages/server/src/event-wiring.ts +23 -10
  68. package/packages/server/src/memory-session-manager.ts +4 -0
  69. package/packages/server/src/pi-core-checker.ts +1 -1
  70. package/packages/server/src/pi-dev-version-check.ts +1 -1
  71. package/packages/server/src/pi-version-skew.ts +24 -46
  72. package/packages/server/src/plugin-intent-cache.ts +67 -0
  73. package/packages/server/src/preferences-store.ts +199 -13
  74. package/packages/server/src/recovery-server.ts +366 -0
  75. package/packages/server/src/routes/__tests__/manifest-route.test.ts +138 -0
  76. package/packages/server/src/routes/doctor-routes.ts +26 -21
  77. package/packages/server/src/routes/manifest-route.ts +162 -0
  78. package/packages/server/src/routes/openspec-routes.ts +4 -25
  79. package/packages/server/src/routes/pi-changelog-routes.ts +5 -24
  80. package/packages/server/src/routes/pi-core-routes.ts +3 -23
  81. package/packages/server/src/routes/plugin-activation-routes.ts +193 -0
  82. package/packages/server/src/routes/recommended-routes.ts +21 -0
  83. package/packages/server/src/routes/system-routes.ts +73 -11
  84. package/packages/server/src/server.ts +105 -307
  85. package/packages/server/src/session-api.ts +5 -63
  86. package/packages/shared/package.json +1 -1
  87. package/packages/shared/src/__tests__/binary-lookup-resolveJiti.test.ts +28 -0
  88. package/packages/shared/src/__tests__/binary-lookup-spawn-env.test.ts +61 -0
  89. package/packages/shared/src/__tests__/binary-lookup.test.ts +16 -0
  90. package/packages/shared/src/__tests__/bridge-register.test.ts +67 -0
  91. package/packages/shared/src/__tests__/ci-electron-no-side-effects.test.ts +129 -0
  92. package/packages/shared/src/__tests__/config.test.ts +40 -0
  93. package/packages/shared/src/__tests__/dashboard-paths.test.ts +81 -0
  94. package/packages/shared/src/__tests__/ensure-windows-path.test.ts +112 -0
  95. package/packages/shared/src/__tests__/intent-types.test.ts +120 -0
  96. package/packages/shared/src/__tests__/jiti-packages-parity.test.ts +85 -0
  97. package/packages/shared/src/__tests__/legacy-managed-dir.test.ts +59 -0
  98. package/packages/shared/src/__tests__/no-direct-child-process.test.ts +12 -0
  99. package/packages/shared/src/__tests__/no-electron-execpath-spawn.test.ts +149 -0
  100. package/packages/shared/src/__tests__/no-flow-command-route-claims.test.ts +71 -0
  101. package/packages/shared/src/__tests__/no-flow-references-in-shell.test.ts +221 -0
  102. package/packages/shared/src/__tests__/no-managed-dir-reference.test.ts +134 -0
  103. package/packages/shared/src/__tests__/no-pi-dashboard-version-jiti-gate.test.ts +41 -0
  104. package/packages/shared/src/__tests__/no-primitive-direct-import.test.ts +235 -0
  105. package/packages/shared/src/__tests__/no-server-imports-in-resolver.test.ts +53 -0
  106. package/packages/shared/src/__tests__/node-spawn-jiti-contract.test.ts +54 -101
  107. package/packages/shared/src/__tests__/node-spawn.test.ts +29 -13
  108. package/packages/shared/src/__tests__/pi-package-resolver.test.ts +300 -0
  109. package/packages/shared/src/__tests__/plugin-activation-contracts.test.ts +74 -0
  110. package/packages/shared/src/__tests__/plugin-bridge-classify-source.test.ts +73 -0
  111. package/packages/shared/src/__tests__/plugin-bridge-register-extended.test.ts +17 -5
  112. package/packages/shared/src/__tests__/plugin-bridge-register-packages.test.ts +233 -0
  113. package/packages/shared/src/__tests__/plugin-bridge-register.test.ts +19 -9
  114. package/packages/shared/src/__tests__/publish-workflow-contract.test.ts +154 -15
  115. package/packages/shared/src/__tests__/recommended-extensions.test.ts +28 -10
  116. package/packages/shared/src/__tests__/resolver-parity-with-scanner.test.ts +76 -0
  117. package/packages/shared/src/__tests__/server-identity.test.ts +127 -0
  118. package/packages/shared/src/__tests__/server-launcher.test.ts +35 -0
  119. package/packages/shared/src/__tests__/source-matching.test.ts +5 -5
  120. package/packages/shared/src/__tests__/sync-versions-spec.test.ts +76 -0
  121. package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +50 -2
  122. package/packages/shared/src/bridge-register.ts +35 -2
  123. package/packages/shared/src/browser-protocol.ts +176 -2
  124. package/packages/shared/src/config.ts +12 -0
  125. package/packages/shared/src/dashboard-paths.ts +69 -0
  126. package/packages/shared/src/dashboard-plugin/index.ts +2 -0
  127. package/packages/shared/src/dashboard-plugin/intent-types.ts +93 -0
  128. package/packages/shared/src/dashboard-plugin/manifest-types.ts +55 -1
  129. package/packages/shared/src/dashboard-plugin/plugin-status.ts +82 -0
  130. package/packages/shared/src/dashboard-plugin/slot-props.ts +11 -0
  131. package/packages/shared/src/dashboard-plugin/slot-types.ts +16 -2
  132. package/packages/shared/src/dashboard-plugin/ui-primitives.ts +287 -0
  133. package/packages/shared/src/dashboard-starter.ts +22 -0
  134. package/packages/shared/src/doctor-core.ts +49 -27
  135. package/packages/shared/src/launch-source-types.ts +9 -9
  136. package/packages/shared/src/legacy-managed-dir.ts +97 -0
  137. package/packages/shared/src/mdns-discovery.ts +4 -1
  138. package/packages/shared/src/pi-package-resolver.ts +388 -0
  139. package/packages/shared/src/platform/binary-lookup.ts +27 -3
  140. package/packages/shared/src/platform/ensure-windows-path.ts +95 -0
  141. package/packages/shared/src/platform/exec.ts +22 -0
  142. package/packages/shared/src/platform/node-spawn.ts +42 -41
  143. package/packages/shared/src/plugin-bridge-register.ts +275 -18
  144. package/packages/shared/src/protocol.ts +94 -2
  145. package/packages/shared/src/recommended-extensions.ts +34 -10
  146. package/packages/shared/src/server-identity.ts +74 -5
  147. package/packages/shared/src/server-launcher.ts +20 -0
  148. package/packages/shared/src/source-matching.ts +1 -1
  149. package/packages/shared/src/tool-registry/__tests__/node-script-toargv-fallback.test.ts +84 -0
  150. package/packages/shared/src/tool-registry/definitions.ts +91 -7
  151. package/packages/shared/src/types.ts +12 -8
  152. package/scripts/maybe-patch-package.cjs +44 -0
  153. package/packages/server/src/__tests__/bootstrap-install-from-list.test.ts +0 -263
  154. package/packages/server/src/__tests__/bootstrap-queue.test.ts +0 -120
  155. package/packages/server/src/__tests__/bootstrap-routes.test.ts +0 -125
  156. package/packages/server/src/__tests__/bootstrap-state.test.ts +0 -119
  157. package/packages/server/src/__tests__/cli-bootstrap.test.ts +0 -36
  158. package/packages/server/src/__tests__/event-status-extraction-flow.test.ts +0 -55
  159. package/packages/server/src/__tests__/legacy-pi-cleanup.test.ts +0 -149
  160. package/packages/server/src/__tests__/post-install-openspec-refresh.test.ts +0 -180
  161. package/packages/server/src/__tests__/post-install-rescan.test.ts +0 -134
  162. package/packages/server/src/__tests__/system-routes-reextract.test.ts +0 -91
  163. package/packages/server/src/bootstrap-install-from-list.ts +0 -232
  164. package/packages/server/src/bootstrap-queue.ts +0 -130
  165. package/packages/server/src/bootstrap-state.ts +0 -159
  166. package/packages/server/src/legacy-pi-cleanup.ts +0 -151
  167. package/packages/server/src/routes/bootstrap-routes.ts +0 -125
  168. package/packages/shared/src/__tests__/bootstrap/README.md +0 -133
  169. package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +0 -378
  170. package/packages/shared/src/__tests__/bootstrap/assertions.ts +0 -136
  171. package/packages/shared/src/__tests__/bootstrap/cube.test.ts +0 -47
  172. package/packages/shared/src/__tests__/bootstrap/cube.ts +0 -66
  173. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +0 -84
  174. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +0 -90
  175. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +0 -34
  176. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/d-overrides.test.ts.snap +0 -20
  177. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +0 -62
  178. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +0 -34
  179. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +0 -49
  180. package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/j-path-gui-minimal.test.ts.snap +0 -12
  181. package/packages/shared/src/__tests__/bootstrap/families/a-electron.test.ts +0 -156
  182. package/packages/shared/src/__tests__/bootstrap/families/b-npm-global.test.ts +0 -157
  183. package/packages/shared/src/__tests__/bootstrap/families/c-dev-monorepo.test.ts +0 -102
  184. package/packages/shared/src/__tests__/bootstrap/families/d-overrides.test.ts +0 -76
  185. package/packages/shared/src/__tests__/bootstrap/families/e-stale-partial.test.ts +0 -94
  186. package/packages/shared/src/__tests__/bootstrap/families/f-cwd-variants.test.ts +0 -87
  187. package/packages/shared/src/__tests__/bootstrap/families/g-windows-specifics.test.ts +0 -143
  188. package/packages/shared/src/__tests__/bootstrap/families/h-home-drift.test.ts +0 -64
  189. package/packages/shared/src/__tests__/bootstrap/families/i-malformed-settings.test.ts +0 -77
  190. package/packages/shared/src/__tests__/bootstrap/families/index.ts +0 -19
  191. package/packages/shared/src/__tests__/bootstrap/families/j-path-gui-minimal.test.ts +0 -61
  192. package/packages/shared/src/__tests__/bootstrap/families/k-dashboard-absent.test.ts +0 -50
  193. package/packages/shared/src/__tests__/bootstrap/families/l-instance-coordination.test.ts +0 -272
  194. package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +0 -58
  195. package/packages/shared/src/__tests__/bootstrap/fixtures/electron-layout.ts +0 -84
  196. package/packages/shared/src/__tests__/bootstrap/fixtures/index.ts +0 -9
  197. package/packages/shared/src/__tests__/bootstrap/fixtures/managed-install.ts +0 -85
  198. package/packages/shared/src/__tests__/bootstrap/fixtures/npm-global-layout.ts +0 -122
  199. package/packages/shared/src/__tests__/bootstrap/fixtures/pi-versions.ts +0 -36
  200. package/packages/shared/src/__tests__/bootstrap/fixtures/settings-json.ts +0 -39
  201. package/packages/shared/src/__tests__/bootstrap/harness.smoke.test.ts +0 -220
  202. package/packages/shared/src/__tests__/bootstrap/harness.ts +0 -413
  203. package/packages/shared/src/__tests__/bootstrap/scenarios-skipped.ts +0 -125
  204. package/packages/shared/src/__tests__/bootstrap/scenarios.ts +0 -132
  205. package/packages/shared/src/__tests__/bootstrap-install-resolve-npm.test.ts +0 -72
  206. package/packages/shared/src/__tests__/install-managed-node-bootstrap-order.test.ts +0 -68
  207. package/packages/shared/src/__tests__/install-managed-node.test.ts +0 -192
  208. package/packages/shared/src/__tests__/installable-list.test.ts +0 -130
  209. package/packages/shared/src/__tests__/no-installable-list-in-bridge.test.ts +0 -52
  210. package/packages/shared/src/bootstrap-install.ts +0 -406
  211. package/packages/shared/src/installable-list.ts +0 -152
  212. 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, isOpenSpecDataEmpty, type DirectoryService } from "./directory-service.js";
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 { registerAllPluginBridges } from "@blackbelt-technology/pi-dashboard-shared/plugin-bridge-register.js";
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, bootstrapState });
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
- // ── Bootstrap REST routes ────────────────────────────────────────
795
- // The routes module is registered here; state + queue are declared
796
- // above (before session-api) so session routes can see them.
797
- registerBootstrapRoutes(fastify, {
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, { bootstrapState });
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
- const clientSearchPaths: string[] = [];
843
+ let clientDir = "";
1082
844
  try {
1083
- const webPkgJson = createRequire(import.meta.url).resolve("@blackbelt-technology/pi-dashboard-web/package.json");
1084
- clientSearchPaths.push(path.join(path.dirname(webPkgJson), "dist"));
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 — fall through to path-based search.
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) => browserGateway.broadcast(msg as any),
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: (_type, _handler) => {},
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, bootstrapState, bootstrapQueue, pendingResumeIntents, pendingAttachRegistry } = deps;
52
+ const { sessionManager, piGateway, browserGateway, pendingForkRegistry, pendingDashboardSpawns, pendingResumeIntents, pendingAttachRegistry } = deps;
62
53
 
63
- /**
64
- * Gate pi-dependent operations on bootstrap status. Returns:
65
- * - null when ready (proceed).
66
- * - `{ code: 202, body: { status: "queued", ticketId } }` when installing;
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);