Pi Mobile uses internal pi extensions to provide mobile-specific workflows that are not available through standard RPC commands.
These extensions are loaded by the bridge when it starts pi --mode rpc.
- Overview
- Where Extensions Live
- Runtime Loading
- Extension 1:
pi-mobile-tree - Extension 2:
pi-mobile-open-stats - Android Client Integration
- Extension UI Method Support
- How to Add a New Internal Extension
- Troubleshooting
- Reference Files
Our custom extensions are intentionally internal plumbing between:
- the Node bridge (
bridge/) - the pi runtime
- the Android client (
app/)
They are used to:
- enable in-place tree navigation with structured results
- trigger mobile-only workflow actions (currently: open stats sheet)
These commands should not appear as user-facing commands in the mobile command palette.
bridge/src/extensions/pi-mobile-tree.tsbridge/src/extensions/pi-mobile-workflows.ts
The bridge injects both extensions into every pi RPC subprocess:
--extension bridge/src/extensions/pi-mobile-tree.ts--extension bridge/src/extensions/pi-mobile-workflows.ts
Implemented in:
bridge/src/server.ts(createPiRpcForwarder(...args))
Command name: pi-mobile-tree
Purpose: perform tree navigation from the bridge and return a structured status payload.
/<command> <entryId> <statusKey>
entryId: required target tree entry IDstatusKey: required, must start withpi_mobile_tree_result:
If arguments are invalid, command exits without side effects.
waitForIdle()navigateTree(entryId, { summarize: false })- If navigation is not cancelled, update editor text via
ctx.ui.setEditorText(...) - Emit result via
ctx.ui.setStatus(statusKey, JSON.stringify(payload)) - Immediately clear status key via
ctx.ui.setStatus(statusKey, undefined)
{
"cancelled": false,
"editorText": "retry this branch",
"currentLeafId": "entry-42",
"sessionPath": "/home/user/.pi/agent/sessions/...jsonl",
"error": "optional"
}If an exception occurs, error is set and the bridge treats navigation as failed.
Command name: pi-mobile-open-stats
Purpose: emit a workflow action to open the stats sheet in the Android app.
- Accepts optional action argument
- Default action:
open_stats - Rejects unknown actions silently
When accepted, it emits:
- status key:
pi-mobile-workflow-action - status text:
{"action":"open_stats"}
Then clears the status key immediately.
The Android client treats these as internal bridge mechanisms.
Defined in ChatViewModel:
pi-mobile-treepi-mobile-open-stats
They are hidden from visible slash-command results by filtering internal names.
| Mobile command | Behavior |
|---|---|
/tree |
Opens mobile tree sheet directly |
/stats |
Attempts internal /pi-mobile-open-stats, falls back to local stats sheet if unavailable |
/model |
Opens model picker |
/session |
Opens stats/session overview sheet |
/compact |
Runs compact on active session |
/export |
Exports current session to HTML |
/import |
Opens the Android document picker and imports a JSONL session into the active runtime |
/copy |
Copies the last assistant response to the Android clipboard |
/fork |
Opens tree sheet and guides user to select fork entry |
/new |
Starts a new session |
/name <new name> |
Renames active session |
/settings |
Shows guidance to use Settings tab |
/hotkeys, /resume, /share, /reload, /changelog, /scoped-models |
Explicitly marked unavailable on mobile |
ChatViewModel listens for extension_ui_request with:
method = setStatusstatusKey = pi-mobile-workflow-action
If payload action is open_stats, it opens the stats sheet.
Non-workflow status keys are surfaced in the extension status strip and can be hidden via Settings.
Pi Mobile currently handles these extension_ui_request methods:
| Method | Client behavior |
|---|---|
select |
Shows select dialog |
confirm |
Shows yes/no dialog |
input |
Shows text input dialog |
editor |
Shows multiline editor dialog |
notify |
Shows transient notification |
setStatus |
Handles internal workflow key (pi-mobile-workflow-action) |
setWidget |
Updates extension widgets above/below editor |
setTitle |
Updates chat title |
set_editor_text |
Replaces prompt editor text |
Related model types:
core-rpc/.../ExtensionUiRequestEventcore-rpc/.../ExtensionErrorEvent
Use this checklist for safe integration:
- Create extension file in
bridge/src/extensions/ - Register command(s) with explicit internal names (prefix with
pi-mobile-) - Load extension in bridge (
bridge/src/server.tsforwarder args) - Define status key contract if extension communicates via
setStatus - Hide internal commands in
ChatViewModel.INTERNAL_HIDDEN_COMMAND_NAMES - Wire client handling (event parsing + UI updates + fallback behavior)
- Add tests
- bridge behavior (
bridge/test/server.test.ts) - viewmodel behavior (
app/src/test/...)
- bridge behavior (
- Document payload schemas in this file
Check:
get_commandsincludespi-mobile-open-stats- extension loaded by bridge subprocess args
setStatusevent payload action is exactlyopen_stats
Check:
get_commandsincludespi-mobile-tree- emitted status key starts with
pi_mobile_tree_result: - extension returns valid JSON payload in
statusText
Check ChatViewModel.INTERNAL_HIDDEN_COMMAND_NAMES contains:
pi-mobile-treepi-mobile-open-stats
bridge/src/extensions/pi-mobile-tree.tsbridge/src/extensions/pi-mobile-workflows.tsbridge/src/server.tsapp/src/main/java/com/ayagmar/pimobile/chat/ChatViewModel.ktapp/src/main/java/com/ayagmar/pimobile/ui/chat/ChatOverlays.ktbridge/test/server.test.tsapp/src/test/java/com/ayagmar/pimobile/chat/ChatViewModelWorkflowCommandTest.kt