Skip to content

feat(desktop): add Pi extension support - #76416

Open
marandaneto wants to merge 4 commits into
masterfrom
feat/pi-extension-system
Open

feat(desktop): add Pi extension support#76416
marandaneto wants to merge 4 commits into
masterfrom
feat/pi-extension-system

Conversation

@marandaneto

@marandaneto marandaneto commented Aug 2, 2026

Copy link
Copy Markdown
Member

Problem

Pi sessions in the desktop app could run built-in harness extensions, but they could not expose the standard Pi extension UI protocol to the renderer. Project-local .pi resources also had no explicit repository trust flow, so safely loading project extensions, skills, prompts, and settings was not possible.

Changes

  • Bridge Pi RPC extension dialogs, notifications, statuses, widgets, titles, and editor text into the desktop UI.
  • Preserve extension state across renderer reconnects with authoritative snapshots, response deduplication, expiration, and reconnect backoff.
  • Add explicit repository trust and revocation using Pi's native trust store. Trust changes restart Pi while preserving the native session and queued messages.
  • Apply repository trust to validated managed Git worktrees without allowing an unrelated working directory to reuse that decision.
  • Recreate a Pi runtime after a failed stop instead of allowing the same-CWD resume fast path to reuse a partially stopped session.
  • Keep project-local resources disabled for cloud Pi sessions.
  • Document global packages, project-local resources, supported UI methods, and the security model.

Before:

flowchart LR
    A{{Pi runtime}} --> B[Conversation RPC]
    B --> C[Desktop chat]
    D[Project .pi resources] --> E[Disabled]
    classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff;
    classDef phRed fill:#f54e00,stroke:#f54e00,color:#fff;
    classDef phYellow fill:#f9bd2b,stroke:#f9bd2b,color:#000;
    classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000;
    class A phBlue;
    class B,C phYellow;
    class D,E phGray;
Loading

After:

flowchart LR
    A{{Pi runtime}} --> B[Conversation RPC]
    A --> C[Extension UI RPC]
    B --> D[Desktop chat]
    C --> E[Dialogs and extension surfaces]
    F[Project .pi resources] --> G[Repository trust gate]
    G --> A
    classDef phBlue fill:#1d4aff,stroke:#1d4aff,color:#fff;
    classDef phRed fill:#f54e00,stroke:#f54e00,color:#fff;
    classDef phYellow fill:#f9bd2b,stroke:#f9bd2b,color:#000;
    classDef phGray fill:#e5e7eb,stroke:#c7ccd1,color:#000;
    class A phBlue;
    class B,C,G phRed;
    class D,E phYellow;
    class F phGray;
Loading

Warning

Trusted project extensions run arbitrary code with the user's permissions. The trust dialog calls this out before enabling repository resources.

How did you test this code?

  • Ran pnpm build:deps and the full desktop pnpm typecheck across 20 packages.
  • Ran focused Vitest suites for the agent RPC/runtime, harness runtime and trust store, core Pi controller and task lifecycle, workspace-server Pi sessions, desktop adapters, and extension UI components: 152 tests passed.
  • Ran Biome checks over the affected files, restricted-import lint for packages/core, host-boundary validation, git diff --check, and hogli ci:preflight --strict.
  • Ran a live Electron smoke test with a disposable project-local extension. Verified disabled resources before trust, the trust warning and Pi restart, notification/status/widget rendering, slash-command dispatch, confirmation response, editor prefill, revocation, and native trust-store persistence.
  • Ran the isolated autoreview skill after implementation and after porting into the monorepo. Final result was blocker-free.

The added tests guard realistic regressions in RPC request/response mapping, replay deduplication and reconnect behavior, trust reuse across unrelated repositories, Pi session continuity during trust changes, and accessible dialog submission behavior.

👉 Stay up-to-date with PostHog coding conventions for a smoother review.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

Added products/desktop/docs/PI-EXTENSIONS.md covering installation, project trust, supported APIs, and security boundaries.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Implemented with the Pi coding agent in a local session. Skills used: pr, autoreview, /writing-tests, /writing-user-facing-copy, and /writing-code-comments.

The implementation uses Pi's native package, extension, RPC UI, and trust semantics rather than introducing a separate desktop plugin system. Project trust is attached to the registered repository and validated managed worktrees; cloud sessions remain isolated from local resources.

@marandaneto marandaneto self-assigned this Aug 2, 2026
@trunk-io

trunk-io Bot commented Aug 2, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions github-actions Bot added the feature/desktop Feature Tag: Desktop label Aug 2, 2026
@trunk-io

trunk-io Bot commented Aug 2, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@marandaneto

Copy link
Copy Markdown
Member Author

Compatibility note for extension authors

PostHog Desktop runs Pi through RPC mode rather than Pi's terminal UI. Standard Pi extension behavior, including tools, hooks, commands, and resource loading, works normally. The RPC UI methods bridged by this PR also work:

  • ctx.ui.select()
  • ctx.ui.confirm()
  • ctx.ui.input()
  • ctx.ui.editor()
  • ctx.ui.notify()
  • ctx.ui.setStatus()
  • ctx.ui.setWidget() with string arrays
  • ctx.ui.setTitle()
  • ctx.ui.setEditorText()

