@blackbelt-technology/pi-agent-dashboard 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +87 -114
- package/README.md +408 -430
- package/docs/architecture.md +465 -12
- package/package.json +10 -5
- package/packages/extension/package.json +14 -4
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +40 -8
- package/packages/extension/src/__tests__/bridge-entry-id-pi-070.test.ts +174 -0
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +201 -0
- package/packages/extension/src/__tests__/event-forwarder.test.ts +30 -0
- package/packages/extension/src/__tests__/fork-entryid-timing.test.ts +64 -76
- package/packages/extension/src/__tests__/git-info.test.ts +67 -55
- package/packages/extension/src/__tests__/multiselect-list.test.ts +137 -0
- package/packages/extension/src/__tests__/no-session-replacement-calls.test.ts +99 -0
- package/packages/extension/src/__tests__/openspec-poller.test.ts +101 -96
- package/packages/extension/src/__tests__/process-scanner-kill.test.ts +61 -0
- package/packages/extension/src/__tests__/provider-register-reload.test.ts +394 -0
- package/packages/extension/src/__tests__/server-auto-start.test.ts +95 -4
- package/packages/extension/src/__tests__/server-launcher.test.ts +16 -0
- package/packages/extension/src/ask-user-tool.ts +5 -4
- package/packages/extension/src/bridge.ts +171 -17
- package/packages/extension/src/dev-build.ts +1 -1
- package/packages/extension/src/git-info.ts +9 -19
- package/packages/extension/src/multiselect-list.ts +146 -0
- package/packages/extension/src/multiselect-polyfill.ts +43 -0
- package/packages/extension/src/pi-env.d.ts +1 -0
- package/packages/extension/src/process-scanner.ts +72 -38
- package/packages/extension/src/provider-register.ts +304 -16
- package/packages/extension/src/server-auto-start.ts +27 -1
- package/packages/extension/src/server-launcher.ts +83 -27
- package/packages/server/package.json +16 -2
- package/packages/server/src/__tests__/bootstrap-queue.test.ts +120 -0
- package/packages/server/src/__tests__/bootstrap-routes.test.ts +125 -0
- package/packages/server/src/__tests__/bootstrap-state.test.ts +119 -0
- package/packages/server/src/__tests__/browse-endpoint.test.ts +17 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +11 -0
- package/packages/server/src/__tests__/concurrent-launch.test.ts +110 -0
- package/packages/server/src/__tests__/config-api.test.ts +68 -0
- package/packages/server/src/__tests__/crash-recovery.test.ts +88 -0
- package/packages/server/src/__tests__/directory-service.test.ts +234 -8
- package/packages/server/src/__tests__/editor-registry.test.ts +28 -15
- package/packages/server/src/__tests__/extension-register-appimage.test.ts +5 -1
- package/packages/server/src/__tests__/extension-register.test.ts +3 -1
- package/packages/server/src/__tests__/find-port-holders.test.ts +94 -0
- package/packages/server/src/__tests__/fixtures/fork-jsonl-roundtrip.jsonl +8 -0
- package/packages/server/src/__tests__/force-kill-handler.test.ts +57 -8
- package/packages/server/src/__tests__/fork-jsonl-roundtrip.test.ts +49 -0
- package/packages/server/src/__tests__/home-lock-escape-hatch.test.ts +60 -0
- package/packages/server/src/__tests__/home-lock-release.test.ts +85 -0
- package/packages/server/src/__tests__/home-lock.test.ts +308 -0
- package/packages/server/src/__tests__/is-pi-process.test.ts +36 -0
- package/packages/server/src/__tests__/node-guard.test.ts +85 -0
- package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +5 -1
- package/packages/server/src/__tests__/package-manager-wrapper.test.ts +45 -10
- package/packages/server/src/__tests__/pi-version-skew.test.ts +237 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +73 -4
- package/packages/server/src/__tests__/process-manager.test.ts +45 -18
- package/packages/server/src/__tests__/provider-probe.test.ts +287 -0
- package/packages/server/src/__tests__/provider-test-route.test.ts +149 -0
- package/packages/server/src/__tests__/restart-helper.test.ts +111 -0
- package/packages/server/src/__tests__/session-action-handler-headless-reload.test.ts +467 -0
- package/packages/server/src/__tests__/session-action-handler-reload-predicate.test.ts +73 -0
- package/packages/server/src/__tests__/session-action-handler-spawn-error.test.ts +74 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +41 -1
- package/packages/server/src/__tests__/tool-routes.test.ts +277 -0
- package/packages/server/src/__tests__/trusted-networks-config.test.ts +19 -0
- package/packages/server/src/__tests__/trusted-networks-no-oauth-roundtrip.test.ts +126 -0
- package/packages/server/src/__tests__/tunnel-cleanup.test.ts +90 -0
- package/packages/server/src/__tests__/tunnel.test.ts +13 -7
- package/packages/server/src/__tests__/wsl-tmux-probe-cache.test.ts +44 -0
- package/packages/server/src/bootstrap-queue.ts +130 -0
- package/packages/server/src/bootstrap-state.ts +131 -0
- package/packages/server/src/browse.ts +8 -3
- package/packages/server/src/browser-handlers/directory-handler.ts +23 -8
- package/packages/server/src/browser-handlers/session-action-handler.ts +213 -79
- package/packages/server/src/browser-handlers/session-action-helpers.ts +36 -0
- package/packages/server/src/cli.ts +310 -39
- package/packages/server/src/config-api.ts +16 -0
- package/packages/server/src/directory-service.ts +270 -39
- package/packages/server/src/editor-detection.ts +12 -9
- package/packages/server/src/editor-manager.ts +19 -4
- package/packages/server/src/editor-pid-registry.ts +9 -8
- package/packages/server/src/editor-registry.ts +22 -25
- package/packages/server/src/git-operations.ts +1 -1
- package/packages/server/src/headless-pid-registry.ts +7 -20
- package/packages/server/src/home-lock-release.ts +72 -0
- package/packages/server/src/home-lock.ts +389 -0
- package/packages/server/src/node-guard.ts +52 -0
- package/packages/server/src/package-manager-wrapper.ts +207 -47
- package/packages/server/src/pi-core-checker.ts +1 -1
- package/packages/server/src/pi-core-updater.ts +7 -1
- package/packages/server/src/pi-resource-scanner.ts +5 -8
- package/packages/server/src/pi-version-skew.ts +207 -0
- package/packages/server/src/preferences-store.ts +17 -3
- package/packages/server/src/process-manager.ts +403 -222
- package/packages/server/src/provider-probe.ts +234 -0
- package/packages/server/src/restart-helper.ts +141 -0
- package/packages/server/src/routes/bootstrap-routes.ts +88 -0
- package/packages/server/src/routes/openspec-routes.ts +25 -1
- package/packages/server/src/routes/pi-core-routes.ts +24 -1
- package/packages/server/src/routes/provider-auth-routes.ts +8 -8
- package/packages/server/src/routes/provider-routes.ts +43 -0
- package/packages/server/src/routes/recommended-routes.ts +10 -12
- package/packages/server/src/routes/system-routes.ts +20 -33
- package/packages/server/src/routes/tool-routes.ts +153 -0
- package/packages/server/src/server-pid.ts +5 -9
- package/packages/server/src/server.ts +211 -10
- package/packages/server/src/session-api.ts +77 -8
- package/packages/server/src/session-bootstrap.ts +17 -3
- package/packages/server/src/session-diff.ts +21 -21
- package/packages/server/src/terminal-manager.ts +61 -20
- package/packages/server/src/tunnel.ts +42 -28
- package/packages/shared/package.json +10 -3
- package/packages/shared/src/__tests__/{tool-resolver.test.ts → binary-lookup.test.ts} +32 -12
- package/packages/shared/src/__tests__/bootstrap/README.md +133 -0
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +370 -0
- package/packages/shared/src/__tests__/bootstrap/assertions.ts +136 -0
- package/packages/shared/src/__tests__/bootstrap/cube.test.ts +47 -0
- package/packages/shared/src/__tests__/bootstrap/cube.ts +66 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +83 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +89 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +33 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/d-overrides.test.ts.snap +20 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +61 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +33 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +46 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/j-path-gui-minimal.test.ts.snap +12 -0
- package/packages/shared/src/__tests__/bootstrap/families/a-electron.test.ts +156 -0
- package/packages/shared/src/__tests__/bootstrap/families/b-npm-global.test.ts +157 -0
- package/packages/shared/src/__tests__/bootstrap/families/c-dev-monorepo.test.ts +102 -0
- package/packages/shared/src/__tests__/bootstrap/families/d-overrides.test.ts +76 -0
- package/packages/shared/src/__tests__/bootstrap/families/e-stale-partial.test.ts +94 -0
- package/packages/shared/src/__tests__/bootstrap/families/f-cwd-variants.test.ts +87 -0
- package/packages/shared/src/__tests__/bootstrap/families/g-windows-specifics.test.ts +143 -0
- package/packages/shared/src/__tests__/bootstrap/families/h-home-drift.test.ts +64 -0
- package/packages/shared/src/__tests__/bootstrap/families/i-malformed-settings.test.ts +77 -0
- package/packages/shared/src/__tests__/bootstrap/families/index.ts +19 -0
- package/packages/shared/src/__tests__/bootstrap/families/j-path-gui-minimal.test.ts +61 -0
- package/packages/shared/src/__tests__/bootstrap/families/k-dashboard-absent.test.ts +50 -0
- package/packages/shared/src/__tests__/bootstrap/families/l-instance-coordination.test.ts +272 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +58 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/electron-layout.ts +84 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/index.ts +9 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/managed-install.ts +85 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/npm-global-layout.ts +122 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/pi-versions.ts +36 -0
- package/packages/shared/src/__tests__/bootstrap/fixtures/settings-json.ts +39 -0
- package/packages/shared/src/__tests__/bootstrap/harness.smoke.test.ts +220 -0
- package/packages/shared/src/__tests__/bootstrap/harness.ts +413 -0
- package/packages/shared/src/__tests__/bootstrap/scenarios-skipped.ts +125 -0
- package/packages/shared/src/__tests__/bootstrap/scenarios.ts +132 -0
- package/packages/shared/src/__tests__/bridge-register.test.ts +29 -6
- package/packages/shared/src/__tests__/config-openspec.test.ts +106 -0
- package/packages/shared/src/__tests__/config.test.ts +56 -0
- package/packages/shared/src/__tests__/detached-spawn.test.ts +243 -0
- package/packages/shared/src/__tests__/managed-paths.test.ts +60 -0
- package/packages/shared/src/__tests__/no-direct-child-process.test.ts +112 -0
- package/packages/shared/src/__tests__/no-direct-platform-branch.test.ts +174 -0
- package/packages/shared/src/__tests__/no-direct-process-kill.test.ts +105 -0
- package/packages/shared/src/__tests__/no-hardcoded-node-modules-paths.test.ts +176 -0
- package/packages/shared/src/__tests__/no-raw-node-import.test.ts +146 -0
- package/packages/shared/src/__tests__/node-spawn.test.ts +210 -0
- package/packages/shared/src/__tests__/platform-commands.test.ts +108 -0
- package/packages/shared/src/__tests__/platform-exec.test.ts +103 -0
- package/packages/shared/src/__tests__/platform-git.test.ts +194 -0
- package/packages/shared/src/__tests__/platform-npm.test.ts +137 -0
- package/packages/shared/src/__tests__/platform-openspec.test.ts +92 -0
- package/packages/shared/src/__tests__/platform-paths.test.ts +284 -0
- package/packages/shared/src/__tests__/platform-process-scan.test.ts +55 -0
- package/packages/shared/src/__tests__/platform-process.test.ts +160 -0
- package/packages/shared/src/__tests__/platform-runner.test.ts +173 -0
- package/packages/shared/src/__tests__/platform-shell.test.ts +74 -0
- package/packages/shared/src/__tests__/process-identify.test.ts +113 -0
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +40 -7
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +43 -7
- package/packages/shared/src/__tests__/resolve-tool-cli.test.ts +105 -0
- package/packages/shared/src/__tests__/semaphore.test.ts +119 -0
- package/packages/shared/src/__tests__/spawn-mechanism.test.ts +131 -0
- package/packages/shared/src/__tests__/state-replay-entry-id.test.ts +69 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +239 -0
- package/packages/shared/src/__tests__/tool-registry-overrides.test.ts +137 -0
- package/packages/shared/src/__tests__/tool-registry-registry.test.ts +343 -0
- package/packages/shared/src/bootstrap-install.ts +212 -0
- package/packages/shared/src/bridge-register.ts +87 -20
- package/packages/shared/src/browser-protocol.ts +71 -1
- package/packages/shared/src/config.ts +87 -15
- package/packages/shared/src/managed-paths.ts +31 -4
- package/packages/shared/src/openspec-poller.ts +63 -46
- package/packages/shared/src/{tool-resolver.ts → platform/binary-lookup.ts} +125 -25
- package/packages/shared/src/platform/commands.ts +100 -0
- package/packages/shared/src/platform/detached-spawn.ts +305 -0
- package/packages/shared/src/platform/exec.ts +220 -0
- package/packages/shared/src/platform/git.ts +155 -0
- package/packages/shared/src/platform/index.ts +16 -0
- package/packages/shared/src/platform/node-spawn.ts +154 -0
- package/packages/shared/src/platform/npm.ts +162 -0
- package/packages/shared/src/platform/openspec.ts +91 -0
- package/packages/shared/src/platform/paths.ts +276 -0
- package/packages/shared/src/platform/process-identify.ts +126 -0
- package/packages/shared/src/platform/process-scan.ts +94 -0
- package/packages/shared/src/platform/process.ts +168 -0
- package/packages/shared/src/platform/runner.ts +369 -0
- package/packages/shared/src/platform/shell.ts +44 -0
- package/packages/shared/src/platform/spawn-mechanism.ts +124 -0
- package/packages/shared/src/platform/subprocess-adapter.ts +124 -0
- package/packages/shared/src/protocol.ts +23 -0
- package/packages/shared/src/recommended-extensions.ts +18 -2
- package/packages/shared/src/resolve-jiti.ts +62 -3
- package/packages/shared/src/rest-api.ts +26 -0
- package/packages/shared/src/semaphore.ts +83 -0
- package/packages/shared/src/state-replay.ts +9 -0
- package/packages/shared/src/tool-registry/definitions.ts +434 -0
- package/packages/shared/src/tool-registry/index.ts +56 -0
- package/packages/shared/src/tool-registry/overrides.ts +118 -0
- package/packages/shared/src/tool-registry/registry.ts +262 -0
- package/packages/shared/src/tool-registry/strategies.ts +198 -0
- package/packages/shared/src/tool-registry/types.ts +180 -0
package/AGENTS.md
CHANGED
|
@@ -18,8 +18,10 @@ See [docs/architecture.md](docs/architecture.md) for full details.
|
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
npm install # Install dependencies
|
|
21
|
-
npm test # Run all tests (vitest)
|
|
22
|
-
npm run test:watch # Watch mode
|
|
21
|
+
npm test # Run all tests (vitest)
|
|
22
|
+
npm run test:watch # Watch mode
|
|
23
|
+
npm run test:bootstrap # Run the bootstrap resolution harness only
|
|
24
|
+
npm run test:bootstrap:watch # Bootstrap harness in watch mode
|
|
23
25
|
npm run build # Build web client (Vite)
|
|
24
26
|
npm run dev # Start Vite dev server
|
|
25
27
|
npm run reload # Reload all connected pi sessions
|
|
@@ -28,68 +30,17 @@ pi-dashboard # Start dashboard server
|
|
|
28
30
|
pi-dashboard --dev # Start with Vite proxy
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
##
|
|
33
|
+
## Running Tests
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
### Why this matters
|
|
36
|
-
|
|
37
|
-
Integration tests call `createServer()`, which on startup runs destructive sweeps against `$HOME/.pi/dashboard/`:
|
|
38
|
-
- `headlessPidRegistry.cleanupOrphans()` — reads `headless-pids.json` and may SIGTERM PIDs it considers orphans (including the **live pi process running the bridge**).
|
|
39
|
-
- `headlessPidRegistry.killAll()` — SIGTERMs every tracked PID on server shutdown.
|
|
40
|
-
- `editorPidRegistry.cleanupOrphans()` — SIGTERMs orphan code-server processes.
|
|
41
|
-
|
|
42
|
-
If tests run against your real `$HOME`, these sweeps can **kill the pi session that launched the tests**. They also write `.meta.json` sidecars into `~/.pi/agent/sessions/`, racing live bridges and corrupting real session state.
|
|
43
|
-
|
|
44
|
-
### How isolation works
|
|
45
|
-
|
|
46
|
-
Three independent layers (defense-in-depth):
|
|
47
|
-
|
|
48
|
-
1. **Process-level HOME override** (`package.json` `test` / `test:watch` scripts): `HOME=$(mktemp -d -t pi-test-XXXXXX) vitest ...` — vitest starts with a fresh HOME under `os.tmpdir()`.
|
|
49
|
-
2. **globalSetup tripwire** (`packages/shared/src/test-support/setup-home.ts`, wired via `globalSetup` in every `vitest.config.ts`): runs ONCE at vitest boot, throws if `process.env.HOME === os.userInfo().homedir` — aborts the run before any test file loads. Pre-creates `.pi/agent/sessions/` and `.pi/dashboard/` subdirs.
|
|
50
|
-
3. **Production-code guards** (`packages/server/src/test-env-guard.ts`): `headlessPidRegistry.cleanupOrphans`/`killAll` and `editorPidRegistry.cleanupOrphans` no-op with `console.warn` when `process.env.VITEST === "true"` AND HOME matches the real user home.
|
|
51
|
-
|
|
52
|
-
### Running tests safely
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
npm test # Preferred — all isolation layers active
|
|
56
|
-
npm run test:watch # Also safe
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
If you must invoke vitest directly (e.g. for a single file):
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
HOME=$(mktemp -d -t pi-test-XXXXXX) npx vitest run path/to/test.ts
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Without the `HOME=$(mktemp -d)` prefix, the globalSetup tripwire will abort the run with an instructive error — but **don't rely on that alone**: if you stash or delete the tripwire code, the prefix is your only remaining safety net.
|
|
66
|
-
|
|
67
|
-
### Writing integration tests
|
|
68
|
-
|
|
69
|
-
Tests that boot a real `DashboardServer` SHOULD use the `createTestServer()` helper:
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
import { createTestServer } from "@blackbelt-technology/pi-dashboard-server/test-support/test-server.js";
|
|
73
|
-
|
|
74
|
-
const { server, httpPort, piPort, stop } = await createTestServer();
|
|
75
|
-
// httpPort and piPort are OS-assigned (port: 0) — no hard-coded port ranges,
|
|
76
|
-
// no risk of colliding with the live dashboard on :8000 / :9999.
|
|
77
|
-
// ...
|
|
78
|
-
await stop();
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Avoid hard-coding ports like `19070`, `19080`, `19090`, etc. in new tests — the helper handles port allocation and safe defaults (`autoShutdown: false`, `tunnel: false`).
|
|
82
|
-
|
|
83
|
-
### Verifying isolation after test changes
|
|
35
|
+
Pipe test output to a tmp file, then grep — avoids re-running to inspect errors:
|
|
84
36
|
|
|
85
37
|
```bash
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
diff /tmp/before.txt /tmp/after.txt | grep -v "pi-agent-dashboard" # should be empty
|
|
38
|
+
npm test 2>&1 | tee /tmp/pi-test.log # run once, capture all output
|
|
39
|
+
grep -nE 'FAIL|Error|✗|✘' /tmp/pi-test.log # find failures
|
|
40
|
+
grep -n -A 20 'FAIL ' /tmp/pi-test.log # failure + context
|
|
90
41
|
```
|
|
91
42
|
|
|
92
|
-
|
|
43
|
+
Always grep the file — never rerun `npm test` just to see errors.
|
|
93
44
|
|
|
94
45
|
## Cross-Platform QA Testing
|
|
95
46
|
|
|
@@ -121,8 +72,9 @@ make clean # Destroy all cloned VMs
|
|
|
121
72
|
| `src/shared/protocol.ts` | Extension↔Server WebSocket messages |
|
|
122
73
|
| `src/shared/browser-protocol.ts` | Server↔Browser WebSocket messages (all message types including PromptBus `prompt_request`/`prompt_dismiss`/`prompt_cancel` must be in the `ServerToBrowserMessage` union — `as any` switch cases are stripped by esbuild in production) |
|
|
123
74
|
| `src/shared/types.ts` | Data models (Session, Workspace, Event) |
|
|
124
|
-
| `src/shared/config.ts` | Shared config loader (`~/.pi/dashboard/config.json`) |
|
|
125
|
-
| `src/
|
|
75
|
+
| `src/shared/config.ts` | Shared config loader (`~/.pi/dashboard/config.json`). Includes `openspec: OpenSpecPollConfig` block (`pollIntervalSeconds` 5–3600, `maxConcurrentSpawns` 1–16, `changeDetection` `"mtime"\|"always"`, `jitterSeconds` 0–60) with clamping via `parseOpenSpecPollConfig`. See change: optimize-openspec-poll-burst |
|
|
76
|
+
| `src/shared/semaphore.ts` | Tiny FIFO semaphore (`createSemaphore(max)` → `{run, setMax, size}`). Used by `directory-service.ts` to cap concurrent `openspec` CLI spawns. Supports live resize via `setMax(n)` for runtime reconfig. |
|
|
77
|
+
| `src/extension/bridge.ts` | Main extension entry point (composes sync/tracker/flow modules, tracks `isAgentStreaming` in persistent BridgeState). **Invariant**: bridge code MUST NOT call `pi.newSession(...)` / `ctx.fork(...)` / `ctx.switchSession(...)` — enforced by `packages/extension/src/__tests__/no-session-replacement-calls.test.ts`. pi 0.69+ invalidates captured `pi`/`ctx` after these calls; the bridge re-captures state in `session_start` keyed on `event.reason ∈ {"new","fork","resume"}`. **Per-message-fork entryId stamping** (change: fix-per-message-fork): `message_start` events now stamp a `nonce` (no entryId, since the user entry isn't persisted yet on pi 0.69+); `message_end` enrichment is deferred via `setTimeout(0)` (NOT `queueMicrotask`, which resolves inside pi's awaited extension dispatcher and misses `appendMessage`); a wrapped `ctx.sessionManager.appendMessage` emits `entry_persisted { entryId, nonce }` so the client reducer can back-fill the user-message bubble's `entryId`. |
|
|
126
78
|
| `src/extension/bridge-context.ts` | Shared mutable state type + helpers for bridge modules |
|
|
127
79
|
| `src/extension/session-sync.ts` | Session register, replay, and switch/fork handling |
|
|
128
80
|
| `src/extension/model-tracker.ts` | Model/thinking-level/git/name change detection |
|
|
@@ -131,7 +83,7 @@ make clean # Destroy all cloned VMs
|
|
|
131
83
|
| `src/extension/server-probe.ts` | TCP probe to detect running server |
|
|
132
84
|
| `src/shared/server-identity.ts` | Identity-verified health check (`isDashboardRunning`) replacing bare TCP probes |
|
|
133
85
|
| `src/shared/mdns-discovery.ts` | mDNS advertise/discover/browse for `_pi-dashboard._tcp` services |
|
|
134
|
-
| `src/extension/server-launcher.ts` | Auto-start server as detached process |
|
|
86
|
+
| `src/extension/server-launcher.ts` | Auto-start server as detached process; captures **both stdout AND stderr** to `~/.pi/dashboard/server.log` (append mode) by passing `stdoutFd: logFd` alongside `logFd` — parity with `pi-dashboard start`'s `stdio: ["ignore", logFd, logFd]`. Exports pure `buildSpawnDetachedOptions` and `buildReadyTimeoutMessage`; the latter appends a `nodejs/node#58515` upgrade hint when `isKnownBadNode(process.version)` is true. |
|
|
135
87
|
| `src/extension/command-handler.ts` | Command routing: `!`/`!!` bash, `/compact`, slash commands |
|
|
136
88
|
| `src/extension/prompt-expander.ts` | Slash command → prompt template expansion (supports colon-to-hyphen aliasing: `/opsx:cmd` → `opsx-cmd.md`) |
|
|
137
89
|
| `src/extension/dev-build.ts` | Dev build-on-reload helper (client build + server shutdown) |
|
|
@@ -146,10 +98,13 @@ make clean # Destroy all cloned VMs
|
|
|
146
98
|
| `src/client/components/BranchPicker.tsx` | Typeahead branch picker with keyboard navigation |
|
|
147
99
|
| `src/client/components/BranchSwitchDialog.tsx` | Checkout orchestration: dirty-state stash, pop prompt |
|
|
148
100
|
| `src/client/lib/git-api.ts` | Client-side fetch helpers for git API endpoints |
|
|
101
|
+
| `src/client/hooks/useImagePaste.ts` | Reusable clipboard-image-paste hook with controlled/uncontrolled modes. Uncontrolled (`useImagePaste()` with no args): hook owns `pendingImages` in local `useState` — used by `ExploreDialog` whose lifetime IS the dialog. Controlled (`useImagePaste({ images, onImagesChange })`): caller owns the array — used by `<CommandInput>` so App can key pending images by `sessionId`. `imageError` stays local in BOTH modes (auto-clears after 3 s, no value in lifting). Supports image/png, image/jpeg, image/gif, image/webp; 10 MB base64 cap. See change: lift-pending-images-to-app |
|
|
149
102
|
| `src/extension/prompt-bus.ts` | PromptBus — unified prompt routing to registered adapters (TUI, dashboard, custom). First-response-wins, cross-adapter dismissal. |
|
|
150
103
|
| `src/extension/dashboard-default-adapter.ts` | Built-in PromptBus adapter that renders prompts as generic interactive dialogs in dashboard chat |
|
|
151
104
|
| `src/client/lib/prompt-component-registry.ts` | Client-side component registry mapping prompt type strings to render metadata (placement, component) |
|
|
152
|
-
| `src/extension/ask-user-tool.ts` | `ask_user` tool registration (bundled in bridge, registered at session_start to avoid static tool-name conflicts with other extensions).
|
|
105
|
+
| `src/extension/ask-user-tool.ts` | `ask_user` tool registration (bundled in bridge, registered at session_start to avoid static tool-name conflicts with other extensions). `multiselect` dispatches through `polyfillMultiselect` (since pi-coding-agent's `ExtensionUIContext` has no `multiselect` method). Tool description instructs agents: *"UI provides a Select all toggle; do not add one."* See change: ask-user-multiselect-polyfill. |
|
|
106
|
+
| `src/extension/multiselect-polyfill.ts` | `polyfillMultiselect(ctx, title, options, opts)` — thin wrapper around `ctx.ui.custom<T>()` that resolves to `string[]` (confirmed) or `undefined` (cancelled). Used for both single-question and batch sub-question `multiselect` paths in `ask-user-tool.ts`. |
|
|
107
|
+
| `src/extension/multiselect-list.ts` | `MultiSelectList` component implementing pi-tui's `Component` interface. Keyboard contract: `↑↓`/`k`/`j` navigate, `Space` toggles current, `Enter` confirms (selected values in original option order), `Escape` cancels. **No "select all" binding in TUI** — the dashboard adapter provides that affordance. |
|
|
153
108
|
| `src/shared/openspec-activity-detector.ts` | Detects OpenSpec activity from tool events; auto-attach requires only changeName (phase optional) |
|
|
154
109
|
| `src/shared/openspec-poller.ts` | OpenSpec CLI polling (shared, used by server DirectoryService) |
|
|
155
110
|
| `src/shared/state-replay.ts` | Synthesizes events from pi entries (shared, used by server + bridge) |
|
|
@@ -165,10 +120,11 @@ make clean # Destroy all cloned VMs
|
|
|
165
120
|
| `src/server/idle-timer.ts` | Auto-shutdown idle timer with sleep-wake resilience |
|
|
166
121
|
| `src/server/session-bootstrap.ts` | Startup session discovery and OpenSpec polling init |
|
|
167
122
|
| `src/server/pi-gateway.ts` | Extension WebSocket gateway (port 9999) |
|
|
168
|
-
| `src/server/browser-gateway.ts` | Browser WebSocket gateway (dispatches to handler modules
|
|
123
|
+
| `src/server/browser-gateway.ts` | Browser WebSocket gateway (dispatches to handler modules) |
|
|
169
124
|
| `src/server/browser-handlers/handler-context.ts` | Shared context type for browser message handlers |
|
|
170
125
|
| `src/server/browser-handlers/subscription-handler.ts` | Subscribe/unsubscribe with async batched replay, backpressure, lazy loading |
|
|
171
|
-
| `src/server/browser-handlers/session-action-handler.ts` | Send prompt, abort, resume, spawn, shutdown, force kill, flow control |
|
|
126
|
+
| `src/server/browser-handlers/session-action-handler.ts` | Send prompt, abort, resume, spawn, shutdown, force kill, flow control. `handleForceKill` delegates the SIGTERM→wait→SIGKILL escalation to `killProcess` from `platform/process.ts` so Windows gets `taskkill /F /T /PID` (genuine tree kill). No direct `process.kill()` anywhere (enforced by `no-direct-process-kill.test.ts`). `handleSendPrompt` also intercepts `/reload` on headless sessions (gated by `headlessPidRegistry.getPid`) and delegates to `handleHeadlessReload`, which SIGTERMs the pi process and respawns with `--session <file> --mode continue` — pi 0.68.0 has no extension-accessible reload path in RPC mode, so kill-and-respawn is the only way to reload settings/extensions/skills for headless sessions. See changes: route-kill-paths-through-platform, headless-reload-via-respawn. |
|
|
127
|
+
| `src/server/browser-handlers/session-action-helpers.ts` | Pure helpers for session-action-handler. `shouldInterceptReload(msg, headlessPidRegistry)` gates the headless-reload interception: exact `/reload` text, no images, PID tracked in registry. Extracted for testability. |
|
|
172
128
|
| `src/client/components/ImageLightbox.tsx` | Full-size image lightbox with zoom/pan (useZoomPan), Esc/backdrop close |
|
|
173
129
|
| `src/client/components/CollapsedToolGroup.tsx` | Collapsed group of repeated tool calls with expand toggle |
|
|
174
130
|
| `src/client/lib/group-tool-calls.ts` | Groups consecutive identical tool calls for chat display |
|
|
@@ -181,42 +137,58 @@ make clean # Destroy all cloned VMs
|
|
|
181
137
|
| `src/client/components/ArchiveBrowserView.tsx` | Searchable archive browser: date-grouped list, two-level nav to artifact reader |
|
|
182
138
|
| `src/client/hooks/useArchiveListing.ts` | Fetch hook + pure helpers (groupByDate, filterEntries) for archive endpoint |
|
|
183
139
|
| `src/server/openspec-archive.ts` | Scans `openspec/changes/archive/` and returns structured ArchiveEntry list |
|
|
184
|
-
| `packages/server/src/openspec-tasks.ts` | Parses an OpenSpec change's `tasks.md` (top-level `- [ ]` / `- [x]` lines under `## ` headings) and rewrites a single checkbox line atomically (tmp + rename, byte-for-byte preservation of other lines). Exports `parseTasksMarkdown`, `readTasks`, `toggleTask`, plus typed `NotFoundError` / `LineMismatchError` / `NotACheckboxError` consumed by `routes/openspec-routes.ts` to map to HTTP 404/409/400. |
|
|
185
|
-
| `packages/client/src/lib/openspec-tasks-api.ts` | Client fetch helpers for `GET /api/openspec/tasks` and `POST /api/openspec/tasks/toggle`. Throws typed `LineMismatchError` on HTTP 409 so `TasksPopover` can refetch + show a banner without string-matching error text. |
|
|
186
|
-
| `packages/client/src/components/StatePill.tsx` | Compact color-coded pill (zinc/blue/amber/green) showing `deriveChangeState(change)` next to the attached-change badge on the session card. Exports `stateToLabel` and the `STATE_PILL_CLASS` map for reuse. |
|
|
187
|
-
| `packages/client/src/components/TasksPopover.tsx` | Portal-rendered popover (anchored to the session card's `Tasks N/M` button) listing every parseable `tasks.md` checkbox grouped by `## ` heading. Optimistic toggle; HTTP 409 triggers a refetch + “File changed” banner; broadcast-driven openspec poll keeps the card counts fresh after each tick.
|
|
188
140
|
| `src/client/components/SessionOpenSpecActions.tsx` | Session-level OpenSpec: searchable attach dialog, action buttons, detach |
|
|
189
141
|
| `src/client/components/DialogPortal.tsx` | Portal wrapper rendering dialogs at document.body with scroll lock |
|
|
190
|
-
| `src/client/components/PinDirectoryDialog.tsx` | Dialog to pin a directory (wraps PathPicker)
|
|
191
|
-
| `packages/client/src/components/LandingPage.tsx` | Empty-state onboarding view. Renders three cards (Setup credentials → Add folder → Start session) that collapse to ✔ rows once each step is satisfied. Accepts `{ providersReady, pinnedCount, sessionsCount, firstPinnedCwd, onOpenPinDialog, onSpawnSession, navigate }`. Falls back to the legacy `π + "Select a session to get started"` placeholder when no onboarding props are supplied (preserves existing test surface). |
|
|
192
|
-
| `packages/client/src/hooks/useProvidersReady.ts` | Hook observing `GET /api/providers`: returns `{ loading, ready, count }` where `ready=true` iff at least one provider has a non-empty `apiKey`. Refetches on window `focus` and on `provider-auth-event` custom events. Used by `LandingPage` via `App.tsx` to drive Step ① state. |
|
|
142
|
+
| `src/client/components/PinDirectoryDialog.tsx` | Dialog to pin a directory (wraps PathPicker) |
|
|
193
143
|
| `src/client/components/PathPicker.tsx` | Reusable keyboard-first path picker with typeahead directory list |
|
|
194
144
|
| `src/client/lib/browse-api.ts` | Client-side browse API helper for PathPicker |
|
|
195
145
|
| `src/server/browse.ts` | Directory listing logic for browse API endpoint |
|
|
196
146
|
| `src/server/pi-resource-scanner.ts` | Discovers pi extensions, skills, prompts from local, global, and package sources |
|
|
197
|
-
| `src/server/package-manager-wrapper.ts` | Thin adapter around pi's `DefaultPackageManager` with operation serialization, progress forwarding, and session reload
|
|
147
|
+
| `src/server/package-manager-wrapper.ts` | Thin adapter around pi's `DefaultPackageManager` with operation serialization, progress forwarding, and session reload; delegates module resolution to `ToolRegistry.resolveModule("pi-coding-agent")` |
|
|
148
|
+
| `src/shared/tool-registry/registry.ts` | `ToolRegistry` service — single-source resolver for every external binary/module (pi, pi-coding-agent, openspec, npm, node, tsx, git, zrok, pi-dashboard). Ordered strategy chain per tool, per-resolution diagnostic trail, in-memory cache, override-aware |
|
|
149
|
+
| `src/shared/tool-registry/definitions.ts` | Registers the standard tool set. Each definition declares an ordered strategy chain (override → bare-import → managed → npm-global → where) and a classifier (strategy → source). Includes build-time tools `electron` and `node-pty` (registered as `kind: "module"` returning the package directory; resolved hoist-aware via `bareImportPackageDirStrategy` with optional `searchPaths`). See change: register-build-time-tools |
|
|
150
|
+
| `packages/shared/bin/pi-dashboard-resolve-tool.cjs` | Shell-callable resolver wrapper for the tool registry. CommonJS, dependency-free, invokable as `node packages/shared/bin/pi-dashboard-resolve-tool.cjs <tool-name> [--json]`. Mirrors the `override` + `bare-import` strategy semantics for build-time tools (electron, node-pty) so CI workflows / Dockerfiles / postinstall scripts can resolve hoisted-vs-nested layouts without depending on the shared package's TS build. **Strategy chain MUST stay in sync with `tool-registry/definitions.ts`** — enforced socially via cross-reference comments and via `no-hardcoded-node-modules-paths.test.ts`. See change: register-build-time-tools |
|
|
151
|
+
| `src/shared/__tests__/no-hardcoded-node-modules-paths.test.ts` | Repo-level lint: scans `.github/workflows/publish.yml`, `.github/workflows/ci.yml`, `packages/electron/scripts/Dockerfile.build`, and the two `fix-pty-permissions.cjs` postinstalls for hardcoded `node_modules/electron` / `node_modules/node-pty` substrings outside the explicit allowlist. Fails with `file:line:col` citation. Replaces hand-rolled inline `require.resolve(...)` patterns with the registered tool registry. Mirrors `no-direct-process-kill.test.ts` and `no-raw-node-import.test.ts`. See change: register-build-time-tools |
|
|
152
|
+
| `src/shared/tool-registry/strategies.ts` | Reusable resolution strategies: `overrideStrategy`, `managedBinStrategy`, `managedModuleStrategy`, `npmGlobalStrategy`, `whereStrategy`, `bareImportStrategy` (uses `createRequire` for sync probe). All take injectable `StrategyDeps` for tests |
|
|
153
|
+
| `src/shared/tool-registry/overrides.ts` | Read/write `~/.pi/dashboard/tool-overrides.json` (machine-local, separate from `config.json`). Lazy-loaded, atomic write via tmp+rename, malformed-file tolerant |
|
|
154
|
+
| `src/shared/tool-registry/types.ts` | `ToolDefinition`, `Strategy`, `StrategyResult`, `Resolution`, `Source`, `UnknownToolError`, `ModuleResolutionError` |
|
|
155
|
+
| `src/shared/tool-registry/index.ts` | Barrel export + `getDefaultRegistry()` singleton accessor |
|
|
156
|
+
| `src/server/routes/tool-routes.ts` | REST routes: `GET /api/tools`, `GET /api/tools/:name`, `POST /api/tools/rescan`, `PUT/DELETE /api/tools/:name`, `POST /api/tools/diagnostics` (text/plain export) |
|
|
157
|
+
| `packages/shared/src/bootstrap-install.ts` | Shared bootstrap installer (`bootstrapInstall({ packages, managedDir?, progress?, npmArgv?, env?, registry? })`, `bootstrapInstallDefaults`, `ensureManagedDir`, `resolveNpmArgv`). Single entry point for pi/openspec/tsx install into `~/.pi-dashboard/` — called from the Electron wizard (via `packages/electron/src/lib/dependency-installer.ts` wrapper that adds bundled-node + offline-cacache concerns) and from the CLI first-run path (`cli.ts runDegradedModeBootstrap`). See change: unified-bootstrap-install. |
|
|
158
|
+
| `packages/server/src/bootstrap-state.ts` | In-memory bootstrap state store (`createBootstrapState()`, `BootstrapState { status, progress, error, version, compatibility, bridgeRegistrationError }`, `BootstrapStateStore` with get/set/subscribe/dispose plus side-channel `setLastInstallPackages` / `getLastInstallPackages` so `POST /api/bootstrap/retry` re-runs the exact failed set instead of a hard-coded default). Partial `set()` supports `undefined` = clear semantics; `setLastInstallPackages` does NOT trigger subscribers (it's not part of the broadcast snapshot). |
|
|
159
|
+
| `packages/server/src/routes/bootstrap-routes.ts` | REST routes: `GET /api/bootstrap/status`, `POST /api/bootstrap/upgrade-pi` (202+ticketId or 409), `POST /api/bootstrap/retry` (202 if failed, else 409). Trigger callbacks are injected so CLI wires them to `bootstrapInstall` while tests wire them to spies. |
|
|
160
|
+
| `packages/server/src/bootstrap-queue.ts` | In-memory ticket queue (`createBootstrapQueue()`, `enqueue(handler)`, `flushAll()`, `size()`, `clear(reason)`, `onTicketComplete(listener)`). `server.ts` flushes on bootstrap-state transition to "ready" and wires `onTicketComplete` → `bootstrap_ticket_complete` WS broadcast so browsers holding a 202 ticketId learn the queued op's outcome. `session-api.ts gateOrEnqueue` uses the queue to defer session spawn during installs. On `clear`, pending tickets are rejected directly (reject closure stored on the entry) so no caller hangs at shutdown. |
|
|
161
|
+
| `packages/server/src/pi-version-skew.ts` | Pi compatibility range reader (`readPiCompatibility` reads `piCompatibility` from `packages/server/package.json`; `readCurrentPiVersion` via `createRequire`, with `fs.realpathSync` on the registry-resolved bin path so symlinked npm-global launchers resolve to the real module's `package.json`), comparator (`parseVersion`, `compareVersions`, `isBelow`, `isAbove` supporting `0.x` wildcard), + `updateBootstrapCompatibility(store, pkgPath)` that writes the result into `bootstrapState.compatibility` with a 60 s cache. Below-minimum adds a 503-blocking `error` message. **CLI surface**: `cli.ts` calls `logCompatibilityWarning(bootstrapState)` after each `updateBootstrapCompatibility(...)` site and emits a stderr warning — 3-line red block on below-minimum (`⚠ pi X is below the required minimum Y … Run: pi-dashboard upgrade-pi`), single-line advisory on below-recommended, silent when in range. **Currently pinned**: `minimum: "0.70.0"`, `recommended: "0.70.0"`, `maximum: null` (lockstep — no backward compatibility for older pi versions). See changes: pi-zero-seventy-compat, warn-pi-version-skew-in-cli. |
|
|
162
|
+
| `packages/client/src/hooks/useBootstrapStatus.ts` | Client hook for bootstrap state. Fetches `/api/bootstrap/status` on mount, subscribes to `bootstrap-status` `CustomEvent` dispatched by `useMessageHandler` on `bootstrap_status_update` WS broadcasts. Exposes `{ state, isLoading, error, refresh, retry, upgradePi }`. |
|
|
163
|
+
| `packages/client/src/components/BootstrapBanner.tsx` | Banner mounted in `App.tsx` above `<MobileShell>`. Hidden at status="ready" with no compatibility hints; blue "Installing pi…" when installing; red "pi install failed — [Retry]" when failed; amber upgrade hints when `compatibility.upgradeRecommended` or `upgradeDashboard` is true. |
|
|
164
|
+
| `src/client/lib/tools-api.ts` | Client-side fetch helpers for `/api/tools*` (`fetchTools`, `rescanAll`, `rescanOne`, `setOverride`, `clearOverride`, `downloadDiagnostics`) |
|
|
165
|
+
| `src/client/components/ToolsSection.tsx` | Settings → General → **Tools** section. One row per registered tool: status badge, source, truncated path, expand-to-trail, override input, per-row rescan. Top-level: Rescan all / Reset overrides / Export diagnostics |
|
|
198
166
|
| `src/server/npm-search-proxy.ts` | Cached proxy for npm registry search (`keywords:pi-package`) and README fetch |
|
|
199
167
|
| `src/server/routes/package-routes.ts` | REST routes: search, readme, installed, install, remove, update, check-updates |
|
|
200
|
-
| `packages/server/src/pi-core-checker.ts` | Discovers pi ecosystem CORE CLI packages (pi itself, pi-dashboard, pi-model-proxy, etc.) from global npm + `~/.pi-dashboard/node_modules/` and compares against the npm registry. Complements PackageManagerWrapper which only manages extensions in `settings.json packages[]`. 5-min cache. |
|
|
201
|
-
| `packages/server/src/pi-core-updater.ts` | Runs `npm update -g <pkg>` (global install) or `npm update <pkg>` in `~/.pi-dashboard/` (managed install), streams progress via listener, acquires `PackageManagerWrapper.runExclusive()` busy-lock, auto-reloads sessions on any successful update. |
|
|
202
|
-
| `packages/server/src/routes/pi-core-routes.ts` | REST routes: `GET /api/pi-core/versions[?refresh=true]`, `POST /api/pi-core/update` with `{ packages?: string[] }` (empty = update all with `updateAvailable`). 409 when busy, 400 on unknown package names. |
|
|
203
|
-
| `packages/client/src/hooks/usePiCoreVersions.ts` | Fetch + 30-min poll hook for pi core version status. Refetches (with `?refresh=true`) when `pi_core_update_complete` arrives via the `pi-core-event` window event. |
|
|
204
|
-
| `packages/client/src/components/PiCoreVersionsSection.tsx` | Settings → Packages tab section showing core packages with current → latest versions, per-package Update button, Update All, Check Now, live progress/error surfaced from WS `pi_core_update_progress`/`complete`. |
|
|
205
|
-
| `packages/client/src/components/PiUpdateBadge.tsx` | Header badge (count of available pi core updates). Hidden when zero. Clicking navigates to `/settings?tab=packages`. Mounted next to `ServerSelector` in `App.tsx` `headerExtra`. |
|
|
206
168
|
| `src/client/components/SortablePinnedGroup.tsx` | Drag-to-reorder wrapper for pinned directory groups |
|
|
207
169
|
| `src/server/preferences-store.ts` | Global UI preferences (pinned dirs, session order) in `preferences.json` |
|
|
208
170
|
| `src/server/meta-persistence.ts` | Per-session debounced `.meta.json` writer |
|
|
209
171
|
| `src/server/session-scanner.ts` | Startup session discovery by scanning `~/.pi/agent/sessions/` |
|
|
210
172
|
| `src/server/migrate-persistence.ts` | One-time migration from `sessions.json` + `state.json` to `.meta.json` |
|
|
211
173
|
| `src/server/session-order-manager.ts` | Per-cwd session ordering with persistence |
|
|
212
|
-
| `src/server/directory-service.ts` | Server-side session discovery, event loading, and OpenSpec polling |
|
|
174
|
+
| `src/server/directory-service.ts` | Server-side session discovery, event loading, and OpenSpec polling. Uses mtime-gated per-directory cache (`DirCache`), shared FIFO semaphore, and deterministic per-cwd `phaseOffsetMs(cwd, jitterSeconds)` jitter (FNV-1a 32-bit hash). `refreshOpenSpec(cwd)` bypasses the mtime gate but still acquires the semaphore. `reconfigurePolling(cfg)` applies live config changes without a restart. Pi-resources scan lives on its own 5×-interval timer so it doesn't stack with the openspec burst. See change: optimize-openspec-poll-burst |
|
|
213
175
|
| `src/server/pending-fork-registry.ts` | Tracks pending fork operations for session placement |
|
|
214
176
|
| `src/server/pending-resume-registry.ts` | Queues prompts for auto-resume of ended sessions |
|
|
215
177
|
| `src/server/json-store.ts` | Atomic JSON file read/write helpers |
|
|
216
|
-
| `src/server/process-manager.ts` | Session spawning via tmux
|
|
178
|
+
| `src/server/process-manager.ts` | Session spawning: dispatches via `platform/spawn-mechanism.ts` `selectMechanism` → `tmux` / `wt` / `wsl-tmux` / `headless`. All mechanisms forward `sessionFile`/`mode` uniformly via `sessionFlagsToArgv`. Windows headless uses `spawnDetached` primitive with `detached: true` (PGID-equivalent via libuv) and stderr-to-file-fd (crash-visible). |
|
|
179
|
+
| `src/shared/platform/detached-spawn.ts` | Three primitives: `spawnDetached` (libuv-correct detached defaults on every OS), `waitForNoCrash` (negative liveness — did it survive the window?), `waitForReady` (positive liveness — did a probe turn true?). All spawn sites with long-lived detached children delegate here. `SpawnDetachedOptions` exposes optional `stdoutFd` (defaults to `"ignore"`) and `logFd` (stderr, defaults to `"ignore"`); the bridge server-launcher sets both to the same fd for parity with the CLI. |
|
|
180
|
+
| `src/shared/platform/node-version-check.ts` | Pure predicate `isKnownBadNode(version)` + message builder `buildNodeVersionWarning(version)` for Node ranges affected by [nodejs/node#58515](https://github.com/nodejs/node/issues/58515) (22.0.0–22.17.x and 24.1.0–24.2.x). Consumed by `packages/server/src/cli.ts` (preflight warning), `packages/extension/src/server-launcher.ts` (ready-timeout hint), and `packages/electron/src/lib/doctor.ts` (Node runtime compatibility row). |
|
|
181
|
+
| `src/shared/platform/preload-fastify.ts` | Shared resolver `resolvePreloadFastifyPath(): string \| null` — returns absolute native path (never `file://`) to `packages/server/preload-fastify.cjs`. Used by all four server-spawn sites (cli daemon, bridge launcher, Electron lifecycle, restart-helper orchestrator) to inject `--require <preloadPath>` BEFORE `--import <jitiLoader>` so Fastify's CJS chain is cached before any ESM hook runs — sidesteps nodejs/node#58515 race-independently. See change: `preload-fastify-cjs`. |
|
|
182
|
+
| `packages/server/preload-fastify.cjs` | CommonJS preload file — loaded by Node's legacy synchronous CJS loader via `--require`. Populates `require.cache` with `fastify` + `@fastify/ajv-compiler` + `@fastify/ajv-compiler/standalone` so the ESM→CJS translator short-circuits on later imports. MUST stay `.cjs`; MUST NOT use ESM syntax. |
|
|
183
|
+
| `src/shared/platform/spawn-mechanism.ts` | `SpawnMechanism` enum (`tmux`/`wt`/`wsl-tmux`/`headless`) + pure `selectMechanism({ platform, userStrategy, electronMode, available })` selector. `buildWtArgs` builds argv for Windows Terminal `new-tab`. `sessionFlagsToArgv` is the uniform flag builder every mechanism MUST call. |
|
|
184
|
+
| `src/shared/platform/process-identify.ts` | `findPidByMarker` + `isProcessLikePi` + `isPiCommandLine` — consolidates the three `process.platform === "win32"` branches that previously lived inside `session-action-handler.ts`. Windows stubs are documented (command-line lookup goes via `headlessPidRegistry` instead). |
|
|
185
|
+
| `src/shared/platform/process.ts` | **Sole source of process termination + liveness primitives**: `isProcessAlive(pid)` (signal 0), `killProcess(pid, {timeoutMs})` (Windows `taskkill /F /T /PID`, POSIX `SIGTERM` → wait → `SIGKILL` tree kill), `killPidWithGroup(pid, sig)` (POSIX `kill(-pid, sig)`, Windows direct kill). Every `process.kill(...)` call outside this file is banned by `no-direct-process-kill.test.ts`. See change: route-kill-paths-through-platform. |
|
|
186
|
+
| `src/shared/platform/node-spawn.ts` | **Sole source of `node --import <loader> <entry>` argv construction**: `toFileUrl(pathOrUrl)` (idempotent path → file:// URL, handles Windows drive letters on POSIX hosts), `spawnNodeScript(opts)` (wraps both loader and entry positions as file:// URLs before delegating to `platform/exec.ts::spawn`). Every `spawn(process.execPath, ["--import", ...])` call outside this file with raw path arguments is banned by `no-raw-node-import.test.ts`. Fixes `ERR_UNSUPPORTED_ESM_URL_SCHEME` on non-C: Windows drives (e.g. `B:\`) where Node's drive-letter heuristic for entry-script paths has known gaps. See change: fix-windows-entry-script-url. |
|
|
187
|
+
| `src/shared/__tests__/no-raw-node-import.test.ts` | Repo-level lint: scans `packages/*/src/` (excluding `platform/node-spawn.ts` + `resolve-jiti.ts` + `__tests__/`) for `spawn(...)` argv with `"--import"` / `"--loader"` followed by raw identifiers not wrapped in `toFileUrl(...)` / `pathToFileURL(...)`. Mirrors `no-direct-process-kill.test.ts` pattern. |
|
|
188
|
+
| `src/shared/__tests__/no-direct-process-kill.test.ts` | Repo-level lint: scans `packages/*/src/` (excluding platform/ and `__tests__/`) for `process.kill(` calls and fails with file:line if any are found. Mirrors `no-direct-child-process.test.ts`. |
|
|
189
|
+
| `src/shared/__tests__/bootstrap/` | In-memory bootstrap resolution harness (memfs-backed). `harness.ts` (withFakeEnv + layer), `fixtures/` (managed/npm-g/electron/dev-monorepo/settings-json layouts), `assertions.ts` (snapshotTrail + snapshotSettingsDelta with `<HOME>` / `<NPM_ROOT>` normalization), `scenarios.ts` (1080-cell cube: platform × dash × pi × settings × env), `scenarios-skipped.ts` (bulk-skip manifest with documented reasons), `cube.test.ts` (fail-closed sweep), `families/*.test.ts` (30+ registered scenario cells across A-K). Run via `npm run test:bootstrap`. See change: bootstrap-resolution-harness. |
|
|
217
190
|
| `src/server/editor-registry.ts` | Detects available native editors (running processes + CLI) |
|
|
218
191
|
| `src/server/editor-manager.ts` | Lifecycle manager for code-server child processes (spawn, stop, idle, heartbeat) |
|
|
219
|
-
| `packages/server/src/editor-pid-registry.ts` | Persists spawned code-server PIDs to `~/.pi/dashboard/editor-pids.json` and sweeps orphaned code-server processes on server boot (runs in `server.start()` before `fastify.listen`). Verifies ownership via cmdline check against `~/.pi/dashboard/editors/` prefix to avoid killing unrelated user-run code-server instances. SIGTERM → 1s grace → SIGKILL escalation. |
|
|
220
192
|
| `src/server/editor-proxy.ts` | Reverse proxy for `/editor/:id/*` to code-server instances |
|
|
221
193
|
| `src/server/editor-detection.ts` | Auto-detect code-server/openvscode-server binary on PATH |
|
|
222
194
|
| `src/server/routes/editor-routes.ts` | REST routes: editor start, stop, heartbeat, status, detect |
|
|
@@ -226,39 +198,43 @@ make clean # Destroy all cloned VMs
|
|
|
226
198
|
| `src/server/provider-auth-handlers.ts` | Pi provider OAuth handlers (Anthropic, Codex, GitHub Copilot, Gemini CLI, Antigravity) |
|
|
227
199
|
| `src/server/provider-auth-storage.ts` | Read/write ~/.pi/agent/auth.json with lockfile for pi provider credentials |
|
|
228
200
|
| `src/server/routes/provider-auth-routes.ts` | REST routes: provider OAuth authorize/exchange/callback, device-code, API key CRUD |
|
|
201
|
+
| `src/server/routes/provider-routes.ts` | REST routes: custom LLM provider CRUD (`GET/PUT /api/providers`) + **`POST /api/providers/test`** connection probe (reuses `provider-probe.ts`). See change: `hot-reload-custom-providers` |
|
|
202
|
+
| `src/server/provider-probe.ts` | Pure per-API-type probe builders (`buildProbeRequest` for `openai-completions`/`openai-responses`/`anthropic-messages`/`google-generative-ai`) + `resolveProbeApiKey` (handles literal, `$ENV_VAR`, and `***` REDACTED sentinel via injected providers reader) + I/O-bearing `probeProvider` (8 s timeout, never echoes apiKey in error text, caps body excerpts at 500 chars). Used by the `/api/providers/test` route and shared with the bridge's discovery path |
|
|
203
|
+
| `src/extension/provider-register.ts` | Reads `~/.pi/agent/providers.json`, calls `pi.registerProvider()` with auto-discovered models, exports `reloadProviders(pi)` + `onProviderChanged(cb)`. `reloadProviders` diffs the current file against a module-level `lastRegistered` snapshot and applies add/remove/change via `registerEntry` / `pi.unregisterProvider`. Called from the bridge's `credentials_updated` handler BEFORE `modelRegistry.refresh()` so new providers appear in `/model` without a session restart. Every discovered model is enriched via `enrichModelMetadata(id, api, probe)` where `probe` wraps pi's `modelRegistry.find()` (captured from `ctx.modelRegistry` at the first `session_start` event; `model_select` is a fallback capture point) — this resolves accurate `contextWindow`, `maxTokens`, `reasoning`, `cost`, and `input` for catalog-known models (e.g., `proxy/cc/claude-opus-4-7` → 1M ctx / reasoning / Opus pricing) and falls back to api-appropriate defaults otherwise (`anthropic-messages` → 200k/64k, `google-generative-ai` → 1M/65k, `openai-completions` → 128k/16k). The fallback path keeps `input: ["text", "image"]` so pasted images reach vision-capable models without pi-ai's `downgradeUnsupportedImages` stripping them client-side; text-only models either ignore the image silently or return a 400 that the user sees. See changes: enrich-custom-provider-model-metadata, enable-image-input-custom-providers |
|
|
204
|
+
| `src/client/lib/providers-api.ts` | Client-side fetch helper: `testProvider({ name?, baseUrl, apiKey, api })` → structured `{ ok, status?, modelCount?, sample?, error? }`. Used by the **Test** button on the LLM Providers card |
|
|
229
205
|
| `src/client/components/ProviderAuthSection.tsx` | Settings section: OAuth login buttons, device-code modal, API key inputs |
|
|
230
206
|
| `src/server/auth-plugin.ts` | Fastify plugin: auth routes, onRequest hook, WS upgrade validation |
|
|
231
|
-
| `src/server/config-api.ts` | Config REST API: read (redacted), write (partial merge), secret preservation |
|
|
207
|
+
| `src/server/config-api.ts` | Config REST API: read (redacted), write (partial merge), secret preservation. `writeConfigPartial` auth-merge propagates `secret`, `providers`, `allowedUsers`, `bypassHosts`, `bypassUrls` (the last two added by change `fix-trusted-networks-no-oauth` — silently dropped before, breaking Trusted Networks saves for users without OAuth). |
|
|
232
208
|
| `src/client/components/SettingsPanel.tsx` | Settings UI: all dashboard config fields, grouped form, save to server |
|
|
233
209
|
| `src/client/hooks/useAuthStatus.ts` | Client auth status hook and login redirect helper |
|
|
234
210
|
| `src/server/localhost-guard.ts` | Network access guard: `createNetworkGuard` (loopback/trusted/authenticated), `isBypassedHost` (CIDR/wildcard/exact), netmask-to-CIDR helpers |
|
|
235
211
|
| `src/server/server-pid.ts` | PID file management for daemon mode |
|
|
236
|
-
| `src/client/components/ServerSelector.tsx` | Server selector dropdown showing persisted known servers with
|
|
212
|
+
| `src/client/components/ServerSelector.tsx` | Server selector dropdown showing persisted known servers. Availability probing runs **only on dropdown open** (once per open, not on mount, not on a timer, not while closed). Accepts `inFlightSwitchKey` to render a spinner on the entry being switched to. Unreachable entries are `disabled`, rendered with `opacity-50` + `cursor-not-allowed`, and clicks are no-ops. Reachable clicks delegate to the transactional staging-socket switch (see `server-switch.ts`). See change: safe-server-switch |
|
|
213
|
+
| `packages/client/src/lib/staging-socket.ts` | `openStagingSocket(url, {timeoutMs}): Promise<WebSocket>` — single-settle helper that resolves on first `OPEN`, rejects on error/close/timeout, and closes the socket on timeout to avoid leaks. Used by the transactional server-switch. |
|
|
214
|
+
| `packages/client/src/lib/server-switch.ts` | `performServerSwitch(target, deps)` — extracted two-phase transaction (stage → commit) from `App.tsx`'s `handleServerSwitch`. Guarantees the ordering `clearInMemoryState` → `setWsUrl` → `persistLastServer`, and never persists localStorage or clears state on staging failure. Fully unit-tested. |
|
|
215
|
+
| `packages/client/src/components/ConnectionStatusBanner.tsx` | Disconnection banner: appears only after the active WebSocket has been non-`OPEN` for >3s continuously; hidden immediately on reconnect; suppressed during in-flight staging switch. Mounted above `<MobileShell>` in `App.tsx`. |
|
|
237
216
|
| `src/client/components/KnownServersSection.tsx` | Settings section: list/add/remove persisted known remote servers |
|
|
238
217
|
| `src/client/components/NetworkDiscoverySection.tsx` | Settings section: mDNS network scan with "Add" action and label prompt |
|
|
239
218
|
| `src/client/lib/known-servers-api.ts` | Client-side fetch helpers for known servers CRUD and discovery endpoints |
|
|
240
219
|
| `src/server/routes/known-servers-routes.ts` | REST routes: known servers CRUD, on-demand mDNS discovery scan |
|
|
241
220
|
| `src/server/terminal-manager.ts` | PTY lifecycle, ring buffer, spawn/attach/kill terminals |
|
|
242
221
|
| `src/server/terminal-gateway.ts` | Binary WebSocket upgrade handler for `/ws/terminal/:id` |
|
|
243
|
-
| `scripts/fix-pty-permissions.cjs` | Postinstall:
|
|
244
|
-
| `src/server/
|
|
245
|
-
| `src/server/tunnel.ts` | Zrok tunnel with reserved shares for persistent URLs. Serialized creation via in-flight promise (`pendingCreate`) prevents parallel reservations from UI double-clicks. `releaseShare(token)` + `scavengeOrphanZrokProcesses(port)` clean up leaked reservations and processes on startup (unconditional when zrok binary is present, even in `--no-tunnel`), on `/api/restart`, `/api/shutdown`, and `/api/tunnel-disconnect`. Retry cap of 1 prevents cascade leaks; timeout path SIGTERM→SIGKILL escalation + release just-in-time tokens. |
|
|
246
|
-
| `packages/client/vite.config.ts` | Client build. `rollupOptions.output.manualChunks` splits bundle into vendor chunks (`react-vendor`, `markdown`, `syntax`, `diff`, `xterm`, `dnd`, `util`) so the initial chunk is ~570 KB (~150 KB gzipped) instead of a 3 MB monolith — avoids tunnel abort thresholds on large assets. |
|
|
247
|
-
| `packages/client/scripts/precompress.mjs` | Post-build step (runs from `build` / `prepare`): zero-dependency Node script that writes `.gz` siblings for every compressible file in `dist/`. `@fastify/static` serves them with `preCompressed: true` so responses ship a stable `Content-Length` header — avoids streaming-compression quirks in intermediate HTTP/2 proxies. |
|
|
248
|
-
| `packages/server/src/server.ts` | Fastify server. Registers `@fastify/compress` globally with gzip+deflate (brotli intentionally disabled — zrok free proxy stream-resets `content-encoding: br`). Serves static client with `preCompressed: true`. CORS callback allows localhost, the active zrok tunnel URL (via `getTunnelUrl()`), any `*.share.zrok.io` host, and configured origins; on mismatch returns `cb(null, false)` rather than throwing — throwing causes `@fastify/cors` to surface HTTP 500 on every asset response (the hidden root cause of browser-only 500s over zrok tunnels, since Vite's `<script type="module" crossorigin>` entries always request in CORS mode). |
|
|
222
|
+
| `scripts/fix-pty-permissions.cjs` | Postinstall: fix node-pty spawn-helper execute permissions |
|
|
223
|
+
| `src/server/tunnel.ts` | Zrok tunnel with reserved shares for persistent URLs, binary detection, PID tracking, stale cleanup |
|
|
249
224
|
| `src/client/components/TunnelButton.tsx` | Unified tunnel/QR button — tunnel icon when not set up, QR icon when inactive, green QR icon when connected; opens QR dialog with disconnect/setup |
|
|
250
225
|
| `src/client/components/QrCodeDialog.tsx` | QR code dialog showing tunnel URL as scannable QR code with copy, disconnect, and setup buttons |
|
|
251
226
|
| `public/manifest.json` | PWA web app manifest for installability |
|
|
252
227
|
| `public/sw.js` | Minimal service worker for PWA installability |
|
|
253
228
|
| `src/client/components/ZrokInstallGuide.tsx` | OS-aware zrok installation guide view (macOS/Linux/Windows) |
|
|
254
|
-
| `src/server/cli.ts` | CLI entry point with subcommands (start/stop/restart/status) |
|
|
229
|
+
| `src/server/cli.ts` | CLI entry point with subcommands (start/stop/restart/status); `findPortHolders` is cross-platform (netstat/taskkill on Windows, lsof on Unix) and `server.log` is opened append-mode with timestamped headers |
|
|
230
|
+
| `src/server/restart-helper.ts` | Cross-platform `/api/restart` orchestrator: spawns a detached `node -e` child using only Node built-ins (net, http) — no sh/lsof/curl dependency; exports pure `buildOrchestratorScript(params)` for testing |
|
|
231
|
+
| `src/shared/resolve-jiti.ts` | Resolves pi's jiti register hook as a `file://` URL (required for `node --import` on Windows); exports pure `buildJitiRegisterUrl(pkgJsonPath)` helper and `resolveJitiFromAnchor(anchorPath)` for managed-install/system-pi callers |
|
|
232
|
+
| `src/shared/platform/paths.ts` | OS-aware path primitives: `normalizePath`, `samePath` (filesystem-level equality), `parsePathInput` (picker input), `withTrailingSep`, `isFilesystemRoot`. All accept optional trailing `platform: NodeJS.Platform` for testability. Windows multi-drive invariant: A:\, B:\, C:\ never merge; bare `B:` input treated as drive root, not cwd-relative. Exported from `platform/index.ts` as `paths.*` namespace alongside `git.*`, `openspec.*`, `npm.*` |
|
|
233
|
+
| `src/client/lib/session-grouping.ts` | `inferPlatform(samples)` heuristic (backslash/drive-letter = Windows, leading `/` = POSIX) + `groupSessionsByDirectory` that uses `normalizePath`-keyed Maps so sessions group under their pinned folder across separator/case/trailing drift |
|
|
234
|
+
| `src/shared/platform/` | Unified cross-OS primitives (see `index.ts` barrel). Sub-modules: `exec.ts` (**the only module that imports `node:child_process`** — wraps `execSync`/`exec`/`execFile`/`spawn`/`spawnSync` with `windowsHide: true` by default; enforced by `no-direct-child-process.test.ts`), `runner.ts` (the Recipe engine — `run(recipe, input)` resolves binaries via `ToolResolver`, applies timeout / tolerate / error normalization, returns `Result<T>`), `git.ts` / `openspec.ts` / `npm.ts` (Recipe-based tool modules — typed functions like `git.diff(...)`, `openspec.list(...)`, `npm.rootGlobal()` that never touch `child_process` or `process.platform`), `binary-lookup.ts` (`where`/`which`, `.cmd` ext, `ToolResolver` class), `process.ts` (`findPortHolders`, `killProcess`, `isProcessAlive`, `killPidWithGroup`, `parseNetstatListeners`), `process-scan.ts` (`isProcessRunning` via pgrep/tasklist, `parseEtime`), `shell.ts` (`detectShell` for SHELL/COMSPEC, `getTerminalEnvHints`), `commands.ts` (`openBrowser`, `isVirtualMachine`). All exported helpers that depend on OS take an optional `platform: NodeJS.Platform` parameter so tests can exercise both branches without mutating `process.platform`. See changes: consolidate-platform-handlers, platform-command-executor. |
|
|
255
235
|
| `src/shared/rest-api.ts` | REST API type definitions |
|
|
256
236
|
| `scripts/reload-all.sh` | Build bridge + reload all pi sessions |
|
|
257
|
-
| `
|
|
258
|
-
| `packages/shared/src/test-support/setup-home.ts` | Vitest `globalSetup` module. Runs ONCE at vitest boot. Tripwire: throws if `process.env.HOME === os.userInfo().homedir` (i.e. tests would run against real user home), aborting the entire run before any test file loads. Warns if HOME is outside `os.tmpdir()`. Pre-creates `<HOME>/.pi/agent/sessions/` and `<HOME>/.pi/dashboard/`. Wired via `test.globalSetup` in every package's `vitest.config.ts`. |
|
|
259
|
-
| `packages/server/src/test-env-guard.ts` | `isUnsafeTestHomeScan()` — returns true when `VITEST=true` AND HOME matches `os.userInfo().homedir`. Gates destructive sweeps in `headlessPidRegistry.cleanupOrphans/killAll` and `editorPidRegistry.cleanupOrphans` so tests never SIGTERM live pi processes even if higher isolation layers are bypassed. |
|
|
260
|
-
| `packages/server/src/test-support/test-server.ts` | `createTestServer(overrides)` helper — boots a real `DashboardServer` on OS-assigned ports (`port: 0`, `piPort: 0`) with safe defaults (no auto-shutdown, no tunnel). Returns `{ server, httpPort, piPort, stop }`. Prefer over hard-coded `19xxx` port constants in new integration tests. |
|
|
261
|
-
| `docs/release-process.md` | Canonical how-to for cutting a release: commit conventions, Unreleased promotion, version bump, tag + push, CI behavior, manual fallback. |
|
|
237
|
+
| `scripts/sync-versions.js` | Post-bump release helper. Reads every workspace `package.json`, enforces lockstep versions, rewrites every inter-package dep specifier (e.g. `"@blackbelt-technology/pi-dashboard-shared": "^<ver>"`) to the current bumped version. Called by `release-cut` skill AND by `.github/workflows/publish.yml` (defensively) between `npm version -ws` and `npm run build`. Required because the npm CLI does not implement the pnpm/yarn `workspace:` protocol — we use plain semver ranges and sync them at bump time. Cross-ref specifiers use plain `"^<ver>"`; `packages/electron` is `"private": true` so `npm publish -ws` skips it automatically. |
|
|
262
238
|
| `src/client/components/PiResourcesView.tsx` | Content area view for browsing pi extensions, skills, and prompts (with Installed/Packages tabs) |
|
|
263
239
|
| `src/client/components/PackageBrowser.tsx` | Reusable inline package browser: npm search, type filters, install/uninstall, manual URL input |
|
|
264
240
|
| `src/client/components/PackageCard.tsx` | Package card with type badges, downloads, install/uninstall actions |
|
|
@@ -281,24 +257,20 @@ make clean # Destroy all cloned VMs
|
|
|
281
257
|
| `src/client/components/TerminalsView.tsx` | Tabbed terminal container per folder (tab bar, keep-alive, rename) |
|
|
282
258
|
| `src/client/components/EditorView.tsx` | code-server iframe embedding with lazy start and heartbeat |
|
|
283
259
|
| `src/client/components/EditorInstallGuide.tsx` | Platform-specific code-server installation guide |
|
|
284
|
-
| `src/client/components/FolderActionBar.tsx` | Unified action bar per folder: +Session, Terminals(N), Editor, Zed, Pi Resources |
|
|
285
|
-
| `packages/client/src/hooks/useImagePaste.ts` | Shared clipboard-image-paste hook (pendingImages, imageError, handlePaste, removeImage, clearImages). Used by `CommandInput` and `ExploreDialog` so behavior is identical across both prompt surfaces. Exports `MAX_IMAGE_SIZE` and `SUPPORTED_IMAGE_TYPES`. |
|
|
286
|
-
| `packages/client/src/components/ImagePreviewStrip.tsx` | Shared image preview strip: error banner + horizontal thumbnail row with remove buttons; thumbnails open `ImageLightbox`. Used by `CommandInput` and `ExploreDialog`. |
|
|
260
|
+
| `src/client/components/FolderActionBar.tsx` | Unified action bar per folder: +Session, +Terminal, Terminals(N), Editor, Zed, Pi Resources |
|
|
287
261
|
| `src/client/lib/folder-encoding.ts` | Base64url encode/decode for folder paths in URL routes |
|
|
288
262
|
| `src/shared/editor-types.ts` | Editor instance types shared across components |
|
|
289
263
|
| `src/client/components/TerminalCard.tsx` | Sidebar card for terminal sessions (cyan accent) |
|
|
290
|
-
| `src/client/App.tsx` | React app with WebSocket integration |
|
|
264
|
+
| `src/client/App.tsx` | React app with WebSocket integration. Owns per-session ephemeral input state alongside `sessionStates`: `drafts: Map<sid, string>` (persisted to `localStorage`, see `chat-input-draft-and-history`) and `pendingImagesMap: Map<sid, ImageContent[]>` (in-memory only, NOT persisted — see change: lift-pending-images-to-app). Both are passed into `<CommandInput>` as controlled props (`draft`/`onDraftChange`, `images`/`onImagesChange`) so they survive content-area route changes that unmount the chat panel and don't leak across session switches. `wrappedHandleSend` clears both via `clearDraftForSession` + `clearImagesForSession` after a successful send. Module-level `EMPTY_IMAGES = Object.freeze([])` provides a stable empty-array reference so sessions with no pending images don't trigger child re-renders. |
|
|
291
265
|
| `src/client/components/MobileShell.tsx` | Two-panel mobile shell with slide transitions and swipe-back |
|
|
292
266
|
| `src/client/components/MobileActionMenu.tsx` | Kebab menu for session actions on mobile (includes OpenSpec commands) |
|
|
293
267
|
| `src/client/components/MobileOverlay.tsx` | Hamburger button and sidebar overlay for mobile |
|
|
294
268
|
| `src/client/components/SessionHeader.tsx` | Session header with OpenSpec attach/detach, flow launcher, MobileAttachButton |
|
|
295
|
-
| `packages/client/src/components/PiLogo.tsx` | Inline-SVG Pi brand-mark component. `fill="currentColor"`, transparent background, themeable via parent `color`. Used in both sidebar headers (`SessionList.tsx`, `SessionSidebar.tsx`). |
|
|
296
|
-
| `packages/client/src/components/SessionSidebar.tsx` | Alternate sidebar; header brand button renders `<PiLogo size={24} />` (inherits `text-blue-500 hover:text-blue-400`) instead of a literal `π` glyph. |
|
|
297
|
-
| `packages/client/src/components/SessionList.tsx` | Desktop sidebar; header brand button uses `<PiLogo />`. Pin-folder button renders `📌 Add folder` label (tooltip: "Pin a folder to the sidebar") instead of icon-only `📌+`. |
|
|
298
|
-
| `packages/client/src/index.css` | Global styles. `.card-working-pulse` layers a 45° repeating-linear-gradient (amber, `background-size: 28.2843px` = one full diagonal period) over a flat amber tint; `background-position` scrolls **horizontally** by `56.5685px` (2 periods) over 2s linear — scroll must be across stripes, diagonal scroll is pattern-invariant. An opacity pulse `0.6 → 1 → 0.6` runs in parallel over 3s ease-in-out (coprime with 2s stripes so they never lock). `prefers-reduced-motion: reduce` disables both animations but keeps the static stripe pattern as a state cue. `.card-input-pulse` (ask_user) intentionally stays pulse-only — stripes = working, pulse-only = waiting on you. |
|
|
299
|
-
| `packages/client/vite.config.ts` | Client build config. `publicDir: "../../../public"` — resolved relative to `root: "src"`, three `../` hops reach the project-root `public/` directory (icon-192.png, icon-512.png, manifest.json, sw.js). Earlier `"../../public"` silently resolved to a non-existent `packages/public/` and caused all PWA assets to 404 in production. |
|
|
300
269
|
| `src/client/hooks/useSwipeBack.ts` | iOS-style left-edge swipe-back gesture (40px edge zone, document-level listeners) |
|
|
301
270
|
| `src/client/components/ChatView.tsx` | Chat message view with scroll-lock: pauses auto-scroll when user scrolls up, floating scroll-to-bottom button, per-session scroll position persistence |
|
|
271
|
+
| `src/client/components/CommandInput.tsx` | Chat textarea + autocomplete + controlled draft / history / images. Props: `sessionId`, `draft`, `onDraftChange` (draft lifted to App.tsx so it survives navigation), `history` (newest-first user prompts for this session), `images`, `onImagesChange` (pending pasted images lifted to App.tsx as `pendingImagesMap` so they survive route takeover and don't leak across sessions). `ArrowUp`/`ArrowDown` walk history bash-style (only when no dropdown is open AND caret is on first/last line); `Escape` during history mode restores the in-progress draft. See changes: chat-input-draft-and-history, lift-pending-images-to-app |
|
|
272
|
+
| `src/client/lib/draft-storage.ts` | Per-session chat input draft persistence helpers: `readAllDrafts()`, `writeDraft(sid, text)`, `deleteDraft(sid)`, key prefix `chat-draft:`. Wraps `localStorage` in try/catch (private-mode / quota safe). |
|
|
273
|
+
| `src/client/lib/message-history.ts` | `extractUserPromptHistory(messages)` — pure filter+dedup over `ChatMessage[]` returning newest-first user prompts with consecutive duplicates collapsed. Drives `ArrowUp`/`ArrowDown` history recall in `CommandInput`. |
|
|
302
274
|
| `src/client/lib/mobile-depth.ts` | Pure function computing MobileShell depth from route state |
|
|
303
275
|
| `src/client/hooks/useZoomPan.ts` | Reusable zoom/pan hook (wheel, drag, pinch, buttons) |
|
|
304
276
|
| `src/client/hooks/useMessageHandler.ts` | WebSocket message dispatch hook (extracted from App.tsx) |
|
|
@@ -329,8 +301,6 @@ make clean # Destroy all cloned VMs
|
|
|
329
301
|
| `src/client/lib/diff-tree.ts` | Directory tree builder from flat file paths |
|
|
330
302
|
| `src/server/session-api.ts` | REST wrappers for WebSocket-only session operations (prompt, abort, spawn, resume, etc.) |
|
|
331
303
|
| `.pi/skills/pi-dashboard/SKILL.md` | Bundled skill: monitor and control the dashboard from any pi session |
|
|
332
|
-
| `.pi/skills/release-cut/SKILL.md` | Skill: cut a new release — pre-flight (clean tree, tests, build), CHANGELOG `[Unreleased]` → versioned promotion, workspace version bump, `chore(release): v<version>` commit, tag + push develop. Stops at the CI-generated draft GitHub Release per `docs/release-process.md`. |
|
|
333
|
-
| `.pi/skills/release-revoke/SKILL.md` | Skill: revoke/rollback a release — delete GitHub Release (draft or published) via `gh release delete`, remove git tag locally + on origin, `npm deprecate` the version (never `npm unpublish` — blocked after 72h / with dependents), optionally revert the `chore(release): v<version>` commit. Handles partial CI failures gracefully. |
|
|
334
304
|
| `.pi/skills/pi-dashboard/references/api-reference.md` | Complete REST API reference for the skill |
|
|
335
305
|
| `.pi/skills/pi-dashboard/references/recipes.md` | Multi-step orchestration recipes |
|
|
336
306
|
| `.pi/skills/pi-dashboard/scripts/dashboard-api.sh` | Helper script with port auto-detection and auth |
|
|
@@ -359,19 +329,22 @@ make clean # Destroy all cloned VMs
|
|
|
359
329
|
| `packages/electron/scripts/docker-make.sh` | Docker entrypoint: platform-aware native module handling, ZIP for Windows |
|
|
360
330
|
| `packages/electron/scripts/Dockerfile.build` | Docker image for cross-platform builds (node:22-bookworm-slim + build tools) |
|
|
361
331
|
| `packages/electron/scripts/bundle-server.sh` | Bundles dashboard server source + deps into resources/server/ (--source-only for cross-builds) |
|
|
332
|
+
| `packages/electron/offline-packages.json` | Pinned versions of pi-coding-agent / openspec / tsx that get bundled as an offline npm cacache per release (see change: `electron-offline-bundled-packages`) |
|
|
333
|
+
| `packages/electron/scripts/bundle-offline-packages.sh` | Build-time script that runs `npm install --os=<os> --cpu=<cpu> --ignore-scripts` for the pinned versions, tars the resulting `_cacache/` (pax format — ustar is too narrow), writes `manifest.json` with SHA-256, and enforces a 100 MB hard budget. Opt-in via `BUNDLE_OFFLINE_PACKAGES=1`. |
|
|
334
|
+
| `packages/electron/resources/offline-packages/manifest.json` | Offline-cache manifest (`{bundledAt, targetPlatform, tarball, tarballBytes, sha256, packages}`). Consumed at runtime by `dependency-installer.ts` via `resolveOfflinePackages()`. |
|
|
335
|
+
| `packages/electron/resources/offline-packages/npm-cache.tar.gz` | gzipped npm cacache used by the first-run offline install. Extracted to `~/.pi-dashboard/.offline-cache/`, verified by SHA-256, consumed by `npm install --offline`, then deleted to reclaim ~140 MB. |
|
|
336
|
+
| `packages/electron/src/lib/offline-packages.ts` | Pure helpers: `parseOfflineManifest`, `resolveOfflinePackages`, `fileSha256`, `extractOfflineCache`, `buildOfflineInstallArgs`, `selectInstallStrategy`. 19 unit tests in `packages/electron/src/__tests__/offline-packages.test.ts`. |
|
|
337
|
+
| `packages/electron/scripts/bundle-recommended-extensions.sh` | Opt-in (via `BUNDLE_RECOMMENDED_EXTENSIONS=1`) build-time bundler that clones each id in `BUNDLED_EXTENSION_IDS` into `packages/electron/resources/bundled-extensions/<id>/`, records `.bundled-sha`, enforces SPDX allowlist (MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC) and a 15 MB total size budget. See change: bundle-first-party-extensions. |
|
|
338
|
+
| `packages/electron/src/lib/dependency-installer.ts` → `installBundledExtensions` | First-run activation of pre-bundled extensions. Copies `<resourcesPath>/bundled-extensions/<id>/` into pi's git cache (`~/.pi/agent/git/<host>/<path>/`), runs `npm install --omit=dev` if runtime deps declared, then calls `manager.addSourceToSettings(gitUrl)` + `settingsManager.flush()`. Returns activated ids so `installRecommendedExtensions` can skip them with `output: "Already installed (bundled)"`. |
|
|
339
|
+
| `packages/electron/src/lib/wizard-badge.ts` | Pure `classifyProgressBadge(output)` helper (`bundled` / `system` / null) shared between wizard HTML inline JS and unit tests. |
|
|
340
|
+
| `packages/shared/src/recommended-extensions.ts` → `BUNDLED_EXTENSION_IDS` | Single source of truth for which recommended ids ship bundled in the Electron installer. Currently `["pi-anthropic-messages", "pi-flows"]` (see design.md for pi-flows license blocker). |
|
|
362
341
|
| `packages/electron/scripts/docker-make.sh` | Docker entrypoint: bundles server, installs native deps, runs Forge make |
|
|
363
342
|
| `packages/electron/scripts/Dockerfile.build` | Docker image for cross-platform builds (node:22-bookworm-slim + build tools) |
|
|
364
343
|
| `packages/electron/scripts/test-server-launch.sh` | Docker-based test for server launch on clean Linux |
|
|
365
344
|
| `packages/electron/scripts/test-electron-install.sh` | Full e2e Docker test: install, wizard, server launch, health check |
|
|
366
345
|
| `packages/electron/scripts/test-electron-install-inner.sh` | Inner test script run inside Docker container |
|
|
367
346
|
| `packages/electron/resources/icon.png` | Master 1024×1024 app icon (π on dark navy) |
|
|
368
|
-
| `.github/workflows/publish.yml` | CI: builds DMG (macOS), DEB+AppImage (Linux), NSIS+ZIP+portable (Windows) on native runners; publishes npm + GitHub Release |
|
|
369
|
-
| `.github/workflows/deploy-site.yml` | CI: builds marketing site in `/site` with Astro, enforces 50 KB gzipped JS budget, deploys to GitHub Pages via `actions/deploy-pages@v4` on push to `develop` when `site/**` changes |
|
|
370
|
-
| `site/` | Public marketing site (Astro + Tailwind + MDX + Preact). Self-contained, published at `BlackBeltTechnology.github.io/pi-agent-dashboard` (later `pi-dashboard.dev`). Uses Pi-blue palette, Supabase-style playful bento grid, 4-state storytelling hero animation. |
|
|
371
|
-
| `site/src/components/HeroAnimation.tsx` | Preact island — the only hydrated hero component. Cycles through dashboard state screenshots every 6s with motion-one crossfade; pauses on hover/touch; respects `prefers-reduced-motion`. |
|
|
372
|
-
| `site/src/content/features.ts` | Data-driven feature list rendered by `BentoGrid.astro` into the 12-card bento layout. |
|
|
373
|
-
| `site/scripts/screenshots/capture.ts` | Playwright screenshot pipeline. Two modes: `SCREENSHOT_TARGET_URL=<url> npm run screenshots` captures against an existing dashboard (recommended); plain `npm run screenshots` spawns a temp `pi-dashboard` with fixture sessions, captures desktop (1440) + mobile (390), and cleans up. Outputs to `site/public/screenshots/{desktop,mobile}/`. |
|
|
374
|
-
| `site/scripts/check-js-size.mjs` | Enforces the 50 KB gzipped JavaScript budget on `site/dist/**/*.js` (wired to `npm run size` and to the deploy workflow). |
|
|
347
|
+
| `.github/workflows/publish.yml` | CI: builds DMG (macOS), DEB+AppImage (Linux), NSIS+ZIP+portable (Windows) on native runners; publishes npm + GitHub Release. **Two triggers**: (a) push of any `v*` tag (the original path — release-cut skill / hand tag), (b) `workflow_dispatch` from the GitHub Actions UI with a single required `version` input (e.g. `"0.4.1"`). The `prepare` job branches on `github.event_name`: tag-push extracts the version from `GITHUB_REF_NAME`; dispatch validates the input as semver, checks tag uniqueness on origin, bumps every workspace `package.json` via `npm version -ws`, syncs cross-ref specifiers via `scripts/sync-versions.js`, promotes `## [Unreleased]` to a dated `## [<version>]` section in `CHANGELOG.md`, commits + tags + pushes the branch. The publish, electron, and github-release jobs all `needs: prepare` and check out `ref: ${{ needs.prepare.outputs.tag }}` so both trigger paths publish the same tree. |
|
|
375
348
|
|
|
376
349
|
## Build & Restart Workflow
|
|
377
350
|
|