@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.
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 +11 -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
@@ -66,10 +66,10 @@ import { createViewedSessionTracker, type ViewedSessionTracker } from "./viewed-
66
66
  import type { TerminalManager } from "./terminal-manager.js";
67
67
  import type { BrowserHandlerContext } from "./browser-handlers/handler-context.js";
68
68
  import { handleSubscribe } from "./browser-handlers/subscription-handler.js";
69
- import { handleSendPrompt, handleResumeSession, handleSpawnSession, handleShutdown, handleAbort, handleFlowControl, handleForceKill, handleKillProcess } from "./browser-handlers/session-action-handler.js";
69
+ import { handleSendPrompt, handleResumeSession, handleSpawnSession, handleShutdown, handleAbort, handleFlowControl, handleForceKill, handleKillProcess, handleClearSteeringQueue, handleClearFollowupSlot, handleEditFollowupSlot, handlePromoteFollowupEntry, handleRemoveFollowupEntry, handleEditFollowupEntry } from "./browser-handlers/session-action-handler.js";
70
70
  import { handleRenameSession, handleHideSession, handleUnhideSession, handleAttachProposal, handleDetachProposal, handleFetchContent, handleListSessions } from "./browser-handlers/session-meta-handler.js";
71
71
  import { handleCreateTerminal, handleKillTerminal, handleRenameTerminal } from "./browser-handlers/terminal-handler.js";
72
- import { handlePinDirectory, handleUnpinDirectory, handleReorderPinnedDirs, handleReorderSessions, handleOpenSpecRefresh, handleOpenSpecBulkArchive, handleExtensionUiResponse, handlePiGatewayForward } from "./browser-handlers/directory-handler.js";
72
+ import { handlePinDirectory, handleUnpinDirectory, handleReorderPinnedDirs, handleReorderSessions, handleOpenSpecRefresh, handleOpenSpecBulkArchive, handleExtensionUiResponse, handlePiGatewayForward, handleCreateWorkspace, handleRenameWorkspace, handleDeleteWorkspace, handleSetWorkspaceCollapsed, handleAddFolderToWorkspace, handleRemoveFolderFromWorkspace, handleReorderWorkspaceFolders, handleReorderWorkspaces } from "./browser-handlers/directory-handler.js";
73
73
 
74
74
 
75
75
 
@@ -111,6 +111,17 @@ export interface BrowserGateway {
111
111
  onConnect?: (ws: WebSocket) => void;
112
112
  /** Broadcast a message to all connected clients */
113
113
  broadcast(msg: ServerToBrowserMessage): void;
114
+ /**
115
+ * Register a handler for a Browser→Server message type the gateway does
116
+ * not natively handle. Used by plugins to receive `plugin_action`
117
+ * messages without modifying the gateway's switch statement.
118
+ * See change: adopt-server-driven-intent-rendering.
119
+ */
120
+ registerHandler(
121
+ type: string,
122
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
+ handler: (msg: any, ws: WebSocket) => void,
124
+ ): void;
114
125
  }
115
126
 
