@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
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Server ↔ Browser WebSocket protocol messages.
3
3
  */
4
+ import type {
5
+ PluginIntentsMessage,
6
+ PluginActionMessage,
7
+ } from "./dashboard-plugin/intent-types.js";
4
8
  import type {
5
9
  DashboardSession,
6
10
  DashboardEvent,
@@ -277,6 +281,28 @@ export interface PinnedDirsUpdatedMessage {
277
281
  paths: string[];
278
282
  }
279
283
 
284
+ // ── Workspaces (folder-workspaces) ───────────────────────────────────
285
+
286
+ /**
287
+ * Named, server-persisted, collapsible container grouping one or more
288
+ * folders. Membership is authoritative and orthogonal to pinning — a
289
+ * folder may be in `folders[]` and `pinnedDirectories` independently.
290
+ * Single-membership: a folder belongs to ≤1 workspace.
291
+ * See change: folder-workspaces.
292
+ */
293
+ export interface Workspace {
294
+ id: string;
295
+ name: string;
296
+ collapsed: boolean;
297
+ folders: string[];
298
+ }
299
+
300
+ /** Server → browser: full workspace list snapshot (sent on subscribe + every mutation). */
301
+ export interface WorkspacesUpdatedMessage {
302
+ type: "workspaces_updated";
303
+ workspaces: Workspace[];
304
+ }
305
+
280
306
  export interface TerminalAddedMessage {
281
307
  type: "terminal_added";
282
308
  terminal: TerminalSession;
@@ -551,6 +577,7 @@ export type ServerToBrowserMessage =
551
577
  | SessionsReorderedMessage
552
578
  | SessionsSnapshotMessage
553
579
  | PinnedDirsUpdatedMessage
580
+ | WorkspacesUpdatedMessage
554
581
  | TerminalAddedMessage
555
582
  | TerminalRemovedMessage
556
583
  | TerminalUpdatedMessage
@@ -574,7 +601,9 @@ export type ServerToBrowserMessage =
574
601
  | BrowserUiModulesListMessage
575
602
  | BrowserUiDataListMessage
576
603
  | BrowserExtUiDecoratorMessage
577
- | BrowserAssetRegisterMessage;
604
+ | BrowserAssetRegisterMessage
605
+ | PluginIntentsMessage
606
+ | QueueUpdateToBrowserMessage;
578
607
 
579
608
  // ── Browser → Server ────────────────────────────────────────────────
580
609
 
@@ -594,6 +623,8 @@ export interface SendPromptToBrowserMessage {
594
623
  sessionId: string;
595
624
  text: string;
596
625
  images?: ImageContent[];
626
+ /** Delivery mode: "steer" (after current turn) or "followUp" (after agent finishes). Defaults to "followUp" when absent. See change: add-steering-message. */
627
+ delivery?: "steer" | "followUp";
597
628
  }
598
629
 
599
630
  export interface AbortToBrowserMessage {
@@ -601,6 +632,86 @@ export interface AbortToBrowserMessage {
601
632
  sessionId: string;
602
633
  }
603
634
 
635
+ /**
636
+ * Browser -> server: clear pi's steering queue for the given session.
637
+ * Server forwards as `clear_steering_queue`; bridge calls `pi.clearSteeringQueue()`.
638
+ * Pi emits a fresh `queue_update` reflecting the empty array, which the server
639
+ * caches + broadcasts. Idempotent on empty queue.
640
+ * See change: add-followup-edit-and-steer-cancel.
641
+ */
642
+ export interface ClearSteeringQueueFromBrowserMessage {
643
+ type: "clear_steering_queue";
644
+ sessionId: string;
645
+ }
646
+
647
+ /**
648
+ * Browser -> server: clear pi's follow-up slot for the given session.
649
+ * Server forwards as `clear_followup_slot`; bridge calls `pi.clearFollowUpQueue()`.
650
+ * Idempotent on empty slot.
651
+ * See change: add-followup-edit-and-steer-cancel.
652
+ */
653
+ export interface ClearFollowupSlotFromBrowserMessage {
654
+ type: "clear_followup_slot";
655
+ sessionId: string;
656
+ }
657
+
658
+ /**
659
+ * Browser -> server (v1, deprecated): replace pi's follow-up slot atomically.
660
+ * v2 prefers `edit_followup_entry { index: 0 }`. Bridge still accepts both
661
+ * for backward compatibility.
662
+ * See change: add-followup-edit-and-steer-cancel.
663
+ */
664
+ export interface EditFollowupSlotFromBrowserMessage {
665
+ type: "edit_followup_slot";
666
+ sessionId: string;
667
+ text: string;
668
+ images?: ImageContent[];
669
+ }
670
+
671
+ /**
672
+ * Browser -> server (v2): move follow-up entry at `index` to position 0.
673
+ * See change: add-followup-edit-and-steer-cancel.
674
+ */
675
+ export interface PromoteFollowupEntryFromBrowserMessage {
676
+ type: "promote_followup_entry";
677
+ sessionId: string;
678
+ index: number;
679
+ }
680
+
681
+ /**
682
+ * Browser -> server (v2): remove follow-up entry at `index`.
683
+ * See change: add-followup-edit-and-steer-cancel.
684
+ */
685
+ export interface RemoveFollowupEntryFromBrowserMessage {
686
+ type: "remove_followup_entry";
687
+ sessionId: string;
688
+ index: number;
689
+ }
690
+
691
+ /**
692
+ * Browser -> server (v2): replace follow-up entry at `index`.
693
+ * See change: add-followup-edit-and-steer-cancel.
694
+ */
695
+ export interface EditFollowupEntryFromBrowserMessage {
696
+ type: "edit_followup_entry";
697
+ sessionId: string;
698
+ index: number;
699
+ text: string;
700
+ images?: ImageContent[];
701
+ }
702
+
703
+ /**
704
+ * Server -> browser: broadcast pi's queue mirror after a `queue_update`
705
+ * arrives from the bridge. Drives `Session.pendingQueues`.
706
+ * See change: add-followup-edit-and-steer-cancel.
707
+ */
708
+ export interface QueueUpdateToBrowserMessage {
709
+ type: "queue_update";
710
+ sessionId: string;
711
+ steering: string[];
712
+ followUp: string[];
713
+ }
714
+
604
715
  export interface RequestCommandsToBrowserMessage {
605
716
  type: "request_commands";
606
717
  sessionId: string;
@@ -803,6 +914,54 @@ export interface ReorderPinnedDirsMessage {
803
914
  paths: string[];
804
915
  }
805
916
 
917
+ // ── Workspace mutation messages (browser → server) ───────────────────
918
+ // See change: folder-workspaces. Verb-first to match pin_directory family.
919
+
920
+ export interface CreateWorkspaceMessage {
921
+ type: "create_workspace";
922
+ name: string;
923
+ }
924
+
925
+ export interface RenameWorkspaceMessage {
926
+ type: "rename_workspace";
927
+ id: string;
928
+ name: string;
929
+ }
930
+
931
+ export interface DeleteWorkspaceMessage {
932
+ type: "delete_workspace";
933
+ id: string;
934
+ }
935
+
936
+ export interface SetWorkspaceCollapsedMessage {
937
+ type: "set_workspace_collapsed";
938
+ id: string;
939
+ collapsed: boolean;
940
+ }
941
+
942
+ export interface AddFolderToWorkspaceMessage {
943
+ type: "add_folder_to_workspace";
944
+ id: string;
945
+ path: string;
946
+ }
947
+
948
+ export interface RemoveFolderFromWorkspaceMessage {
949
+ type: "remove_folder_from_workspace";
950
+ id: string;
951
+ path: string;
952
+ }
953
+
954
+ export interface ReorderWorkspaceFoldersMessage {
955
+ type: "reorder_workspace_folders";
956
+ id: string;
957
+ paths: string[];
958
+ }
959
+
960
+ export interface ReorderWorkspacesMessage {
961
+ type: "reorder_workspaces";
962
+ ids: string[];
963
+ }
964
+
806
965
  export interface OpenSpecBulkArchiveBrowserMessage {
807
966
  type: "openspec_bulk_archive";
808
967
  cwd: string;
@@ -964,6 +1123,14 @@ export type BrowserToServerMessage =
964
1123
  | PinDirectoryMessage
965
1124
  | UnpinDirectoryMessage
966
1125
  | ReorderPinnedDirsMessage
1126
+ | CreateWorkspaceMessage
1127
+ | RenameWorkspaceMessage
1128
+ | DeleteWorkspaceMessage
1129
+ | SetWorkspaceCollapsedMessage
1130
+ | AddFolderToWorkspaceMessage
1131
+ | RemoveFolderFromWorkspaceMessage
1132
+ | ReorderWorkspaceFoldersMessage
1133
+ | ReorderWorkspacesMessage
967
1134
  | OpenSpecBulkArchiveBrowserMessage
968
1135
  | CreateTerminalBrowserMessage
969
1136
  | KillTerminalBrowserMessage
@@ -981,4 +1148,11 @@ export type BrowserToServerMessage =
981
1148
  | UiManagementBrowserMessage
982
1149
  | SessionViewBrowserMessage
983
1150
  | SessionUnviewBrowserMessage
984
- | KillProcessBrowserMessage;
1151
+ | KillProcessBrowserMessage
1152
+ | ClearSteeringQueueFromBrowserMessage
1153
+ | ClearFollowupSlotFromBrowserMessage
1154
+ | EditFollowupSlotFromBrowserMessage
1155
+ | PromoteFollowupEntryFromBrowserMessage
1156
+ | RemoveFollowupEntryFromBrowserMessage
1157
+ | EditFollowupEntryFromBrowserMessage
1158
+ | PluginActionMessage;
@@ -212,6 +212,15 @@ export interface DashboardConfig {
212
212
  cors: CorsConfig;
213
213
  /** Last-used server address (host:port) for reconnection */
214
214
  lastServer?: string;
215
+ /**
216
+ * Display name shown as the PWA app label when installed on a home screen
217
+ * or app drawer. Used as the `<source>` segment of the dynamic
218
+ * `/manifest.json` `name` field: `"Pi-Dash · <source>"`. Trimmed; blank /
219
+ * whitespace-only values are treated as unset and the server falls back to
220
+ * the request `Host` header (port stripped) → `os.hostname()` → literal
221
+ * `"Pi-Dash"`. See change: add-dynamic-pwa-manifest-naming.
222
+ */
223
+ dashboardName?: string;
215
224
  /** Whether the server was launched by the Electron app */
216
225
  electronMode: boolean;
217
226
  /**
@@ -574,6 +583,9 @@ export function loadConfig(): DashboardConfig {
574
583
  : defaults.cors.allowedOrigins,
575
584
  },
576
585
  ...(typeof parsed.lastServer === "string" ? { lastServer: parsed.lastServer } : {}),
586
+ ...(typeof parsed.dashboardName === "string" && parsed.dashboardName.trim()
587
+ ? { dashboardName: parsed.dashboardName }
588
+ : {}),
577
589
  electronMode: parsed.electronMode === true,
578
590
  knownServers: parseKnownServers(parsed.knownServers),
579
591
  reattachPlacement: parseReattachPlacement(parsed.reattachPlacement),
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Single source of truth for filesystem paths the dashboard touches at runtime.
3
+ *
4
+ * Why this file exists
5
+ * --------------------
6
+ * Two distinct directories were historically conflated by `~/`-anchored
7
+ * `path.join` calls scattered across packages:
8
+ *
9
+ * ~/.pi/dashboard/ — config + the *server* log (`server.log`)
10
+ * ~/.pi-dashboard/ — the *managed* install dir (npm packages, etc.)
11
+ * Older bootstrap code also wrote an *installer*
12
+ * log to `~/.pi-dashboard/server.log` (note: same
13
+ * filename, different dir). That file is now
14
+ * legacy/dead in the V2 launch path.
15
+ *
16
+ * Loading-page recovery surfaced this on 2026-05-17: the IPC handler
17
+ * read `~/.pi-dashboard/server.log` (stale installer log from May 8)
18
+ * while the live server wrote to `~/.pi/dashboard/server.log`.
19
+ *
20
+ * All path math lives here. Every $HOME override goes through `env.homedir`
21
+ * so tests can re-root without mutating `os.homedir()`.
22
+ *
23
+ * See change: harvest-bootstrap-survivor-fixes (cherry-pick 1).
24
+ */
25
+ import path from "node:path";
26
+ import os from "node:os";
27
+ import { getManagedDir as getManagedDirInternal, type ManagedPathsEnv } from "./managed-paths.js";
28
+
29
+ /** Shared env override surface — `homedir` only, mirrors `ManagedPathsEnv`. */
30
+ export type DashboardPathsEnv = ManagedPathsEnv;
31
+
32
+ /** `~/.pi/dashboard/` — config dir for `config.json`, `server.log`, etc. */
33
+ export function getDashboardConfigDir(env?: DashboardPathsEnv): string {
34
+ return path.join(env?.homedir ?? os.homedir(), ".pi", "dashboard");
35
+ }
36
+
37
+ /** `~/.pi/dashboard/server.log` — the live dashboard server's stdout/stderr log. */
38
+ export function getDashboardServerLogPath(env?: DashboardPathsEnv): string {
39
+ return path.join(getDashboardConfigDir(env), "server.log");
40
+ }
41
+
42
+ /**
43
+ * `~/.pi/dashboard/first-run-done` — sentinel file written by the Electron
44
+ * wizard on completion. Presence means the one-step welcome was shown and
45
+ * acknowledged; subsequent launches skip the wizard.
46
+ *
47
+ * Lives under `~/.pi/dashboard/` (not the legacy `~/.pi-dashboard/`) so it
48
+ * survives Electron whole-app updates and remains the same path across
49
+ * all install layouts.
50
+ *
51
+ * See change: eliminate-electron-runtime-install (Q2 ratification).
52
+ */
53
+ export function getFirstRunMarkerPath(env?: DashboardPathsEnv): string {
54
+ return path.join(getDashboardConfigDir(env), "first-run-done");
55
+ }
56
+
57
+ /** `~/.pi-dashboard/` — managed-install root (npm packages, etc.). Re-export. */
58
+ export function getManagedDir(env?: DashboardPathsEnv): string {
59
+ return getManagedDirInternal(env);
60
+ }
61
+
62
+ /**
63
+ * `~/.pi-dashboard/server.log` — the legacy *installer* log. Distinct from
64
+ * the server log; left here so callers can be explicit about which file
65
+ * they want and the grep tooling has a single canonical reference.
66
+ */
67
+ export function getInstallerLogPath(env?: DashboardPathsEnv): string {
68
+ return path.join(getManagedDir(env), "server.log");
69
+ }
@@ -9,3 +9,5 @@ export * from "./slot-types.js";
9
9
  export * from "./manifest-types.js";
10
10
  export * from "./slot-props.js";
11
11
  export * from "./plugin-status.js";
12
+ export * from "./ui-primitives.js";
13
+ export * from "./intent-types.js";
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Plugin intent protocol — wire-format types.
3
+ *
4
+ * Plugins describe per-session UI contributions as `IntentNode` JSON trees
5
+ * and broadcast them via `ServerPluginContext.broadcastToSubscribers`. The
6
+ * shell on every connected client receives the broadcast, looks up the
7
+ * referenced primitive name in its local UI primitive registry, and renders
8
+ * the resulting React component with the JSON-serializable props.
9
+ *
10
+ * Function references SHALL NOT cross the wire. Action handlers (onClick,
11
+ * onSubmit, etc.) are declared as `ActionDescriptor` objects; the client's
12
+ * IntentRenderer wires them to send `PluginActionMessage` back over the
13
+ * WebSocket when triggered.
14
+ *
15
+ * See change: adopt-server-driven-intent-rendering.
16
+ */
17
+ import type { SlotId } from "./slot-types.js";
18
+
19
+ /**
20
+ * A node in an intent tree.
21
+ *
22
+ * The plugin describes WHAT to render by primitive name. The client's
23
+ * IntentRenderer resolves the name to a `ComponentType` from its local
24
+ * primitive registry.
25
+ */
26
+ export interface IntentNode {
27
+ /** Stable primitive name. Resolved by useUiPrimitive on the client. */
28
+ primitive: string;
29
+ /**
30
+ * Props passed to the resolved component. Values may be primitives,
31
+ * serializable structures, OR nested IntentNodes (rendered recursively
32
+ * as React elements by IntentRenderer).
33
+ */
34
+ props?: Record<string, unknown>;
35
+ /** Optional stable React key for reconciliation. Required for items in lists. */
36
+ key?: string;
37
+ /**
38
+ * Optional action descriptors. The IntentRenderer wires these to send
39
+ * `PluginActionMessage` to the server when the user triggers them.
40
+ */
41
+ actions?: Record<string, ActionDescriptor>;
42
+ }
43
+
44
+ /**
45
+ * What to send back to the server when the user triggers an action.
46
+ *
47
+ * Function references SHALL NOT cross the wire. This is what does instead.
48
+ */
49
+ export interface ActionDescriptor {
50
+ /** The plugin to dispatch the action to (matches manifest id). */
51
+ pluginId: string;
52
+ /** Plugin-defined action name. */
53
+ action: string;
54
+ /** Optional payload. Must be JSON-serializable. */
55
+ payload?: Record<string, unknown>;
56
+ }
57
+
58
+ /**
59
+ * Server → Browser envelope. One message per (slot, session) change from
60
+ * a plugin. The shell on every connected client receives it via the bridge
61
+ * fanout.
62
+ */
63
+ export interface PluginIntentsMessage {
64
+ type: "plugin_intents";
65
+ /** The plugin emitting this intent. */
66
+ pluginId: string;
67
+ /**
68
+ * The session this intent applies to. `null` for global slots like
69
+ * `settings-section` that aren't bound to a single session.
70
+ */
71
+ sessionId: string | null;
72
+ /** The slot this intent occupies. */
73
+ slot: SlotId;
74
+ /** The intent tree. `null` means "clear my contribution to this slot". */
75
+ intent: IntentNode | null;
76
+ }
77
+
78
+ /**
79
+ * Browser → Server envelope. Emitted by the IntentRenderer when the user
80
+ * triggers an `ActionDescriptor` (e.g. a button click) inside a rendered
81
+ * intent tree.
82
+ */
83
+ export interface PluginActionMessage {
84
+ type: "plugin_action";
85
+ /** Matches `ActionDescriptor.pluginId` on the originating intent. */
86
+ pluginId: string;
87
+ /** The session context this action originated in (may be `null`). */
88
+ sessionId: string | null;
89
+ /** Plugin-defined action name. */
90
+ action: string;
91
+ /** Optional payload. */
92
+ payload?: Record<string, unknown>;
93
+ }
@@ -19,7 +19,24 @@ export interface PluginClaim {
19
19
  * Defaults to "general" if omitted.
20
20
  */
21
21
  tab?: SettingsTab;
22
- /** Slot-specific extra config. */
22
+ /**
23
+ * URL path pattern (wouter syntax, must start with "/") for the
24
+ * `shell-overlay-route` slot. First-class field so the slot consumer
25
+ * has a typed contract instead of digging through `config`.
26
+ * Example: "/session/:sid/flow/:flowId/agent/:agentId".
27
+ *
28
+ * See change: fix-flows-plugin-polish (path-as-first-class-claim-field).
29
+ */
30
+ path?: string;
31
+ /**
32
+ * For `shell-overlay-route` claims: which URL parameter holds the session
33
+ * id. The slot consumer uses this to resolve the parent session metadata
34
+ * via `useShellSession`. Defaults to "sid" when omitted.
35
+ *
36
+ * See change: fix-flows-plugin-polish (path-as-first-class-claim-field).
37
+ */
38
+ sessionParam?: string;
39
+ /** Slot-specific extra config (escape hatch — prefer first-class fields). */
23
40
  config?: Record<string, unknown>;
24
41
  /**
25
42
  * Optional exported predicate function name for filtering contributions.
@@ -40,6 +57,27 @@ export interface PluginClaim {
40
57
  shouldRender?: string;
41
58
  }
42
59
 
60
+ /**
61
+ * Declarative requirements a plugin has on its environment.
62
+ * Probed by the dashboard-plugin-runtime; surfaced in PluginStatus.requirements.
63
+ *
64
+ * Names are matched against pi extensions (via /api/packages/installed),
65
+ * binaries (via the shared ToolRegistry), and named service probes from a
66
+ * closed built-in registry. Plugins SHALL NOT register additional service
67
+ * names in V1.
68
+ *
69
+ * See change: add-plugin-activation-ui (Layer 1.5).
70
+ */
71
+ export interface PluginRequirements {
72
+ /** pi extension package identifiers (matched via the same logic
73
+ RECOMMENDED_EXTENSIONS uses: name / id / source / displayName). */
74
+ piExtensions?: string[];
75
+ /** Binaries that must resolve on PATH via the tool-registry. */
76
+ binaries?: string[];
77
+ /** Named service probes (closed built-in registry; "pi-model-proxy" only in V1). */
78
+ services?: string[];
79
+ }
80
+
43
81
  /**
44
82
  * The pi-dashboard-plugin manifest.
45
83
  * Declared as the `pi-dashboard-plugin` field in a package.json,
@@ -65,6 +103,22 @@ export interface PluginManifest {
65
103
  configSchema?: string;
66
104
  /** Slot claims. */
67
105
  claims: PluginClaim[];
106
+ /**
107
+ * Optional declarative requirements probed by the runtime and surfaced as
108
+ * `PluginStatus.requirements` / `missingRequirements`.
109
+ * See change: add-plugin-activation-ui (Layer 1.5).
110
+ */
111
+ requires?: PluginRequirements;
112
+ /**
113
+ * Other plugin ids this plugin requires (hard, transitive). When a
114
+ * dependency is missing from discovery or disabled in config, the loader
115
+ * SHALL skip this plugin's server entry and surface the gap via
116
+ * `PluginStatus.missingDeps`. Cycles soft-fail at discovery (loaded: false
117
+ * for every plugin in the cycle).
118
+ *
119
+ * See change: add-plugin-activation-ui (Layer 2 — dependency graph).
120
+ */
121
+ dependsOn?: string[];
68
122
  /**
69
123
  * When true, the plugin is a test fixture and SHALL be excluded from
70
124
  * production bundles (NODE_ENV=production).
@@ -2,15 +2,97 @@
2
2
  * Plugin status types used in /api/health.plugins[] and WebSocket broadcasts.
3
3
  */
4
4
 
5
+ /**
6
+ * Where pi-coding-agent's loader will (or won't) find this plugin's bridge.
7
+ *
8
+ * - `"packages[]"` — bridge path present in `settings.json#packages[]`
9
+ * (pi reads this; bridge will load on session start)
10
+ * - `"dashboardPluginBridges"` — only present in the legacy key pi ignores
11
+ * (loaded: false expected)
12
+ * - `"none"` — plugin has no bridge entry or registration failed
13
+ *
14
+ * See change: fix-pi-flows-end-to-end (Group 2).
15
+ */
16
+ export type BridgeLoadSource = "packages[]" | "dashboardPluginBridges" | "none";
17
+
18
+ /**
19
+ * Structured probe report for a plugin's declarative `requires`.
20
+ * See change: add-plugin-activation-ui (Layer 1.5).
21
+ */
22
+ export interface PluginRequirementReport {
23
+ piExtensions: { name: string; satisfied: boolean }[];
24
+ binaries: { name: string; satisfied: boolean; resolvedPath?: string }[];
25
+ services: { name: string; satisfied: boolean; error?: string }[];
26
+ }
27
+
28
+ /**
29
+ * Latest bridge-status probe forwarded from the pi-side extension (for
30
+ * status-emitting bridges like flows-anthropic-bridge). Omitted when the
31
+ * plugin has no bridge or hasn't reported yet.
32
+ */
33
+ export interface BridgeProbeSnapshot {
34
+ status: "probing" | "waiting_peers" | "active" | "degraded";
35
+ /** Per-peer probe results keyed by the peer spec (e.g. "@pi/anthropic-messages"). */
36
+ peers: Record<string, { ok: boolean; reason?: string }>;
37
+ /** Unix-ms timestamp when the bridge emitted this snapshot. */
38
+ at: number;
39
+ }
40
+
5
41
  /** Status of a single discovered plugin, reported by /api/health. */
6
42
  export interface PluginStatus {
7
43
  id: string;
44
+ /**
45
+ * Human-readable name from the manifest. Used by the Plugins activation
46
+ * UI. Falls back to `id` at the consumer if absent on legacy payloads.
47
+ * See change: add-plugin-activation-ui.
48
+ */
49
+ displayName: string;
8
50
  enabled: boolean;
9
51
  loaded: boolean;
10
52
  /** Error message if the plugin failed to load or has a conflict. */
11
53
  error?: string;
12
54
  /** Number of slot claims declared in the plugin's manifest. */
13
55
  claims: number;
56
+ /**
57
+ * Where the bridge entry is registered, classified at health-check time.
58
+ * See change: fix-pi-flows-end-to-end Group 2.
59
+ */
60
+ bridgeLoadedFrom?: BridgeLoadSource;
61
+ /** Latest bridge-status probe (only present for status-emitting bridges). */
62
+ lastProbe?: BridgeProbeSnapshot;
63
+ /**
64
+ * Structured probe report against the plugin's declarative `requires`.
65
+ * Populated by the loader after `loadServerEntries` and refreshed on every
66
+ * successful `package_operation_complete` and at most every 30 seconds.
67
+ * Absent when the plugin declares no `requires`.
68
+ * See change: add-plugin-activation-ui.
69
+ */
70
+ requirements?: PluginRequirementReport;
71
+ /**
72
+ * Flat list of unsatisfied requirement names across all three categories.
73
+ * Always `[]` when every requirement is satisfied or the plugin declares
74
+ * no `requires`. Never `undefined`.
75
+ * See change: add-plugin-activation-ui.
76
+ */
77
+ missingRequirements?: string[];
78
+ /**
79
+ * `dependsOn` array verbatim from the manifest. Empty when the plugin
80
+ * has no inter-plugin dependencies.
81
+ * See change: add-plugin-activation-ui (Layer 2 — dependency graph).
82
+ */
83
+ dependsOn?: string[];
84
+ /**
85
+ * Plugin ids that declare THIS plugin in their `dependsOn`. Computed
86
+ * by the loader by inverting the graph. Empty when nothing depends on
87
+ * this plugin.
88
+ */
89
+ dependents?: string[];
90
+ /**
91
+ * Plugin ids from `dependsOn` that are either missing from discovery or
92
+ * disabled in config. Non-empty implies the loader skipped this plugin's
93
+ * server entry (`loaded: false`).
94
+ */
95
+ missingDeps?: string[];
14
96
  }
15
97
 
16
98
  /** WebSocket broadcast sent to all browsers when a plugin's config changes. */
@@ -42,6 +42,10 @@ export interface SlotPropsMap {
42
42
  session: DashboardSession;
43
43
  pluginContext: AnyPluginContext;
44
44
  };
45
+ "session-card-flows": {
46
+ session: DashboardSession;
47
+ pluginContext: AnyPluginContext;
48
+ };
45
49
  "workspace-action-bar": {
46
50
  session: DashboardSession;
47
51
  pluginContext: AnyPluginContext;
@@ -71,6 +75,13 @@ export interface SlotPropsMap {
71
75
  onClose: () => void;
72
76
  pluginContext: AnyPluginContext;
73
77
  };
78
+ "shell-overlay-route": {
79
+ params: Record<string, string>;
80
+ /** DashboardSession metadata resolved from the URL’s session param (`config.sessionParam`, default `"sid"`). Undefined when the URL has no session id or no matching session. */
81
+ session?: DashboardSession;
82
+ onBack: () => void;
83
+ pluginContext: AnyPluginContext;
84
+ };
74
85
  "settings-section": {
75
86
  pluginContext: AnyPluginContext;
76
87
  };
@@ -15,13 +15,15 @@ export type SlotId =
15
15
  | "sidebar-folder-section"
16
16
  | "session-card-action-bar"
17
17
  | "workspace-action-bar"
18
- // (session-card-memory is also react-only; declared below for ordering)
18
+ | "shell-overlay-route"
19
+ // (session-card-memory and session-card-flows are also react-only; declared below for ordering)
19
20
  | "content-inline-footer"
20
21
  | "anchored-popover"
21
22
  | "command-route"
22
23
  | "tool-renderer"
23
24
  // React-or-descriptor slots
24
25
  | "session-card-memory"
26
+ | "session-card-flows"
25
27
  | "session-card-badge"
26
28
  | "content-view"
27
29
  | "content-header-sticky"
@@ -54,6 +56,11 @@ export const SLOT_DEFINITIONS: Record<SlotId, SlotDefinition> = {
54
56
  payloadTier: "react-only",
55
57
  description: "Collapsible block above session list per workspace folder",
56
58
  },
59
+ "shell-overlay-route": {
60
+ multiplicity: "many",
61
+ payloadTier: "react-only",
62
+ description: "Plugin-owned full-screen route mounted at top of the shell (desktop + mobile). Each claim ships a wouter path pattern via `config.path` and a React component receiving { params, onBack, session? }.",
63
+ },
57
64
  "session-card-badge": {
58
65
  multiplicity: "many",
59
66
  payloadTier: "react-or-descriptor",
@@ -69,6 +76,11 @@ export const SLOT_DEFINITIONS: Record<SlotId, SlotDefinition> = {
69
76
  payloadTier: "react-only",
70
77
  description: "Memory/Honcho contributions inside the MEMORY subcard of a session card",
71
78
  },
79
+ "session-card-flows": {
80
+ multiplicity: "many",
81
+ payloadTier: "react-only",
82
+ description: "Flow contributions inside the FLOWS subcard of a session card",
83
+ },
72
84
  "workspace-action-bar": {
73
85
  multiplicity: "many",
74
86
  payloadTier: "react-only",
@@ -179,11 +191,13 @@ type SessionScopedSlot =
179
191
  | "session-card-badge"
180
192
  | "session-card-action-bar"
181
193
  | "session-card-memory"
194
+ | "session-card-flows"
182
195
  | "workspace-action-bar"
183
196
  | "content-view"
184
197
  | "content-header-sticky"
185
198
  | "content-inline-footer"
186
- | "command-route";
199
+ | "command-route"
200
+ | "shell-overlay-route";
187
201
 
188
202
  /** Slot ids whose predicates receive a folder descriptor. */
189
203
  type FolderScopedSlot = "sidebar-folder-section";