@blackbelt-technology/pi-agent-dashboard 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +19 -30
- package/README.md +69 -1
- package/docs/architecture.md +89 -165
- package/package.json +11 -7
- package/packages/extension/package.json +2 -2
- package/packages/extension/src/__tests__/bridge-default-model-gate.test.ts +47 -0
- package/packages/extension/src/__tests__/bridge-followup-chat-order.test.ts +215 -0
- package/packages/extension/src/__tests__/bridge-followup-multi-entry.test.ts +202 -0
- package/packages/extension/src/__tests__/bridge-queue-update-forward.test.ts +77 -0
- package/packages/extension/src/__tests__/bridge-retry-ordering.test.ts +148 -0
- package/packages/extension/src/__tests__/bridge-shadow-queue-drain.test.ts +221 -0
- package/packages/extension/src/__tests__/bridge-shadow-queue-gate.test.ts +299 -0
- package/packages/extension/src/__tests__/bridge-shutdown-reset.test.ts +238 -0
- package/packages/extension/src/__tests__/bridge-slash-command-routing.test.ts +127 -31
- package/packages/extension/src/__tests__/command-handler.test.ts +105 -3
- package/packages/extension/src/__tests__/fixtures/usage-limit-error-strings.ts +127 -0
- package/packages/extension/src/__tests__/source-detector.test.ts +15 -0
- package/packages/extension/src/__tests__/usage-limit-orderer.test.ts +12 -0
- package/packages/extension/src/bridge-default-model-gate.ts +32 -0
- package/packages/extension/src/bridge.ts +299 -20
- package/packages/extension/src/command-handler.ts +53 -7
- package/packages/extension/src/dashboard-default-adapter.ts +5 -0
- package/packages/extension/src/prompt-bus.ts +15 -0
- package/packages/extension/src/slash-dispatch.ts +30 -15
- package/packages/extension/src/source-detector.ts +13 -5
- package/packages/extension/src/usage-limit-orderer.ts +18 -1
- package/packages/server/bin/pi-dashboard.mjs +62 -14
- package/packages/server/package.json +9 -5
- package/packages/server/src/__tests__/browser-gateway-register-handler.test.ts +69 -0
- package/packages/server/src/__tests__/cli-env-no-clobber.test.ts +46 -0
- package/packages/server/src/__tests__/cli-no-bootstrap-references.test.ts +69 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +9 -10
- package/packages/server/src/__tests__/cli-version.test.ts +151 -0
- package/packages/server/src/__tests__/directory-service-openspec-enabled.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service-refresh-force.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service-specs-mtime.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service-toctou.test.ts +9 -0
- package/packages/server/src/__tests__/directory-service.test.ts +9 -0
- package/packages/server/src/__tests__/doctor-route.test.ts +53 -0
- package/packages/server/src/__tests__/event-wiring-queue-state.test.ts +156 -0
- package/packages/server/src/__tests__/event-wiring-resume-clear.test.ts +105 -0
- package/packages/server/src/__tests__/health-shape.test.ts +35 -12
- package/packages/server/src/__tests__/installed-package-enricher.test.ts +12 -12
- package/packages/server/src/__tests__/is-activity-event.test.ts +4 -7
- package/packages/server/src/__tests__/package-routes.test.ts +6 -2
- package/packages/server/src/__tests__/pi-changelog-routes.test.ts +10 -13
- package/packages/server/src/__tests__/pi-core-checker.test.ts +2 -2
- package/packages/server/src/__tests__/pi-version-skew.test.ts +3 -2
- package/packages/server/src/__tests__/plugin-activation-routes.test.ts +267 -0
- package/packages/server/src/__tests__/plugin-intent-cache.test.ts +75 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +196 -0
- package/packages/server/src/__tests__/reattach-placement.test.ts +9 -0
- package/packages/server/src/__tests__/recommended-routes.test.ts +2 -2
- package/packages/server/src/__tests__/recovery-server.test.ts +203 -0
- package/packages/server/src/__tests__/session-action-handler-clear-queue.test.ts +153 -0
- package/packages/server/src/__tests__/session-action-handler-headless-reload.test.ts +43 -0
- package/packages/server/src/__tests__/session-order-manager.test.ts +9 -0
- package/packages/server/src/__tests__/session-order-reboot.test.ts +9 -0
- package/packages/server/src/__tests__/session-ordering-integration.test.ts +9 -0
- package/packages/server/src/browser-gateway.ts +83 -5
- package/packages/server/src/browser-handlers/directory-handler.ts +69 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +89 -0
- package/packages/server/src/browser-handlers/subscription-handler.ts +23 -0
- package/packages/server/src/changelog-parser.ts +1 -1
- package/packages/server/src/cli.ts +68 -250
- package/packages/server/src/event-status-extraction.ts +14 -62
- package/packages/server/src/event-wiring.ts +23 -10
- package/packages/server/src/memory-session-manager.ts +4 -0
- package/packages/server/src/pi-core-checker.ts +1 -1
- package/packages/server/src/pi-dev-version-check.ts +1 -1
- package/packages/server/src/pi-version-skew.ts +24 -46
- package/packages/server/src/plugin-intent-cache.ts +67 -0
- package/packages/server/src/preferences-store.ts +199 -13
- package/packages/server/src/recovery-server.ts +366 -0
- package/packages/server/src/routes/__tests__/manifest-route.test.ts +138 -0
- package/packages/server/src/routes/doctor-routes.ts +26 -21
- package/packages/server/src/routes/manifest-route.ts +162 -0
- package/packages/server/src/routes/openspec-routes.ts +4 -25
- package/packages/server/src/routes/pi-changelog-routes.ts +5 -24
- package/packages/server/src/routes/pi-core-routes.ts +3 -23
- package/packages/server/src/routes/plugin-activation-routes.ts +193 -0
- package/packages/server/src/routes/recommended-routes.ts +21 -0
- package/packages/server/src/routes/system-routes.ts +73 -11
- package/packages/server/src/server.ts +105 -307
- package/packages/server/src/session-api.ts +5 -63
- package/packages/shared/package.json +1 -1
- package/packages/shared/src/__tests__/binary-lookup-resolveJiti.test.ts +28 -0
- package/packages/shared/src/__tests__/binary-lookup-spawn-env.test.ts +61 -0
- package/packages/shared/src/__tests__/binary-lookup.test.ts +16 -0
- package/packages/shared/src/__tests__/bridge-register.test.ts +67 -0
- package/packages/shared/src/__tests__/ci-electron-no-side-effects.test.ts +129 -0
- package/packages/shared/src/__tests__/config.test.ts +40 -0
- package/packages/shared/src/__tests__/dashboard-paths.test.ts +81 -0
- package/packages/shared/src/__tests__/ensure-windows-path.test.ts +112 -0
- package/packages/shared/src/__tests__/intent-types.test.ts +120 -0
- package/packages/shared/src/__tests__/jiti-packages-parity.test.ts +85 -0
- package/packages/shared/src/__tests__/legacy-managed-dir.test.ts +59 -0
- package/packages/shared/src/__tests__/no-direct-child-process.test.ts +12 -0
- package/packages/shared/src/__tests__/no-electron-execpath-spawn.test.ts +149 -0
- package/packages/shared/src/__tests__/no-flow-command-route-claims.test.ts +71 -0
- package/packages/shared/src/__tests__/no-flow-references-in-shell.test.ts +221 -0
- package/packages/shared/src/__tests__/no-managed-dir-reference.test.ts +134 -0
- package/packages/shared/src/__tests__/no-pi-dashboard-version-jiti-gate.test.ts +41 -0
- package/packages/shared/src/__tests__/no-primitive-direct-import.test.ts +235 -0
- package/packages/shared/src/__tests__/no-server-imports-in-resolver.test.ts +53 -0
- package/packages/shared/src/__tests__/node-spawn-jiti-contract.test.ts +54 -101
- package/packages/shared/src/__tests__/node-spawn.test.ts +29 -13
- package/packages/shared/src/__tests__/pi-package-resolver.test.ts +300 -0
- package/packages/shared/src/__tests__/plugin-activation-contracts.test.ts +74 -0
- package/packages/shared/src/__tests__/plugin-bridge-classify-source.test.ts +73 -0
- package/packages/shared/src/__tests__/plugin-bridge-register-extended.test.ts +17 -5
- package/packages/shared/src/__tests__/plugin-bridge-register-packages.test.ts +233 -0
- package/packages/shared/src/__tests__/plugin-bridge-register.test.ts +19 -9
- package/packages/shared/src/__tests__/publish-workflow-contract.test.ts +154 -15
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +28 -10
- package/packages/shared/src/__tests__/resolver-parity-with-scanner.test.ts +76 -0
- package/packages/shared/src/__tests__/server-identity.test.ts +127 -0
- package/packages/shared/src/__tests__/server-launcher.test.ts +35 -0
- package/packages/shared/src/__tests__/source-matching.test.ts +5 -5
- package/packages/shared/src/__tests__/sync-versions-spec.test.ts +76 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +50 -2
- package/packages/shared/src/bridge-register.ts +35 -2
- package/packages/shared/src/browser-protocol.ts +176 -2
- package/packages/shared/src/config.ts +12 -0
- package/packages/shared/src/dashboard-paths.ts +69 -0
- package/packages/shared/src/dashboard-plugin/index.ts +2 -0
- package/packages/shared/src/dashboard-plugin/intent-types.ts +93 -0
- package/packages/shared/src/dashboard-plugin/manifest-types.ts +55 -1
- package/packages/shared/src/dashboard-plugin/plugin-status.ts +82 -0
- package/packages/shared/src/dashboard-plugin/slot-props.ts +11 -0
- package/packages/shared/src/dashboard-plugin/slot-types.ts +16 -2
- package/packages/shared/src/dashboard-plugin/ui-primitives.ts +287 -0
- package/packages/shared/src/dashboard-starter.ts +22 -0
- package/packages/shared/src/doctor-core.ts +49 -27
- package/packages/shared/src/launch-source-types.ts +9 -9
- package/packages/shared/src/legacy-managed-dir.ts +97 -0
- package/packages/shared/src/mdns-discovery.ts +4 -1
- package/packages/shared/src/pi-package-resolver.ts +388 -0
- package/packages/shared/src/platform/binary-lookup.ts +27 -3
- package/packages/shared/src/platform/ensure-windows-path.ts +95 -0
- package/packages/shared/src/platform/exec.ts +22 -0
- package/packages/shared/src/platform/node-spawn.ts +42 -41
- package/packages/shared/src/plugin-bridge-register.ts +275 -18
- package/packages/shared/src/protocol.ts +94 -2
- package/packages/shared/src/recommended-extensions.ts +34 -10
- package/packages/shared/src/server-identity.ts +74 -5
- package/packages/shared/src/server-launcher.ts +20 -0
- package/packages/shared/src/source-matching.ts +1 -1
- package/packages/shared/src/tool-registry/__tests__/node-script-toargv-fallback.test.ts +84 -0
- package/packages/shared/src/tool-registry/definitions.ts +91 -7
- package/packages/shared/src/types.ts +12 -8
- package/scripts/maybe-patch-package.cjs +44 -0
- package/packages/server/src/__tests__/bootstrap-install-from-list.test.ts +0 -263
- package/packages/server/src/__tests__/bootstrap-queue.test.ts +0 -120
- package/packages/server/src/__tests__/bootstrap-routes.test.ts +0 -125
- package/packages/server/src/__tests__/bootstrap-state.test.ts +0 -119
- package/packages/server/src/__tests__/cli-bootstrap.test.ts +0 -36
- package/packages/server/src/__tests__/event-status-extraction-flow.test.ts +0 -55
- package/packages/server/src/__tests__/legacy-pi-cleanup.test.ts +0 -149
- package/packages/server/src/__tests__/post-install-openspec-refresh.test.ts +0 -180
- package/packages/server/src/__tests__/post-install-rescan.test.ts +0 -134
- package/packages/server/src/__tests__/system-routes-reextract.test.ts +0 -91
- package/packages/server/src/bootstrap-install-from-list.ts +0 -232
- package/packages/server/src/bootstrap-queue.ts +0 -130
- package/packages/server/src/bootstrap-state.ts +0 -159
- package/packages/server/src/legacy-pi-cleanup.ts +0 -151
- package/packages/server/src/routes/bootstrap-routes.ts +0 -125
- package/packages/shared/src/__tests__/bootstrap/README.md +0 -133
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +0 -378
- package/packages/shared/src/__tests__/bootstrap/assertions.ts +0 -136
- package/packages/shared/src/__tests__/bootstrap/cube.test.ts +0 -47
- package/packages/shared/src/__tests__/bootstrap/cube.ts +0 -66
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +0 -84
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +0 -90
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +0 -34
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/d-overrides.test.ts.snap +0 -20
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +0 -62
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +0 -34
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +0 -49
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/j-path-gui-minimal.test.ts.snap +0 -12
- package/packages/shared/src/__tests__/bootstrap/families/a-electron.test.ts +0 -156
- package/packages/shared/src/__tests__/bootstrap/families/b-npm-global.test.ts +0 -157
- package/packages/shared/src/__tests__/bootstrap/families/c-dev-monorepo.test.ts +0 -102
- package/packages/shared/src/__tests__/bootstrap/families/d-overrides.test.ts +0 -76
- package/packages/shared/src/__tests__/bootstrap/families/e-stale-partial.test.ts +0 -94
- package/packages/shared/src/__tests__/bootstrap/families/f-cwd-variants.test.ts +0 -87
- package/packages/shared/src/__tests__/bootstrap/families/g-windows-specifics.test.ts +0 -143
- package/packages/shared/src/__tests__/bootstrap/families/h-home-drift.test.ts +0 -64
- package/packages/shared/src/__tests__/bootstrap/families/i-malformed-settings.test.ts +0 -77
- package/packages/shared/src/__tests__/bootstrap/families/index.ts +0 -19
- package/packages/shared/src/__tests__/bootstrap/families/j-path-gui-minimal.test.ts +0 -61
- package/packages/shared/src/__tests__/bootstrap/families/k-dashboard-absent.test.ts +0 -50
- package/packages/shared/src/__tests__/bootstrap/families/l-instance-coordination.test.ts +0 -272
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +0 -58
- package/packages/shared/src/__tests__/bootstrap/fixtures/electron-layout.ts +0 -84
- package/packages/shared/src/__tests__/bootstrap/fixtures/index.ts +0 -9
- package/packages/shared/src/__tests__/bootstrap/fixtures/managed-install.ts +0 -85
- package/packages/shared/src/__tests__/bootstrap/fixtures/npm-global-layout.ts +0 -122
- package/packages/shared/src/__tests__/bootstrap/fixtures/pi-versions.ts +0 -36
- package/packages/shared/src/__tests__/bootstrap/fixtures/settings-json.ts +0 -39
- package/packages/shared/src/__tests__/bootstrap/harness.smoke.test.ts +0 -220
- package/packages/shared/src/__tests__/bootstrap/harness.ts +0 -413
- package/packages/shared/src/__tests__/bootstrap/scenarios-skipped.ts +0 -125
- package/packages/shared/src/__tests__/bootstrap/scenarios.ts +0 -132
- package/packages/shared/src/__tests__/bootstrap-install-resolve-npm.test.ts +0 -72
- package/packages/shared/src/__tests__/install-managed-node-bootstrap-order.test.ts +0 -68
- package/packages/shared/src/__tests__/install-managed-node.test.ts +0 -192
- package/packages/shared/src/__tests__/installable-list.test.ts +0 -130
- package/packages/shared/src/__tests__/no-installable-list-in-bridge.test.ts +0 -52
- package/packages/shared/src/bootstrap-install.ts +0 -406
- package/packages/shared/src/installable-list.ts +0 -152
- package/packages/shared/src/launch-source-flag.ts +0 -14
package/docs/architecture.md
CHANGED
|
@@ -358,7 +358,7 @@ Descriptor-only slots (existing in `extension-ui-system`): `management-modal`, `
|
|
|
358
358
|
|
|
359
359
|
**Bundled-by-default plugins:** The plugin loader treats all plugins identically (same manifest, same discovery, same `enabled` flag, same failure isolation). What distinguishes "bundled-by-default" plugins (initial set: `git-plugin`) is purely operational — the build pipeline always includes them in `packages/`. Their absence is a deliberate user opt-out, not a normal state. OpenSpec, Flows, and Subagents plugins are bundled in standard builds but their absence is a normal use case (e.g. a workspace without OpenSpec).
|
|
360
360
|
|
|
361
|
-
**Future Work — external plugin discovery:** Phase 1 scans `packages/*/package.json` only. The manifest format (`pi-dashboard-plugin` field in any `package.json`) is intentionally **format-compatible with arbitrary npm packages**, which unblocks an eventual progression where stable plugins can be PR'd into upstream packages (e.g.
|
|
361
|
+
**Future Work — external plugin discovery:** Phase 1 scans `packages/*/package.json` only. The manifest format (`pi-dashboard-plugin` field in any `package.json`) is intentionally **format-compatible with arbitrary npm packages**, which unblocks an eventual progression where stable plugins can be PR'd into upstream packages (e.g. `pi-dashboard-subagents/dashboard/`) and discovered from `node_modules`. The deferred work (trust model, SemVer pinning of the plugin context API, build integration with `node_modules` paths) is documented in `dashboard-plugin-architecture/design.md` §"Future Work: external plugin discovery".
|
|
362
362
|
|
|
363
363
|
#### JSX slot wrappers and `??` fallback chains — anti-pattern
|
|
364
364
|
|
|
@@ -382,124 +382,69 @@ A repository-level lint (`packages/client/src/__tests__/no-jsx-slot-nullish-fall
|
|
|
382
382
|
|
|
383
383
|
**Authoring on-ramp:** Skill package `packages/dashboard-plugin-skill/` ships `@blackbelt-technology/pi-dashboard-plugin-skill` (publishable). Skill name `dashboard-plugin-scaffold`. Hybrid contract: `ask_user` batch up front, prescriptive steps after. Two modes. `new` mode: scaffolds `packages/<id>-plugin/` matching `packages/demo-plugin/` layout. Per-slot stubs for 10 React slots. Optional server entry. Optional bridge entry, default off. `augment` mode: runs in pi session at cwd of existing pi-extension. Grep prelude scans `ctx.ui.*`, `pi.registerTool`, banned `ctx.fork`. LLM analysis vs canonical TUI→dashboard mapping table. Per-callsite `ask_user` multiselect. Injects `pi-dashboard-plugin` field into `package.json`. Writes `src/dashboard/`. Purely additive: no existing source modified. SDK = runtime + shared package exports. No separate SDK package. Skill adds both as deps. Forward-compat contract enforced at scaffold time: top-level manifest field, package-relative paths, no `workspace:*`, exports subpaths match, `requiredApi` set. Augmented external extensions resolve under future `node_modules` discovery scan. Canonical on-ramp. `demo-plugin` = runtime fixture. Skill = authoring fixture.
|
|
384
384
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
`
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
`
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
literal contract from `unified-bootstrap-install` task 4.3 ("registry
|
|
449
|
-
rescan") that was previously narrowed to `rescan("pi")` and left
|
|
450
|
-
`openspec` / `tsx` cached as `not-found` forever.
|
|
451
|
-
|
|
452
|
-
2. **Force-refresh OpenSpec for every known directory** — iterates
|
|
453
|
-
`directoryService.knownDirectories()` and for each cwd calls
|
|
454
|
-
`refreshOpenSpec(cwd)` (bypasses the mtime gate per the
|
|
455
|
-
`fix-openspec-mtime-gate-toctou` design's escape-hatch contract).
|
|
456
|
-
Compares the returned `OpenSpecData` against the prior cache; emits
|
|
457
|
-
`openspec_update` to all browsers when the prior was empty or the
|
|
458
|
-
payload differs. Per-cwd failures are isolated via try/catch so one
|
|
459
|
-
cwd cannot block the others. Concurrency is bounded by the existing
|
|
460
|
-
`OpenSpecPollConfig.maxConcurrentSpawns` semaphore inside
|
|
461
|
-
`directory-service.ts` (default 4).
|
|
462
|
-
|
|
463
|
-
3. **Force-refresh pi-resources for every known directory** — same
|
|
464
|
-
iteration; silent on failure (matches
|
|
465
|
-
`directory-service.ts::schedulePiResourcesTick`).
|
|
466
|
-
|
|
467
|
-
The hook fires once per transition, fire-and-forget so the subscribe
|
|
468
|
-
callback returns synchronously. Because all three install entry points
|
|
469
|
-
(`runDegradedModeBootstrap`, REST `triggerUpgradePi`, REST
|
|
470
|
-
`triggerRetry`) flip the same state, the centralized hook covers every
|
|
471
|
-
caller — the local `registry.rescan("pi")` block in `cli.ts` was
|
|
472
|
-
removed as part of this change.
|
|
473
|
-
|
|
474
|
-
Without this hook, the OpenSpec session-card buttons (`P/D/T/S`
|
|
475
|
-
letters, attach combo, refresh) stayed hidden after a fresh first-run
|
|
476
|
-
install until either the user manually reloaded or up to 30 s elapsed
|
|
477
|
-
— and even then the mtime gate could decline to re-poll if no file
|
|
478
|
-
actually changed since boot.
|
|
479
|
-
|
|
480
|
-
See changes: `unified-bootstrap-install`, `pi-zero-seventy-compat`, `warn-pi-version-skew-in-cli`, `fix-openspec-buttons-after-bootstrap-install`.
|
|
481
|
-
|
|
482
|
-
#### Managed Node runtime
|
|
483
|
-
|
|
484
|
-
Electron resources ship bundled Node under `resources/node/` (Windows: `node.exe` + `npm.cmd` + `npx.cmd` at root; Unix: `bin/node` + `bin/npm` + `bin/npx`). `installManagedNode` (`packages/shared/src/bootstrap-install.ts`) `fs.cp`-copies bundle into `<managedDir>/node/` (default `~/.pi-dashboard/node/`), writes `.version` marker for idempotency. `installStandalone` calls it BEFORE first `bootstrapInstall` so npm shims exist when registry install runs; Doctor calls it unconditionally on every launch as a self-repair step. ToolRegistry `node` + `npm` strategy chains prefer `<managedDir>/node/` ahead of system PATH; `prependManagedNodeToPath(env, managedDir)` (`packages/shared/src/platform/managed-node-path.ts`) injects same dir at HEAD of every spawned child's `PATH` (pi-session, pi-core-updater, headless RPC, server-launcher) so `npm.cmd`/`npx.cmd` resolve without `where npm` returning empty on Windows. Standalone CLI / dev / builds without `resources/node/`: bundled source absent, both helpers no-op, resolver falls through to system PATH. See change: embed-managed-node-runtime.
|
|
485
|
-
|
|
486
|
-
#### Legacy pi detection & cleanup
|
|
487
|
-
|
|
488
|
-
Pi renamed `@mariozechner/pi-coding-agent` → `@earendil-works/pi-coding-agent` at v0.74. Old scope ships only up to v0.73.x; new scope's `bin/pi` symlink collides with legacy install on `npm install -g` (EEXIST), producing silent "no spawning" failures when both exist.
|
|
489
|
-
|
|
490
|
-
`packages/server/src/legacy-pi-cleanup.ts` scans 3 locations: npm-global (`npm root -g` → `<root>/@mariozechner/pi-coding-agent`), npx-cache (`~/.npm/_npx/*/node_modules/@mariozechner/pi-coding-agent`, all hashed entries), managed (`~/.pi-dashboard/node_modules/@mariozechner/pi-coding-agent`). Detection cost: one `npm root -g` (~50ms) + `fs.statSync` per candidate. Read-only.
|
|
491
|
-
|
|
492
|
-
Startup scan: `server.ts` calls `detectLegacyPiInstalls()` once at boot, writes result to `bootstrapState.legacyPiInstalls`. Broadcast via `bootstrap_status_update` WS.
|
|
493
|
-
|
|
494
|
-
REST: `GET /api/bootstrap/legacy-pi` force-refreshes detection. `POST /api/bootstrap/legacy-pi/cleanup` removes all detected installs, re-scans, returns `{results, remaining}`. Both auth-gated.
|
|
495
|
-
|
|
496
|
-
UI: `BootstrapBanner` renders amber "Remove legacy pi (N)" sub-banner when `legacyPiInstalls.length > 0` AND `status === "ready"`. Takes precedence over upgrade-recommended hint (legacy install blocks `upgrade-pi`). Wired via `useBootstrapStatus().cleanupLegacyPi()`.
|
|
497
|
-
|
|
498
|
-
Cleanup actions: npm-global removed via `npm uninstall -g @mariozechner/pi-coding-agent --no-fund --no-audit` so bin symlinks unwound; npx-cache + managed via `fs.rmSync({recursive, force})`. Per-install try/catch — one failure does not abort siblings.
|
|
499
|
-
|
|
500
|
-
Idempotent: empty pre-scan short-circuits the POST without invoking npm; missing path under `fs.rmSync({force:true})` returns `removed: true`. Tests in `packages/server/src/__tests__/legacy-pi-cleanup.test.ts` (12 cases) cover pure helpers, fs-backed detection under HOME-tempdir isolation, and removal idempotency.
|
|
501
|
-
|
|
502
|
-
See change: legacy-pi-cleanup.
|
|
385
|
+
#### Plugin Bridge Registration
|
|
386
|
+
|
|
387
|
+
Dual-write contract. `registerPluginBridge` writes BOTH locations in `~/.pi/agent/settings.json`:
|
|
388
|
+
|
|
389
|
+
- `dashboardPluginBridges["dashboard-<id>"]` — legacy key. Kept for forward compat.
|
|
390
|
+
- `packages[]` — user-visible package list. pi-coding-agent reads only this.
|
|
391
|
+
- `_dashboardManagedPackages` — ownership map. Tracks which `packages[]` entries owned by dashboard vs user. Prevents clobbering user-added entries.
|
|
392
|
+
|
|
393
|
+
pi-coding-agent ignores `dashboardPluginBridges` entirely. Bridge extensions invisible to pi until written to `packages[]`. Symptom of single-write bug: plugin bridge loaded by dashboard but never invoked by pi runtime; `/api/flows-anthropic-bridge/status` reports "no sessions reporting".
|
|
394
|
+
|
|
395
|
+
Reconciliation: one-shot `reconcilePluginBridgePackages` runs at server start. Replays current plugin manifests through `ensurePackageEntry` for every claim with a `bridge` entry. Drops dangling managed entries via `removePackageEntry` when manifest gone. Atomic settings write (tmp + rename).
|
|
396
|
+
|
|
397
|
+
Escape hatch: env `PI_DASHBOARD_DISABLE_PLUGIN_BRIDGE_PACKAGES_WRITE=1` skips `packages[]` write. Legacy key still written. Used for forward-compat testing against pi versions that own `packages[]` differently.
|
|
398
|
+
|
|
399
|
+
Classification helper `classifyBridgeSource(settings, id)` returns `"packages[]"` / `"dashboardPluginBridges"` / `"both"` / `"none"`. `/api/health.plugins[].bridgeLoadedFrom` surfaces it. `"both"` = healthy post-0.5.4. `"dashboardPluginBridges"` only = stale install pre-reconcile.
|
|
400
|
+
|
|
401
|
+
#### Plugin Staleness Detection
|
|
402
|
+
|
|
403
|
+
Detects when client bundle predates installed plugin set. No new REST route. No new WS message.
|
|
404
|
+
|
|
405
|
+
Build time: vite-plugin emits `export const PLUGIN_REGISTRY_HASH = "<sha256>"` into `packages/client/src/generated/plugin-registry.tsx`. Hash computed by `pluginRegistryHash(discoverPlugins())` over `deterministicSerializePlugins` output (sorted manifest fields, stable JSON).
|
|
406
|
+
|
|
407
|
+
Runtime: `/api/health` returns `bundleHash` field. Server computes via same `pluginRegistryHash(discoverPlugins())`. Hash mismatch ⇒ disk has plugins client bundle does not know about (or vice versa).
|
|
408
|
+
|
|
409
|
+
Client: `PluginStalenessBanner` fetches `/api/health` on mount. Compares `bundleHash` against imported `PLUGIN_REGISTRY_HASH`. Mismatch ⇒ render banner with Refresh + Dismiss buttons. Refresh calls `location.reload()`. Dismiss persists in `sessionStorage` key `pi-plugin-staleness-dismissed` (tab-scoped, clears on browser close). Dismissed banner stays hidden until next session.
|
|
410
|
+
|
|
411
|
+
#### Plugin Activation UI
|
|
412
|
+
|
|
413
|
+
Settings ▸ Plugins tab lists every discovered plugin (enabled or not) with display name, description, enable/disable toggle, missing-requirement chips, inline Install affordances.
|
|
414
|
+
|
|
415
|
+
**Toggle workflow.** `PluginsSection` calls `POST /api/plugins/:id/toggle` (`packages/server/src/routes/plugin-activation-routes.ts`). Route writes `plugins.<id>.enabled` via config-api partial merge, broadcasts `plugin_config_update { id, config }` to every browser. Effect is **restart-required**: runtime claim filter (`SlotRegistry.setEnabledSet`) only re-reads enabled-set when client mounts or receives `plugin-config-update` event for the bundle's current plugin set; flipping `enabled` for a plugin whose server entry already loaded doesn't unload it. UI surfaces restart-required banner by comparing toggle timestamp to `/api/health.startedAt`.
|
|
416
|
+
|
|
417
|
+
**Declarative requirements.** Plugins declare `requires: { piExtensions?, binaries?, services? }` in their manifest (`PluginManifest.requires`, validated by `manifest-validator.ts`). At plugin load, `loader.ts` runs `runRequirementProbes(manifest.requires, requirementDeps)` from `packages/dashboard-plugin-runtime/src/server/requirement-probes.ts`. Probes:
|
|
418
|
+
|
|
419
|
+
- `probePiExtension(id)` cross-refs installed pi-extension set (deps.listInstalled).
|
|
420
|
+
- `probeBinary(name)` resolves via tool registry.
|
|
421
|
+
- `probeService(name)` dispatches to service-probe map (e.g. `service-probes/pi-model-proxy.ts::detectPiModelProxy`).
|
|
422
|
+
|
|
423
|
+
Results populate `PluginStatus.requirements` + flat `missingRequirements: string[]`; surfaced via `GET /api/plugins`. 30s in-process cache keyed by category+name. `server.ts` invokes `refreshRequirementProbesFor(pluginIds)` on every successful `package_operation_complete` + broadcasts `plugin_config_update` for any plugin whose missing-set changed — install/uninstall of a pi-extension lights up dependent plugin without restart.
|
|
424
|
+
|
|
425
|
+
**UI cross-references.** `RecommendedExtensions.tsx` reads `EnrichedRecommendedExtension.dashboardPluginInstalled` (computed server-side in `recommended-routes.ts::enrichEntry` from `RecommendedExtension.dashboardPlugin`) + renders `+plugin: <id>` badge linking to Plugins tab. `pi-memory-honcho` declares `dashboardPlugin: "honcho"`; `honcho-plugin` declares `requires.piExtensions: ["pi-memory-honcho"]` so install paths converge.
|
|
426
|
+
|
|
427
|
+
**Restart-required model.** `usePluginEnabledSet` snapshots `/api/health.startedAt` ISO timestamp on first load. Subsequent `plugin_config_update` events update enabled-set live for claim filtering, but components that consumed plugin server entries (already loaded) require a restart to drop. `PluginsSection` compares toggle time to snapshot and renders "Restart required" banner when divergent.
|
|
428
|
+
|
|
429
|
+
**Settings consolidation.** Plugin-contributed `settings-section` claims render only under owning plugin row in Settings ▸ Plugins. Legacy `claim.tab` manifest field preserved for back-compat manifests; `SettingsPanel` no longer consumes it. See change: add-plugin-activation-ui (settings-consolidation).
|
|
430
|
+
|
|
431
|
+
### Bootstrap & First Run (R3, immutable bundle)
|
|
432
|
+
|
|
433
|
+
pi/openspec/tsx are regular npm dependencies of `@blackbelt-technology/pi-dashboard-server`. There is no runtime install pyramid. All three arms (Electron, standalone `npm i -g`, bridge) start ready.
|
|
434
|
+
|
|
435
|
+
- **Electron** — reads server resources from `<resourcesPath>/server/node_modules/` (immutable, read-only). Updates land via electron-updater whole-app replacement. See [electron-immutable-bundle.md](./electron-immutable-bundle.md) and [electron-bootstrap-flow.md](./electron-bootstrap-flow.md) for the 6-state startup machine.
|
|
436
|
+
- **Standalone (`npm i -g @blackbelt-technology/pi-agent-dashboard`)** — npm resolves pi/openspec/tsx at install time via regular `dependencies`. Server binds port 8000 immediately; `cli.ts runForeground` logs `[bootstrap] ready (pi resolved via <source>)` after a single `ToolRegistry.resolve("pi")` call. Failure throws hard citing corrupted `node_modules/`.
|
|
437
|
+
- **Bridge** — pi loads the bridge extension; bridge auto-starts the server. pi-core update path remains via `pi-core-routes.ts` (writable target).
|
|
438
|
+
|
|
439
|
+
`launchSource` (returned by `/api/health`) is `"electron" | "standalone" | "bridge"`, derived from `DASHBOARD_STARTER`. Client uses it via `useLaunchSource()` to hide pi-core update UI on Electron (immutable bundle has no writable target).
|
|
440
|
+
|
|
441
|
+
Compatibility skew helpers in `pi-version-skew.ts` (`readPiCompatibility`, `readCurrentPiVersion`, `computeCompatibility`) survive as pure helpers. The pinned range is `minimum: "0.70.0"`, `recommended: "0.70.0"`, `maximum: null` (lockstep — one supported pi means no conditional code paths in the bridge).
|
|
442
|
+
|
|
443
|
+
#### Legacy `~/.pi-dashboard/` advisory
|
|
444
|
+
|
|
445
|
+
Pre-R3 builds installed pi/openspec/tsx into `~/.pi-dashboard/node_modules/` at runtime. R3 leaves that directory untouched. `detectLegacyManagedDir({ homedir })` in `packages/shared/src/legacy-managed-dir.ts` returns `{present, path, pkgCount, sizeMb}`. Doctor surfaces a warning-severity row "Legacy install directory" with a `rm -rf <path>` suggestion. Server `cli.ts` logs the path once at startup after the `[bootstrap] ready` line. Repo-lint `no-managed-dir-reference.test.ts` blocks any new write into the legacy directory from `packages/electron/src/lib/`, `packages/server/src/`, or `packages/shared/src/` outside the explicit allowlist.
|
|
446
|
+
|
|
447
|
+
See change: eliminate-electron-runtime-install.
|
|
503
448
|
|
|
504
449
|
### Force Kill Escalation
|
|
505
450
|
The Stop button supports two-click escalation for stuck sessions:
|
|
@@ -651,15 +596,21 @@ The sidebar splits each folder's session cards into two tiers (alive on top, end
|
|
|
651
596
|
|
|
652
597
|
Both halves share one mental model: "the session you just acted on appears at the top of its new tier." No protocol changes — the existing `sessions_reordered` broadcast carries the new order. See change `top-of-tier-on-status-change`.
|
|
653
598
|
|
|
654
|
-
###
|
|
655
|
-
|
|
599
|
+
### Shell overlay routing
|
|
600
|
+
Shell-owned content overlays URL-driven via wouter routes. Supersedes priority-chain helper from `fix-desktop-back-navigation`.
|
|
656
601
|
|
|
657
|
-
|
|
658
|
-
-
|
|
659
|
-
-
|
|
660
|
-
-
|
|
602
|
+
Routes:
|
|
603
|
+
- `/folder/:encodedCwd/openspec/:changeName/:artifactId` — OpenSpec preview.
|
|
604
|
+
- `/folder/:encodedCwd/openspec/archive` — archive browser.
|
|
605
|
+
- `/folder/:encodedCwd/openspec/specs` — specs browser.
|
|
606
|
+
- `/folder/:encodedCwd/readme` — README preview.
|
|
607
|
+
- `/folder/:encodedCwd/pi-resources` — pi resources view.
|
|
608
|
+
- `/session/:id/diff` — file diff view.
|
|
609
|
+
- `/pi-resource?path=&title=` — cross-folder file preview.
|
|
661
610
|
|
|
662
|
-
|
|
611
|
+
`App.tsx` matches via `useRoute`. URL builders in `packages/client/src/lib/route-builders.ts`. Back-arrow (desktop + mobile) calls `goBackOrHome(navigate)` from `packages/client/src/lib/history-back.ts` — `window.history.back()` when `history.length > 1`, else `navigate("/")`. No priority chain, no overlay state.
|
|
612
|
+
|
|
613
|
+
Plugin content-view claims (e.g. flows-plugin) remain predicate-driven, out of scope for shell routing. See change `overlay-url-routing`.
|
|
663
614
|
|
|
664
615
|
### Model & Thinking Level Flow
|
|
665
616
|
1. Bridge sends current model and thinking level in `session_register` on connect
|
|
@@ -817,7 +768,7 @@ Package operations use pi's `DefaultPackageManager` API on the server, serialize
|
|
|
817
768
|
|
|
818
769
|
**Pi Core Version Check (separate from extension management):**
|
|
819
770
|
- `GET /api/pi-core/versions[?refresh=true]` — returns `PiCoreStatus` with all discovered pi ecosystem CLI packages (pi itself, pi-dashboard, pi-model-proxy, bare `pi-*` and scoped `@x/pi-*`), their installed version, latest npm-registry version, `updateAvailable` flag, and `installSource` (`"global"` via `npm list -g --depth=0 --json` vs `"managed"` in `~/.pi-dashboard/node_modules/`). Cached 5 min.
|
|
820
|
-
- `POST
|
|
771
|
+
- `POST` to the pi-core update endpoint with `{ packages?: string[] }` — updates the listed packages, or all packages with `updateAvailable` when omitted. Runs `npm update -g <pkg>` (global) or `npm update <pkg>` against a managed install when present. Shares the `PackageManagerWrapper.runExclusive()` busy-lock with extension operations — returns 409 on contention. **Standalone + bridge arms only**; Electron hides this UI under R3 because the bundle is read-only (immutable; updates land via electron-updater whole-app replacement).
|
|
821
772
|
|
|
822
773
|
Why a separate system? Pi's `DefaultPackageManager` only manages packages listed in `settings.json packages[]` (extensions/skills/prompts/themes). The pi CLI binary itself and the dashboard server package are installed directly via `npm -g` (or into `~/.pi-dashboard/` in the Electron case) and are invisible to pi's manager. `PiCoreChecker` + `PiCoreUpdater` (`pi-core-checker.ts` + `pi-core-updater.ts`) fill that gap.
|
|
823
774
|
|
|
@@ -827,7 +778,7 @@ Core update progress delivered via typed `pi_core_update_progress` / `pi_core_up
|
|
|
827
778
|
|
|
828
779
|
- Settings tab renders single `<UnifiedPackagesSection>`.
|
|
829
780
|
- Three sub-groups in priority order: Core → Recommended → Other.
|
|
830
|
-
- **Core**: strict whitelist from `pi-core-checker.ts#CORE_PACKAGE_NAMES`. Update via
|
|
781
|
+
- **Core**: strict whitelist from `pi-core-checker.ts#CORE_PACKAGE_NAMES`. Update via the pi-core update endpoint. No Uninstall. Hidden when `launchSource === "electron"` (immutable bundle).
|
|
831
782
|
- **Recommended Extensions**: rows where `isRecommended` true on `/api/packages/installed` response (server-side cross-reference against `RECOMMENDED_EXTENSIONS` manifest).
|
|
832
783
|
- **Other Packages**: every remaining installed row.
|
|
833
784
|
- Each package classified into exactly one group. Core wins over Recommended wins over Other (dedupe).
|
|
@@ -876,15 +827,11 @@ The `ArchiveBrowserView` provides a searchable, date-grouped listing of archived
|
|
|
876
827
|
|
|
877
828
|
### Content View Management
|
|
878
829
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
**Mutual exclusivity**: A `clearAllContentViews()` helper resets all content view states. It is called before opening any new content view, ensuring the previous view is always dismissed. This combines `clearAppContentViews()` (App-level states: preview, specs browser, archive browser, diff view, flow YAML, architect detail, flow agent detail) with `clearContentViews()` from `useContentViews` (pi resources, pi resource file preview, readme preview).
|
|
882
|
-
|
|
883
|
-
**Session switch**: When the selected session changes, `clearAllContentViews()` is called to dismiss any open content view.
|
|
830
|
+
Content area (right panel) shows one view at a time: ChatView, ArchiveBrowserView, SpecsBrowserView, PiResourcesView, MarkdownPreviewView (readme, pi resource file, flow YAML, OpenSpec artifact), FileDiffView, FlowArchitectDetail, FlowAgentDetail.
|
|
884
831
|
|
|
885
|
-
|
|
832
|
+
Shell overlays dispatch by route match (see Shell overlay routing). Mutual exclusivity intrinsic — only one route active at a time. No `clearAllContentViews` helper, no `onBeforeOpen`. Session switch navigates; route change unmounts previous view.
|
|
886
833
|
|
|
887
|
-
|
|
834
|
+
Plugin content-view claims (flows-plugin) still predicate-driven via SlotRegistry. See change `overlay-url-routing`.
|
|
888
835
|
|
|
889
836
|
### Network Access Control
|
|
890
837
|
|
|
@@ -1365,7 +1312,7 @@ The dashboard supports browser-based authentication with pi's LLM providers, ena
|
|
|
1365
1312
|
|
|
1366
1313
|
### Model metadata enrichment for custom providers
|
|
1367
1314
|
|
|
1368
|
-
Custom-provider `/v1/models` endpoints only advertise `{id, owned_by}` — do not expose `context_window`, `max_tokens`, `cost`, `reasoning`. Rather than hardcode flat 200k / 16k / $0 / no-reasoning on every discovered model (silently wrong for proxied frontier models like `proxy/cc/claude-opus-4-7` → Opus 4.7's 1M window), bridge's `registerEntry()` runs each discovered id through pure `enrichModelMetadata(id, api, probe)` helper. Helper: (a) strips common proxy prefixes (`cc/`, `anthropic/`, `openrouter/openai/…`) so bare id tried; (b) probes pi's `modelRegistry.find(provider, id)` via ordered api-appropriate candidate list (`anthropic-messages` → `["anthropic", "opencode"]`, `google-generative-ai` → `["google", "google-vertex"]`, `openai-completions` → `["openai", "openrouter", "groq", "xai", "mistral"]`); (c) returns registry's full metadata when matched. Registry reference captured from `ctx.modelRegistry` first time pi fires `session_start` on extension (`model_select` as fallback capture point) — no direct `@
|
|
1315
|
+
Custom-provider `/v1/models` endpoints only advertise `{id, owned_by}` — do not expose `context_window`, `max_tokens`, `cost`, `reasoning`. Rather than hardcode flat 200k / 16k / $0 / no-reasoning on every discovered model (silently wrong for proxied frontier models like `proxy/cc/claude-opus-4-7` → Opus 4.7's 1M window), bridge's `registerEntry()` runs each discovered id through pure `enrichModelMetadata(id, api, probe)` helper. Helper: (a) strips common proxy prefixes (`cc/`, `anthropic/`, `openrouter/openai/…`) so bare id tried; (b) probes pi's `modelRegistry.find(provider, id)` via ordered api-appropriate candidate list (`anthropic-messages` → `["anthropic", "opencode"]`, `google-generative-ai` → `["google", "google-vertex"]`, `openai-completions` → `["openai", "openrouter", "groq", "xai", "mistral"]`); (c) returns registry's full metadata when matched. Registry reference captured from `ctx.modelRegistry` first time pi fires `session_start` on extension (`model_select` as fallback capture point) — no direct `@earendil-works/pi-ai` import. Since `activate()` registers providers before any event handler fires, first pass uses fallback defaults; `session_start` handler re-registers all providers with enriched metadata via `pi.registerProvider`'s idempotent "replace" semantics. When registry never available or no match, fallback keeps `input: ["text","image"]` so image-capable-by-default contract preserved. Built-in + OAuth providers bypass entirely — metadata comes from pi's bundled `models.generated.js`. See `packages/extension/src/provider-register.ts` + change `enrich-custom-provider-model-metadata`.
|
|
1369
1316
|
|
|
1370
1317
|
### Testing a custom provider (Test button)
|
|
1371
1318
|
|
|
@@ -1675,33 +1622,6 @@ Settings → General → **Tools** renders one row per registered tool: status b
|
|
|
1675
1622
|
|
|
1676
1623
|
See change: `consolidate-tool-resolution`.
|
|
1677
1624
|
|
|
1678
|
-
### Testing the bootstrap state space
|
|
1679
|
-
|
|
1680
|
-
Resolution behavior intersects with HOME, platform, install layout, and pi's `settings.json` state across ~1000 combinations. Rather than hope CI on three runners plus manual QA cover all of them, the dashboard ships an in-memory harness at `packages/shared/src/__tests__/bootstrap/` that models the full cube:
|
|
1681
|
-
|
|
1682
|
-
```
|
|
1683
|
-
3 platforms (win32, darwin, linux)
|
|
1684
|
-
× 5 dash-locations (electron, npm-g, dev, managed, absent)
|
|
1685
|
-
× 6 pi-states (absent, present-no-ext, present-stale-ext, present-valid, malformed, appimage-tmp)
|
|
1686
|
-
× 4 settings-states (missing, empty, valid, malformed)
|
|
1687
|
-
× 3 env-states (normal, spaces-unicode, home-drift)
|
|
1688
|
-
= 1080 cells
|
|
1689
|
-
```
|
|
1690
|
-
|
|
1691
|
-
Each cell is **either** a registered test (writing a trail snapshot via `snapshotTrail`) **or** an explicit skip with a documented reason (in `scenarios-skipped.ts`). `cube.test.ts` fails CI when any cell is neither — a forcing function so that adding a new platform, a new install mechanic, or a new pi-state silently never happens.
|
|
1692
|
-
|
|
1693
|
-
The harness is memfs-backed (no real fs, no subprocesses, no network) and runs in ~2 seconds via `npm run test:bootstrap`. The primary assertion is a normalized trail snapshot that captures strategy order, failure reasons, and `toArgv` output — which catches most bootstrap regressions before CI even reaches a real OS.
|
|
1694
|
-
|
|
1695
|
-
Key locked-in invariants (from current snapshots):
|
|
1696
|
-
|
|
1697
|
-
- Unix pi chain: `override → managed-bin → where` (no bare-import, no npm-g — a real limitation for GUI-launched minimal-PATH scenarios).
|
|
1698
|
-
- Win32 pi chain: 5-level fallback including the no-cmd-flash `.cmd` probe and `node.exe` prepend for `.js` targets.
|
|
1699
|
-
- Override strategy is first in every chain; invalid overrides fall through with `invalid: ...` reason.
|
|
1700
|
-
- Path normalization cross-OS via `<HOME>` / `<NPM_ROOT>` placeholders — snapshots stable on macOS and Linux CI.
|
|
1701
|
-
- **Windows bug captured**: `npm i -g pi-dashboard` + no pi → pi unresolved. Trail snapshot locks in the current broken state; `unified-bootstrap-install` will update it when the fix lands.
|
|
1702
|
-
|
|
1703
|
-
See change: `bootstrap-resolution-harness`. Full walkthrough in `packages/shared/src/__tests__/bootstrap/README.md`.
|
|
1704
|
-
|
|
1705
1625
|
## Path Handling (`platform/paths.ts`)
|
|
1706
1626
|
|
|
1707
1627
|
Filesystem paths are OS-aware, and the dashboard touches them in three user-visible places: pin-directory storage (server), session-grouping (client), and the path picker UI (client). All three go through a single module — `packages/shared/src/platform/paths.ts` — rather than inventing their own logic.
|
|
@@ -1810,7 +1730,7 @@ See change: `eliminate-bash-on-windows-runners`.
|
|
|
1810
1730
|
|
|
1811
1731
|
1. `attach` — health probe returns 200 within 3s on the configured port.
|
|
1812
1732
|
2. `devMonorepo` — `!app.isPackaged AND existsSync(cwd/packages/server/src/cli.ts)`.
|
|
1813
|
-
3. `piExtension` — `~/.pi/agent/settings.json` has a bridge
|
|
1733
|
+
3. `piExtension` — `~/.pi/agent/settings.json#packages[]` has a bridge entry with resolvable server package >= `bundledMinVersion`. Walks `settings.packages[]` via `listPiPackages` from `pi-package-resolver.ts` (legacy `settings.extensions[]` never existed in pi schema; pre-fix probe read non-existent field and always returned null).
|
|
1814
1734
|
4. `npmGlobal` — `which pi-dashboard` returns a real-path not under `process.resourcesPath`, version >= `bundledMinVersion`.
|
|
1815
1735
|
5. `extracted` — always succeeds (fallback). May trigger bundle extraction from `process.resourcesPath` when version marker mismatches.
|
|
1816
1736
|
|
|
@@ -1820,6 +1740,10 @@ The spawned server receives `DASHBOARD_STARTER=Electron`. Lifecycle ownership ru
|
|
|
1820
1740
|
|
|
1821
1741
|
The `LAUNCH_SOURCE_V2=false` escape hatch reverts to the legacy `mode.json` path (documented below). The flag and its legacy path will be removed in a follow-up change.
|
|
1822
1742
|
|
|
1743
|
+
**Diagnostics dual-write.** Launch-source probe diagnostics route through `logLaunchSource(level, msg)` + `appendDashboardLog(line)`. Every probe outcome writes both to stderr AND to `~/.pi/dashboard/server.log` with `[<ts>] [launch-source] <msg>` prefix. Packaged-Electron `.desktop` launches discard stderr, so the log file is the sole post-mortem trail for cold-launch probe-cascade bugs. See change: `fix-electron-cold-launch-probe-cascade`.
|
|
1744
|
+
|
|
1745
|
+
**Extract self-heal.** `buildExtractedSource` passes `extractFs: Partial<ExtractFs>` (no no-op overrides) so `extractBundle`'s `buildFs` fills real-fs defaults for `mkdirSync`/`readdirSync`/`rmSync`/`statSync`. Selective-wipe step now clears stale absolute symlinks under `~/.pi-dashboard/node_modules/.../node_modules/.bin/X` before `cpSync`, self-healing `ERR_FS_CP_EINVAL` on any user's corrupt managed dir. See change: `fix-electron-cold-launch-probe-cascade`.
|
|
1746
|
+
|
|
1823
1747
|
### Legacy first-launch flow (LAUNCH_SOURCE_V2=false)
|
|
1824
1748
|
|
|
1825
1749
|
The Electron app's first-launch flow has three branches (escape-hatch only):
|
|
@@ -1844,7 +1768,7 @@ The Electron app's first-launch flow has three branches (escape-hatch only):
|
|
|
1844
1768
|
install
|
|
1845
1769
|
```
|
|
1846
1770
|
|
|
1847
|
-
|
|
1771
|
+
**Historical note** (pre-R3 only; superseded by `eliminate-electron-runtime-install`): the pre-R3 auto-skip-wizard branch wrote `mode.json` as power-user but skipped the managed install step, leaving `~/.pi-dashboard/node_modules/` empty. The bundled server's runtime then fell back to the user's system pi for the TS loader, which on machines with `pi-coding-agent@0.71.x` ships jiti 2.6.5 — misnormalizes triple-slash file:// URLs on Windows, crashes server child with `MODULE_NOT_FOUND`. Under R3, the runtime install pyramid is eliminated entirely: pi/openspec/tsx ship inside the immutable bundle, no system-pi fallback path exists.
|
|
1848
1772
|
|
|
1849
1773
|
The fix:
|
|
1850
1774
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blackbelt-technology/pi-agent-dashboard",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Web dashboard for monitoring and interacting with pi agent sessions",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
]
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
|
+
"scripts/maybe-patch-package.cjs",
|
|
33
34
|
"packages/server/src/",
|
|
34
35
|
"packages/server/scripts/",
|
|
35
36
|
"packages/server/package.json",
|
|
@@ -47,16 +48,17 @@
|
|
|
47
48
|
"LICENSE"
|
|
48
49
|
],
|
|
49
50
|
"scripts": {
|
|
50
|
-
"postinstall": "node packages/server/scripts/fix-pty-permissions.cjs",
|
|
51
|
+
"postinstall": "node scripts/maybe-patch-package.cjs && node packages/server/scripts/fix-pty-permissions.cjs",
|
|
51
52
|
"dev": "npm run dev --workspace=@blackbelt-technology/pi-dashboard-web",
|
|
52
53
|
"build": "npm run build --workspace=@blackbelt-technology/pi-dashboard-web",
|
|
53
54
|
"test": "HOME=$(mktemp -d -t pi-test-XXXXXX) NODE_OPTIONS=\"--localstorage-file=$(mktemp -t pi-test-ls-XXXXXX)\" vitest run",
|
|
54
55
|
"test:watch": "HOME=$(mktemp -d -t pi-test-XXXXXX) NODE_OPTIONS=\"--localstorage-file=$(mktemp -t pi-test-ls-XXXXXX)\" vitest",
|
|
55
|
-
"
|
|
56
|
-
"test:bootstrap:watch": "HOME=$(mktemp -d -t pi-test-XXXXXX) vitest packages/shared/src/__tests__/bootstrap",
|
|
56
|
+
"generate:plugin-registry": "node scripts/generate-plugin-registry.mjs",
|
|
57
57
|
"lint": "tsc --noEmit",
|
|
58
58
|
"reload": "./scripts/reload-all.sh",
|
|
59
59
|
"reload:check": "./scripts/reload-all.sh --check",
|
|
60
|
+
"link:local": "npm link --workspace=@blackbelt-technology/pi-dashboard-server",
|
|
61
|
+
"unlink:local": "npm rm -g @blackbelt-technology/pi-dashboard-server",
|
|
60
62
|
"electron:dev": "npm run start:dev --workspace=@blackbelt-technology/pi-dashboard-electron",
|
|
61
63
|
"electron:start": "npm run start --workspace=@blackbelt-technology/pi-dashboard-electron",
|
|
62
64
|
"electron:make": "npm run make --workspace=@blackbelt-technology/pi-dashboard-electron",
|
|
@@ -74,15 +76,17 @@
|
|
|
74
76
|
"node": ">=22.12.0 <25"
|
|
75
77
|
},
|
|
76
78
|
"dependencies": {
|
|
77
|
-
"@blackbelt-technology/pi-dashboard-extension": "^0.5.
|
|
78
|
-
"@blackbelt-technology/pi-dashboard-server": "^0.5.
|
|
79
|
-
"@blackbelt-technology/pi-dashboard-web": "^0.5.
|
|
79
|
+
"@blackbelt-technology/pi-dashboard-extension": "^0.5.4",
|
|
80
|
+
"@blackbelt-technology/pi-dashboard-server": "^0.5.4",
|
|
81
|
+
"@blackbelt-technology/pi-dashboard-web": "^0.5.4"
|
|
80
82
|
},
|
|
81
83
|
"optionalDependencies": {
|
|
82
84
|
"appdmg": "^0.6.6"
|
|
83
85
|
},
|
|
84
86
|
"devDependencies": {
|
|
85
87
|
"jsdom": "^29.0.2",
|
|
88
|
+
"patch-package": "^8.0.1",
|
|
89
|
+
"tsx": "^4.21.0",
|
|
86
90
|
"typescript": "^5.7.0",
|
|
87
91
|
"vitest": "^4.0.0"
|
|
88
92
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blackbelt-technology/pi-dashboard-extension",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Pi bridge extension for pi-dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
".pi/skills/pi-dashboard/"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@blackbelt-technology/pi-dashboard-shared": "^0.5.
|
|
27
|
+
"@blackbelt-technology/pi-dashboard-shared": "^0.5.4",
|
|
28
28
|
"ws": "^8.18.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { shouldApplyDefaultModel } from "../bridge-default-model-gate.js";
|
|
3
|
+
|
|
4
|
+
describe("shouldApplyDefaultModel", () => {
|
|
5
|
+
const base = { hasModelRegistry: true, hasDefaultModel: true };
|
|
6
|
+
|
|
7
|
+
it("applies for a brand-new session (reason=startup, entries=0)", () => {
|
|
8
|
+
expect(shouldApplyDefaultModel({ ...base, reason: "startup", entryCount: 0 })).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("does NOT apply for resumed sessions (entries>0, reason=startup)", () => {
|
|
12
|
+
expect(shouldApplyDefaultModel({ ...base, reason: "startup", entryCount: 5 })).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("does NOT apply for in-process new (reason=new)", () => {
|
|
16
|
+
// pi handles its own default for in-process /new — bridge stays out
|
|
17
|
+
expect(shouldApplyDefaultModel({ ...base, reason: "new", entryCount: 0 })).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("does NOT apply for in-process resume (reason=resume)", () => {
|
|
21
|
+
expect(shouldApplyDefaultModel({ ...base, reason: "resume", entryCount: 5 })).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("does NOT apply for in-process fork (reason=fork)", () => {
|
|
25
|
+
expect(shouldApplyDefaultModel({ ...base, reason: "fork", entryCount: 5 })).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("does NOT apply for reload of in-flight session (reason=reload, entries>0)", () => {
|
|
29
|
+
expect(shouldApplyDefaultModel({ ...base, reason: "reload", entryCount: 5 })).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("does NOT apply when defaultModel is not configured", () => {
|
|
33
|
+
expect(
|
|
34
|
+
shouldApplyDefaultModel({ ...base, hasDefaultModel: false, reason: "startup", entryCount: 0 }),
|
|
35
|
+
).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("does NOT apply when model registry not yet available", () => {
|
|
39
|
+
expect(
|
|
40
|
+
shouldApplyDefaultModel({ ...base, hasModelRegistry: false, reason: "startup", entryCount: 0 }),
|
|
41
|
+
).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("does NOT apply when reason is undefined", () => {
|
|
45
|
+
expect(shouldApplyDefaultModel({ ...base, reason: undefined, entryCount: 0 })).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
});
|