116
127
  export function createBrowserGateway(
@@ -131,6 +142,14 @@ export function createBrowserGateway(
131
142
  ): BrowserGateway {
132
143
  const wss = new WebSocketServer({ noServer: true });
133
144
 
145
+ /**
146
+ * Plugin-registered handlers for custom Browser→Server message types.
147
+ * Lives outside subscriptions because handlers are global, not per-WS.
148
+ * See change: adopt-server-driven-intent-rendering.
149
+ */
150
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
151
+ const customHandlers = new Map<string, (msg: any, ws: WebSocket) => void>();
152
+
134
153
  // Track subscriptions: ws → Set<sessionId>
135
154
  const subscriptions = new Map<WebSocket, Set<string>>();
136
155
  // Track which sessions are mid-replay per WebSocket (suppress live events)
@@ -274,6 +293,12 @@ export function createBrowserGateway(
274
293
  // Send pinned directories on connect
275
294
  if (preferencesStore) {
276
295
  sendTo(ws, { type: "pinned_dirs_updated", paths: preferencesStore.getPinnedDirectories() });
296
+ // Send current workspaces snapshot. See change: folder-workspaces.
297
+ // Guarded with `typeof` so old PreferencesStore stubs in tests that
298
+ // predate workspaces still work — they simply get no workspace snapshot.
299
+ if (typeof preferencesStore.getWorkspaces === "function") {
300
+ sendTo(ws, { type: "workspaces_updated", workspaces: preferencesStore.getWorkspaces() });
301
+ }
277
302
  }
278
303
 
279
304
  // Send OpenSpec data for every known directory — exactly one
@@ -358,6 +383,24 @@ export function createBrowserGateway(
358
383
  case "abort":
359
384
  handleAbort(msg, ctx);
360
385
  break;
386
+ case "clear_steering_queue":
387
+ handleClearSteeringQueue(msg, ctx);
388
+ break;
389
+ case "clear_followup_slot":
390
+ handleClearFollowupSlot(msg, ctx);
391
+ break;
392
+ case "edit_followup_slot":
393
+ handleEditFollowupSlot(msg, ctx);
394
+ break;
395
+ case "promote_followup_entry":
396
+ handlePromoteFollowupEntry(msg, ctx);
397
+ break;
398
+ case "remove_followup_entry":
399
+ handleRemoveFollowupEntry(msg, ctx);
400
+ break;
401
+ case "edit_followup_entry":
402
+ handleEditFollowupEntry(msg, ctx);
403
+ break;
361
404
  case "force_kill":
362
405
  await handleForceKill(msg, ctx);
363
406
  break;
@@ -409,6 +452,30 @@ export function createBrowserGateway(
409
452
  case "reorder_pinned_dirs":
410
453
  handleReorderPinnedDirs(msg, ctx);
411
454
  break;
455
+ case "create_workspace":
456
+ handleCreateWorkspace(msg, ctx);
457
+ break;
458
+ case "rename_workspace":
459
+ handleRenameWorkspace(msg, ctx);
460
+ break;
461
+ case "delete_workspace":
462
+ handleDeleteWorkspace(msg, ctx);
463
+ break;
464
+ case "set_workspace_collapsed":
465
+ handleSetWorkspaceCollapsed(msg, ctx);
466
+ break;
467
+ case "add_folder_to_workspace":
468
+ handleAddFolderToWorkspace(msg, ctx);
469
+ break;
470
+ case "remove_folder_from_workspace":
471
+ handleRemoveFolderFromWorkspace(msg, ctx);
472
+ break;
473
+ case "reorder_workspace_folders":
474
+ handleReorderWorkspaceFolders(msg, ctx);
475
+ break;
476
+ case "reorder_workspaces":
477
+ handleReorderWorkspaces(msg, ctx);
478
+ break;
412
479
  case "openspec_refresh":
413
480
  handleOpenSpecRefresh(msg, ctx);
414
481
  break;
@@ -532,10 +599,17 @@ export function createBrowserGateway(
532
599
  viewedSessionTracker.unview(msg.sessionId, ws);
533
600
  break;
534
601
  }
535
- default:
536
- // Forward simple pi-gateway commands
537
- handlePiGatewayForward(msg, ctx);
602
+ default: {
603
+ // Plugin-registered custom handler takes precedence over pi-gateway forward.
604
+ const type = (msg as { type?: string } | undefined)?.type;
605
+ if (type && customHandlers.has(type)) {
606
+ customHandlers.get(type)!(msg, ws);
607
+ } else {
608
+ // Forward simple pi-gateway commands
609
+ handlePiGatewayForward(msg, ctx);
610
+ }
538
611
  break;
612
+ }
539
613
  }
540
614
  } catch (err) {
541
615
  const type = (msg as { type?: string } | undefined)?.type ?? "unknown";
@@ -568,6 +642,10 @@ export function createBrowserGateway(
568
642
  broadcast(msg);
569
643
  },
570
644
 
645
+ registerHandler(type, handler) {
646
+ customHandlers.set(type, handler);
647
+ },
648
+
571
649
  broadcastEvent(sessionId: string, seq: number, event: any) {
572
650
  const subscribers = getSubscribers(sessionId);
573
651
  const msg: ServerToBrowserMessage = {
@@ -75,6 +75,75 @@ export function handleReorderPinnedDirs(
75
75
  }
76
76
  }
77
77
 
78
+ // ── folder-workspaces handlers ──────────────────────────────────
79
+ //
80
+ // Each handler dispatches to PreferencesStore which returns true on
81
+ // mutation. Broadcast `workspaces_updated` only on actual mutation
82
+ // (no broadcast for no-op / invalid / unknown-id calls). See spec
83
+ // folder-workspaces.
84
+
85
+ function broadcastWorkspaces(ctx: BrowserHandlerContext): void {
86
+ if (!ctx.preferencesStore) return;
87
+ ctx.broadcast({ type: "workspaces_updated", workspaces: ctx.preferencesStore.getWorkspaces() });
88
+ }
89
+
90
+ export function handleCreateWorkspace(
91
+ msg: Extract<BrowserToServerMessage, { type: "create_workspace" }>,
92
+ ctx: BrowserHandlerContext,
93
+ ): void {
94
+ const ws = ctx.preferencesStore?.createWorkspace(msg.name);
95
+ if (ws) broadcastWorkspaces(ctx);
96
+ }
97
+
98
+ export function handleRenameWorkspace(
99
+ msg: Extract<BrowserToServerMessage, { type: "rename_workspace" }>,
100
+ ctx: BrowserHandlerContext,
101
+ ): void {
102
+ if (ctx.preferencesStore?.renameWorkspace(msg.id, msg.name)) broadcastWorkspaces(ctx);
103
+ }
104
+
105
+ export function handleDeleteWorkspace(
106
+ msg: Extract<BrowserToServerMessage, { type: "delete_workspace" }>,
107
+ ctx: BrowserHandlerContext,
108
+ ): void {
109
+ if (ctx.preferencesStore?.deleteWorkspace(msg.id)) broadcastWorkspaces(ctx);
110
+ }
111
+
112
+ export function handleSetWorkspaceCollapsed(
113
+ msg: Extract<BrowserToServerMessage, { type: "set_workspace_collapsed" }>,
114
+ ctx: BrowserHandlerContext,
115
+ ): void {
116
+ if (ctx.preferencesStore?.setWorkspaceCollapsed(msg.id, msg.collapsed)) broadcastWorkspaces(ctx);
117
+ }
118
+
119
+ export function handleAddFolderToWorkspace(
120
+ msg: Extract<BrowserToServerMessage, { type: "add_folder_to_workspace" }>,
121
+ ctx: BrowserHandlerContext,
122
+ ): void {
123
+ if (ctx.preferencesStore?.addFolderToWorkspace(msg.id, msg.path)) broadcastWorkspaces(ctx);
124
+ }
125
+
126
+ export function handleRemoveFolderFromWorkspace(
127
+ msg: Extract<BrowserToServerMessage, { type: "remove_folder_from_workspace" }>,
128
+ ctx: BrowserHandlerContext,
129
+ ): void {
130
+ if (ctx.preferencesStore?.removeFolderFromWorkspace(msg.id, msg.path)) broadcastWorkspaces(ctx);
131
+ }
132
+
133
+ export function handleReorderWorkspaceFolders(
134
+ msg: Extract<BrowserToServerMessage, { type: "reorder_workspace_folders" }>,
135
+ ctx: BrowserHandlerContext,
136
+ ): void {
137
+ if (ctx.preferencesStore?.reorderWorkspaceFolders(msg.id, msg.paths)) broadcastWorkspaces(ctx);
138
+ }
139
+
140
+ export function handleReorderWorkspaces(
141
+ msg: Extract<BrowserToServerMessage, { type: "reorder_workspaces" }>,
142
+ ctx: BrowserHandlerContext,
143
+ ): void {
144
+ if (ctx.preferencesStore?.reorderWorkspaces(msg.ids)) broadcastWorkspaces(ctx);
145
+ }
146
+
78
147
  export function handleReorderSessions(
79
148
  msg: Extract<BrowserToServerMessage, { type: "reorder_sessions" }>,
80
149
  ctx: BrowserHandlerContext,
@@ -256,6 +256,7 @@ export async function handleSendPrompt(
256
256
  sessionId: msg.sessionId,
257
257
  text: msg.text,
258
258
  images: msg.images,
259
+ delivery: msg.delivery,
259
260
  });
260
261
  if (!sent) {
261
262
  console.error(`[dashboard] send_prompt failed: no bridge connection for session ${msg.sessionId}`);
@@ -523,6 +524,94 @@ export function handleAbort(
523
524
  ctx.piGateway.sendToSession(msg.sessionId, { type: "abort", sessionId: msg.sessionId });
524
525
  }
525
526
 
527
+ /**
528
+ * Clear pi's steering queue for the given session. Forwards to bridge,
529
+ * which calls `pi.clearSteeringQueue()`. Idempotent. Drops silently when
530
+ * the session is unknown. See change: add-followup-edit-and-steer-cancel.
531
+ */
532
+ export function handleClearSteeringQueue(
533
+ msg: Extract<BrowserToServerMessage, { type: "clear_steering_queue" }>,
534
+ ctx: BrowserHandlerContext,
535
+ ): void {
536
+ if (!ctx.sessionManager.get(msg.sessionId)) return;
537
+ ctx.piGateway.sendToSession(msg.sessionId, { type: "clear_steering_queue", sessionId: msg.sessionId });
538
+ }
539
+
540
+ /**
541
+ * Clear pi's follow-up slot for the given session. Forwards to bridge,
542
+ * which calls `pi.clearFollowUpQueue()`. Idempotent.
543
+ * See change: add-followup-edit-and-steer-cancel.
544
+ */
545
+ export function handleClearFollowupSlot(
546
+ msg: Extract<BrowserToServerMessage, { type: "clear_followup_slot" }>,
547
+ ctx: BrowserHandlerContext,
548
+ ): void {
549
+ if (!ctx.sessionManager.get(msg.sessionId)) return;
550
+ ctx.piGateway.sendToSession(msg.sessionId, { type: "clear_followup_slot", sessionId: msg.sessionId });
551
+ }
552
+
553
+ /**
554
+ * Atomic replace of pi's follow-up slot (clear-then-send). Forwards text +
555
+ * optional images to bridge, which calls clearFollowUpQueue then sendUserMessage.
556
+ * See change: add-followup-edit-and-steer-cancel.
557
+ */
558
+ export function handleEditFollowupSlot(
559
+ msg: Extract<BrowserToServerMessage, { type: "edit_followup_slot" }>,
560
+ ctx: BrowserHandlerContext,
561
+ ): void {
562
+ if (!ctx.sessionManager.get(msg.sessionId)) return;
563
+ ctx.piGateway.sendToSession(msg.sessionId, {
564
+ type: "edit_followup_slot",
565
+ sessionId: msg.sessionId,
566
+ text: msg.text,
567
+ images: msg.images,
568
+ });
569
+ }
570
+
571
+ /**
572
+ * Promote the follow-up entry at `index` to the head (position 0) of the queue.
573
+ * See change: add-followup-edit-and-steer-cancel (task 13.3).
574
+ */
575
+ export function handlePromoteFollowupEntry(
576
+ msg: Extract<BrowserToServerMessage, { type: "promote_followup_entry" }>,
577
+ ctx: BrowserHandlerContext,
578
+ ): void {
579
+ if (!ctx.sessionManager.get(msg.sessionId)) return;
580
+ ctx.piGateway.sendToSession(msg.sessionId, {
581
+ type: "promote_followup_entry",
582
+ sessionId: msg.sessionId,
583
+ index: msg.index,
584
+ });
585
+ }
586
+
587
+ /** Remove the follow-up entry at `index` from the queue. */
588
+ export function handleRemoveFollowupEntry(
589
+ msg: Extract<BrowserToServerMessage, { type: "remove_followup_entry" }>,
590
+ ctx: BrowserHandlerContext,
591
+ ): void {
592
+ if (!ctx.sessionManager.get(msg.sessionId)) return;
593
+ ctx.piGateway.sendToSession(msg.sessionId, {
594
+ type: "remove_followup_entry",
595
+ sessionId: msg.sessionId,
596
+ index: msg.index,
597
+ });
598
+ }
599
+
600
+ /** Replace the follow-up entry at `index` with new text. */
601
+ export function handleEditFollowupEntry(
602
+ msg: Extract<BrowserToServerMessage, { type: "edit_followup_entry" }>,
603
+ ctx: BrowserHandlerContext,
604
+ ): void {
605
+ if (!ctx.sessionManager.get(msg.sessionId)) return;
606
+ ctx.piGateway.sendToSession(msg.sessionId, {
607
+ type: "edit_followup_entry",
608
+ sessionId: msg.sessionId,
609
+ index: msg.index,
610
+ text: msg.text,
611
+ images: msg.images,
612
+ });
613
+ }
614
+
526
615
  export function handleFlowControl(
527
616
  msg: Extract<BrowserToServerMessage, { type: "flow_control" }>,
528
617
  ctx: BrowserHandlerContext,
@@ -5,6 +5,7 @@ import type { WebSocket } from "ws";
5
5
  import type { ServerToBrowserMessage, BrowserToServerMessage } from "@blackbelt-technology/pi-dashboard-shared/browser-protocol.js";
6
6
  import type { BrowserHandlerContext } from "./handler-context.js";
7
7
  import { extractStatsFromEvents } from "../event-status-extraction.js";
8
+ import { pluginIntentCache } from "../plugin-intent-cache.js";
8
9
  import type { StoredEvent } from "../memory-event-store.js";
9
10
 
10
11
  const REPLAY_BATCH_SIZE = 50;
@@ -94,6 +95,28 @@ export function replayUiState(
94
95
  sendTo(ws, { type: "ext_ui_decorator", sessionId, descriptor } as any);
95
96
  }
96
97
  }
98
+
99
+ // Replay cached plugin intents for this session (per-session AND global).
100
+ // See change: adopt-server-driven-intent-rendering.
101
+ for (const entry of pluginIntentCache.getForSession(sessionId)) {
102
+ sendTo(ws, {
103
+ type: "plugin_intents",
104
+ pluginId: entry.pluginId,
105
+ sessionId: entry.sessionId,
106
+ slot: entry.slot,
107
+ intent: entry.intent,
108
+ } as any);
109
+ }
110
+ // Also replay global (sessionId === null) intents.
111
+ for (const entry of pluginIntentCache.getForSession(null)) {
112
+ sendTo(ws, {
113
+ type: "plugin_intents",
114
+ pluginId: entry.pluginId,
115
+ sessionId: null,
116
+ slot: entry.slot,
117
+ intent: entry.intent,
118
+ } as any);
119
+ }
97
120
  }
98
121
 
99
122
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Pure parser for Keep-a-Changelog-style markdown files.
3
3
  *
4
- * Pi (`@mariozechner/pi-coding-agent`) ships a `CHANGELOG.md` whose
4
+ * Pi (`@earendil-works/pi-coding-agent` (formerly `@mariozechner/pi-coding-agent`)) ships a `CHANGELOG.md` whose
5
5
  * format is mechanically reliable:
6
6
  * - H2 release headers: `## [<version>] - <date>`
7
7
  * - H3 sub-section headers: `### Breaking Changes`, `### New Features`,