TUI-specific rendering cannot be transferred directly to Electron. ctx.ui.custom(), component factories, raw terminal input, and TUI keybindings depend on terminal component objects and keyboard handling that are not serializable through the RPC protocol. Extensions using these APIs should keep their TUI implementation and provide an RPC-compatible fallback in the same extension.

export default function (pi: ExtensionAPI) {
  pi.registerCommand("my-command", {
    description: "Run my extension",
    handler: async (_args, ctx) => {
      if (ctx.mode === "tui") {
        // Rich terminal-only implementation.
        return runCustomTui(ctx);
      }

      if (!ctx.hasUI) {
        return;
      }

      const choice = await ctx.ui.select("Choose an action", [
        "Analyze",
        "Configure",
      ]);

      if (choice === "Configure") {
        const value = await ctx.ui.input("Configuration", "Enter a value");
        ctx.ui.notify(`Saved ${value}`, "info");
      }
    },
  });

  // Useful in Pi's TUI. Desktop users can invoke /my-command instead.
  pi.registerShortcut("ctrl+shift+x", {
    description: "Run my extension",
    handler: /* ... */,
  });
}

The main fallback mappings are:

TUI-specific feature Desktop-compatible fallback
ctx.ui.custom() select, confirm, input, or editor
Custom component widget setWidget(id, string[])
Custom status line setStatus()
Raw terminal input Structured dialogs
Keyboard shortcut A registerCommand() slash command
Direct editor manipulation setEditorText()
Terminal title or rendering setTitle() and notify()

Use ctx.mode === "tui" to guard terminal-only behavior and ctx.hasUI for methods supported by both TUI and RPC clients:

if (ctx.mode === "tui") {
  // Custom components, component factories, and terminal input.
}

if (ctx.hasUI) {
  // Dialogs, notifications, statuses, and text widgets.
}

For widgets, use text lines in RPC/Desktop mode because component factories are ignored:

ctx.ui.setWidget("results", [
  "3 files changed",
  "Tests passing",
]);

A future rich custom UI system would need a declarative, sandboxed protocol that Electron can render. Arbitrary terminal components cannot be supported transparently by the current RPC wire format.

@marandaneto
marandaneto requested review from a team August 2, 2026 11:32
@marandaneto
marandaneto marked this pull request as ready for review August 2, 2026 11:32
@greptile-apps

greptile-apps Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
products/desktop/packages/workspace-server/src/services/pi-session/pi-session.ts:490-495
**Failed stop leaves stale session**

When `session.client.stop()` rejects for the current session, this branch rethrows before removing the session or unregistering its process. A later resume with the same cwd and trust path then takes the existing-session fast path, leaving the task connected to a dead or partially stopped Pi runtime.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(desktop): add Pi extension support" | Re-trigger Greptile

@jonathanlab jonathanlab left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this, really appreciate you digging in to this. Neat to see extension RPC slot in fairly easily like this.

Because we our RPC streams are not stored anywhere, it seems like this turned out to be trickier than expected, we have to do a lot of state tracking with regards to extensionReplay and extension_state_snapshot and state management in PiSessionStore. I'm hesitant about introducing more state because it makes things complex and hard to reason about.

I personally think we should try to keep UI extension state ephemeral. As far as I know, pi-cli extension UI state is also ephemeral and does not survive reconnects, so lets not try do to that here. Any state that's meaningful we should serialize in the session file. We can then construct fresh UI from that on restart.

There's also some things that need refactoring/changing here, left some comments.

Comment thread products/desktop/docs/PI-EXTENSIONS.md
Comment thread products/desktop/packages/agent/src/pi/types.ts Outdated
Comment thread products/desktop/packages/core/src/pi-runtime/piSessionController.ts Outdated
Comment thread products/desktop/packages/agent/src/pi/types.ts Outdated
Comment thread products/desktop/packages/harness/src/project-trust.ts Outdated
Comment thread products/desktop/packages/harness/src/runtime.ts Outdated
Comment thread products/desktop/packages/host-router/src/routers/pi-session.router.ts Outdated
Comment thread products/desktop/packages/ui/src/features/pi-sessions/piExtensionEditorText.ts Outdated
@marandaneto

Copy link
Copy Markdown
Member Author

Addressed the overall review direction in ee6c62c. Extension UI state is now ephemeral: the backend replay/snapshot protocol and custom lifecycle events were removed, extension concerns were split into a dedicated controller and reducer, and runtime-scoped streams clear state rather than reconstructing it across reconnects or replacements. Pending dialogs are cancelled when possible so removing replay does not leave an active extension waiting indefinitely.

@marandaneto
marandaneto requested a review from jonathanlab August 3, 2026 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature/desktop Feature Tag: Desktop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants