Skip to content

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 06 May 13:09

⚠️ Known issue: the Windows portable .exe builds in this release do not launch. Use the PI-Dashboard-win32-x64.zip / PI-Dashboard-win32-arm64.zip archive instead — extract anywhere and run PI Dashboard.exe. The portable assets have been removed from this release.

Added

  • Skill invocations in chat now render as collapsible cards; ↑ recalls the slash form, not the expanded body. When a user types /skill:openspec-explore continue with X (or any other skill), the dashboard's bridge now wraps the expanded skill body in pi's own <skill name="..." location="...">body</skill>\n\nargs envelope (byte-identical to pi's _expandSkillCommand output). The chat view detects this envelope and renders a distinct purple-tinted card with a wrench icon, the full slash form (/skill:openspec-explore continue with X) always visible in the header, and a body that's collapsed by default — click the chevron to expand the skill body and args. Four copy buttons: copy as Markdown (raw wrapper), copy as plain text (rendered body), copy as /skill: command (slash form to invoke again), and copy as message (just the user's typed args, hidden when the skill was invoked without args). Only the chevron icon is the expand/collapse toggle so the slash text remains mouse-selectable for native drag-copy. The chat-input ↑ history-recall now returns the slash form too, so users can re-invoke a skill without deleting thousands of characters first. The session sidebar's display name and search also see the condensed form because firstMessage is now condensed server-side before truncation. As a side effect, this aligns dashboard-typed skill invocations with pi-TUI's persisted format — single source of truth across both ingress paths. (change: render-skill-invocations-collapsibly)
  • Chat markdown now renders local-file images and LaTeX math. Agents can reference local screenshots inline as ![alt](/abs/path.png) or ![alt](./relative.png); the bridge inlines the bytes via a new streaming-safe pi-asset:<hash> token + side-channel asset_register WebSocket event so each unique image's bytes ride exactly once per session regardless of how many message_update chunks repeat the token. Math expressions — inline $x = \beta$ and display $$\sum_i^n i$$ (block-level) — are typeset via remark-math + rehype-katex with throwOnError:false so half-formed mid-stream expressions render as a fallback rather than crashing the markdown view. SVG, PNG, JPEG, GIF, WebP, AVIF, and BMP are supported with caps of 5 MB per image and 20 MB of new bytes per message; oversized / unreadable / unsupported-type tokens render as a visible placeholder rather than a broken-image glyph. The dashboard server adds zero new HTTP routes — image bytes flow through the existing event stream pattern that Read-tool images already use. (change: chat-markdown-local-images-and-math)

Changed

  • Provider Authentication settings now mirror the full provider list pi knows about, instead of an 8-item curated subset. The dashboard server's hardcoded OAUTH_PROVIDERS and API_KEY_PROVIDERS arrays have been replaced by a bridge-pushed catalogue: the bridge introspects modelRegistry.authStorage + modelRegistry.getProviderDisplayName and sends a new providers_list message alongside the existing models_list. The server caches the catalogue per pi process and uses it as the source for GET /api/provider-auth/status. Providers like deepseek, fireworks, cerebras, mistral, kimi-coding, huggingface, google-vertex, amazon-bedrock, etc. are now manageable from Settings → Provider Authentication. The status response also surfaces envVar (the env var pi-ai checks, e.g. OPENAI_API_KEY) and ambient: true for AWS profile / GCP ADC ambient credentials, with maskedKey: "(ambient)". Extension-registered OAuth providers (added via pi.registerProvider({oauth: ...}) from another extension) become visible automatically. No package.json changes; no new dependencies. (change: replace-hardcoded-provider-lists)

  • Windows: NSIS installer removed. The NSIS setup wizard (@felixrieseberg/electron-forge-maker-nsis) is dropped. Windows distribution is now ZIP (.zip) and portable .exe (7-Zip SFX via electron-builder --win portable — no NSIS dependency). Users previously using the NSIS installer should extract the .zip to any directory and run PI Dashboard.exe directly. No system-level installation is required. (change: simplify-electron-bootstrap-derived-state)

  • Electron Bootstrap via Derived State (Phase C — simplify-electron-bootstrap-derived-state).

    • Replaced ~/.pi-dashboard/mode.json startup-flag with per-launch capability probes and DASHBOARD_STARTER env var.
    • Added selectLaunchSource() resolver — five precedence-ordered sources: attach > devMonorepo > piExtension > npmGlobal > extracted.
    • DASHBOARD_STARTER env var now stamped on every server spawn ("Electron" | "Bridge" | "Standalone"); exposed via /api/health.starter.
    • Added pid field to /api/health for lifecycle ownership; Electron stops the server only when starter=Electron AND pid matches.
    • Server reads ~/.pi/dashboard/installable.json before binding; installs missing packages in bootstrap phase (no-op when file absent).
    • Added bundle-extract.ts — version-marker-driven ~/.pi-dashboard/ extraction with survive-extract whitelist (node/, node-pending/, node-old/).
    • Added POST /api/electron/reextract endpoint (Electron-only; 403 for other starters; 202 triggers Electron-side restart).
    • Config files (*config*, mode.json, recommended-wizard.json, api-key.json) auto-migrated to ~/.pi/dashboard/migrate/<timestamp>/ on upgrade.
    • LAUNCH_SOURCE_V2 flag now defaults to true; set LAUNCH_SOURCE_V2=false to revert to the legacy mode.json-based flow (escape hatch only).
    • Note: LAUNCH_SOURCE_V2 flag will be removed in a follow-up change after Phase C ships without regressions.

Migration Notes (for users upgrading from v0.4.x)

  • ~/.pi-dashboard/mode.json is no longer used; existing installs are auto-migrated on first launch.
  • Config files (*config*, mode.json, recommended-wizard.json, api-key.json) are archived to ~/.pi/dashboard/migrate/<timestamp>/ on first V2 launch.
  • ~/.pi-dashboard/ is now managed exclusively by the Electron app; do not manually edit files there.
  • installable.json lives at ~/.pi/dashboard/installable.json and controls which packages are installed.
  • API key may need re-entry if it was stored in api-key.json (check ~/.pi/dashboard/migrate/ for recovery).
  • node/, node-pending/, node-old/ under ~/.pi-dashboard/ are preserved across Electron version upgrades.

Fixed

  • Newly-spawned sessions no longer get their session name overwritten with a UUID-shaped string when the agent reads/writes/CLI-references a UUID-named OpenSpec change directory. The activity detector (packages/shared/src/openspec-activity-detector.ts) used a permissive [^\s"']+ capture group for change-name extraction that accepted UUIDs, mixed-case slugs, underscored slugs, and pathologically-long tokens. The previous fix (fix-openspec-flag-rename-bug) only rejected --prefixed tokens (CLI flags) on the explicit premise that the detector is a stable single source — that premise didn't hold. Two layers of defense now: (a) the detector validates every captured token through the new isValidOpenSpecChangeSlug helper enforcing OpenSpec's own slug shape ^[a-z][a-z0-9-]{0,63}$ (lowercase, leading-letter, kebab-case, max 64 chars — mirrors openspec new change validation) on all three branches (Read, Write, Bash), and (b) the auto-attach branch in packages/server/src/event-wiring.ts re-validates detected.changeName against the same predicate before stamping openspecChange/attachedProposal/name or sending rename_session, so a future detector regression cannot propagate junk to disk. Manual attach paths (browser attach_proposal, REST /api/session/:id/attach-proposal) intentionally bypass the slug check and accept any user-supplied name from the server-curated change list. (change: fix-uuid-rename-bug)
  • Browser-rendered terminals no longer render at half-height on the folder-terminals page. App.tsx kept a legacy keep-alive <TerminalView> list mounted unconditionally for the long-removed /terminal/:id route. Whenever the user opened /folder/<cwd>/terminals, that legacy list mounted a hidden <TerminalView> for every terminal in the global Map alongside the visible <TerminalView> rendered inside <TerminalsView> — two WebSockets per terminal id, two AttachAddons writing to the same PTY, and two FitAddon instances racing to send resize messages. The hidden one (measuring a display:none 0×0 container) won often enough to shrink the PTY to a near-zero geometry, while the visible xterm rendered the PTY's tiny output into the top half of its viewport. The fix removes the legacy keep-alive list, the /terminal/:id route matcher, and the redirect effects (~50 LOC out of App.tsx); <TerminalsView> becomes the single owner of <TerminalView> mounting. As defense-in-depth, terminal-manager.ts now ignores any inbound {type:"resize"} control message with cols < 2 or rows < 2 — a PTY at those dimensions is non-functional for every supported shell and no legitimate user intent maps there. As a bundled bonus, the auto-shutdown idle timer now factors in active terminals: a long-running cargo build or tail -f in a terminal with no agent attached keeps the server alive, instead of being killed by idle-shutdown after shutdownIdleSeconds. BREAKING (theoretical): direct navigation to /terminal/:id now lands on the SPA catch-all (/); no in-tree code path used the route, no docs reference it, and folder-scoped /folder/:encodedCwd/terminals is the canonical entry. (change: fix-terminal-half-height-dual-mount)
  • Sidebar no longer leaves a running session below the “Show N ended” divider after the dashboard server restarts. The browser used to merge incremental on-connect session_added + sessions_reordered messages into stale state from the previous server lifetime; depending on bridge-reattach timing, an actually-running session could end up rendered under the ended divider until a manual page refresh. The server now emits a single atomic sessions_snapshot message on every browser WebSocket connect, and the client REPLACES (does not merge) its sessions Map and sessionOrderMap from that payload, so stale ids are dropped atomically. BREAKING (browser protocol): older browser tabs from before this release that reconnect to a server with this change will see no sessions until refreshed; the legacy per-session bootstrap loop has been removed. Live updates after the snapshot continue to use the existing incremental messages. (change: fix-stale-sessions-on-reconnect)

First-launch unblocking (unsigned binaries)

The Windows installers and macOS DMGs are not yet code-signed / notarized.
Both OSes will block first-launch with a security warning. These are not
malware — the artifacts are the exact ones produced by
.github/workflows/publish.yml
against this tag. Pick whichever workaround fits your workflow.

Tracking: Authenticode signing → change windows-authenticode-signing;
macOS notarization → change macos-notarization (planned). This section
will shrink and eventually disappear as each lands.

Windows — SmartScreen warning

SmartScreen will show "Windows protected your PC" the first time you
run any .exe artifact (Setup, portable, or any .exe extracted from a
ZIP).

Option A — at the SmartScreen dialog:

  1. Click More info.
  2. Click Run anyway.

Option B — pre-clear the Mark-of-the-Web:

  1. Right-click the downloaded .exe (or the .zip) → Properties.
  2. At the bottom of the General tab, tick Unblock next to
    "This file came from another computer...".
  3. Click OK and run as normal.

For ZIP archives, unblock the archive itself before extracting so
the contained .exes inherit the cleared zone.

macOS — Gatekeeper / quarantine

macOS will refuse to launch the app on first run with "PI Dashboard
cannot be opened because the developer cannot be verified"
or silently
quarantine it.

Option A — control-click the app:

  1. Open the DMG and drag PI Dashboard to Applications.
  2. In Applications, right-click (or Control-click) PI Dashboard →
    Open
    .
  3. Click Open in the confirmation dialog. Subsequent launches are
    unrestricted.

Option B — clear the quarantine attribute from the terminal:

xattr -d com.apple.quarantine "/Applications/PI Dashboard.app"

If the DMG itself is being blocked, clear it on the mounted volume
before copying:

xattr -d com.apple.quarantine "/Volumes/PI Dashboard/PI Dashboard.app"