App
⚠ verified v0.0.496 (current: v0.0.505)App
Base class for Plexi v3 apps. Subclass and override event handlers.
Override any of:
Awaited (block the event loop until they return): on_init(ctx) — after Init handshake on_render(ctx) — on each Render event on_render_seed(ctx, payload) — on RenderSeed (headless only, before first Render) on_suspend() — on Suspend on_resume() — on Resume on_shutdown() — on Shutdown
Task (dispatched as asyncio tasks — event loop continues): on_key(ctx, key, mods) — on Key event on_click(ctx, x, y, button) — on Click event on_mouse_down(ctx, x, y, button, mods={}) — on MouseDown event on_mouse_up(ctx, x, y, button, mods={}) — on MouseUp event on_mouse_move(ctx, x, y, buttons, mods={}) — on MouseMove event on_command(ctx, text) — on Command event on_paste(ctx, text) — on Paste event on_text_submitted(ctx, id, text) — on TextInput Enter press on_pipe_message(ctx, pipe_id, payload) — on PipeMessage on_path_changed(ctx, cwd) — on PathChanged on_inject(ctx, payload) — on Inject event on_nav_back(ctx, view_id) — on NavBack event on_timer(ctx, timer_id) — on Timer event on_scroll(ctx, id, offset_y) — on Scroll event on_file_picked(ctx, request_id, paths) — on FilePicked event on_file_pick_cancelled(ctx, request_id) — on FilePickCancelled on_mcp_call(ctx, tool_name, arguments) — on MCP tool call
Fire-and-forget (no RenderContext — called outside a render frame): on_pane_spawned(pane_id, request_id) — pane spawn succeeded on_pane_spawn_error(reason, request_id) — pane spawn failed on_context_state(state) — context state query result on_midi_input_opened(pipe_id, port_id, port_name) — MIDI input opened
Task handlers are dispatched as asyncio tasks — the event loop does not
wait for them to complete before processing the next event. Declare them
async def whenever they do any I/O. Never call blocking operations
(time.sleep, requests.get, etc.) directly from these handlers; use
await asyncio.to_thread(fn) or threading.Thread + emit.run_sync().
Awaited handlers block the event loop until they return. Use await-able
Emitter helpers freely; they do not deadlock because the stdin reader runs
as a concurrent task.
Fire-and-forget handlers do not receive a RenderContext because they are dispatched outside a render frame.
Methods
on_init
def on_init(_ctx: RenderContext) -> 'Coroutine[Any, Any, None] | None'
on_render
def on_render(_ctx: RenderContext) -> None
on_key
def on_key(_ctx: RenderContext, _key: str, _mods: dict) -> 'Coroutine[Any, Any, None] | None'
on_click
def on_click(_ctx: RenderContext, _x: float, _y: float, _button: str) -> 'Coroutine[Any, Any, None] | None'
on_mouse_down
def on_mouse_down(_ctx: RenderContext, _x: float, _y: float, _button: str, _mods: dict = {}) -> 'Coroutine[Any, Any, None] | None'
on_mouse_up
def on_mouse_up(_ctx: RenderContext, _x: float, _y: float, _button: str, _mods: dict = {}) -> 'Coroutine[Any, Any, None] | None'
on_mouse_move
def on_mouse_move(_ctx: RenderContext, _x: float, _y: float, _buttons: list, _mods: dict = {}) -> 'Coroutine[Any, Any, None] | None'
on_command
def on_command(_ctx: RenderContext, _text: str) -> 'Coroutine[Any, Any, None] | None'
on_paste
def on_paste(_ctx: RenderContext, _text: str) -> 'Coroutine[Any, Any, None] | None'
on_pipe_message
def on_pipe_message(_ctx: RenderContext, _pipe_id: str, _payload: Any) -> 'Coroutine[Any, Any, None] | None'
on_path_changed
def on_path_changed(_ctx: RenderContext, _cwd: str) -> 'Coroutine[Any, Any, None] | None'
on_inject
def on_inject(_ctx: RenderContext, _payload: Any) -> 'Coroutine[Any, Any, None] | None'
on_render_seed
def on_render_seed(_ctx: RenderContext, _payload: Any) -> 'Coroutine[Any, Any, None] | None'
on_nav_back
def on_nav_back(_ctx: RenderContext, _view_id: str) -> 'Coroutine[Any, Any, None] | None'
Called when the host emits NavBack — user pressed Cmd+[ or the
back arrow in the pane chrome. view_id is the view being navigated
back to (the new top of stack, or empty string for root).
The app should update its own view state to show view_id, then call
ctx.emit.pop_nav() to remove the entry from the host stack.
on_pane_spawned
def on_pane_spawned(_pane_id: int, _request_id: 'str | None' = None) -> None
Called when a SpawnPane request succeeded (#592). Override to track the spawned pane.
on_pane_spawn_error
def on_pane_spawn_error(_reason: str, _request_id: 'str | None' = None) -> None
Called when a SpawnPane request failed (#592). Override to handle the error.
on_context_state
def on_context_state(_state: dict) -> None
Called when a QueryContextState response arrives (#1518).
_state is a dict with keys: context_id, name, path, status,
pane_count, panes (list of pane summaries), children (list of child
context ids).
on_timer
def on_timer(_ctx: RenderContext, _timer_id: str) -> 'Coroutine[Any, Any, None] | None'
on_scroll
def on_scroll(_ctx: RenderContext, _id: str, _offset_y: float) -> 'Coroutine[Any, Any, None] | None'
on_scroll_delta
def on_scroll_delta(_ctx: RenderContext, _delta_y: float) -> 'Coroutine[Any, Any, None] | None'
on_list_select
def on_list_select(_ctx: 'RenderContext', _id: str, _index: int) -> 'Coroutine[Any, Any, None] | None'
Called when a list_view selection changes via j/k/up/down.
on_list_activate
def on_list_activate(_ctx: 'RenderContext', _id: str, _index: int) -> 'Coroutine[Any, Any, None] | None'
Called when Enter is pressed on a selected list_view item.
on_text_submitted
def on_text_submitted(_ctx: RenderContext, _id: str, _text: str) -> 'Coroutine[Any, Any, None] | None'
on_file_picked
def on_file_picked(_ctx: RenderContext, _request_id: str, _paths: 'list[str]') -> 'Coroutine[Any, Any, None] | None'
Called when the user selected one or more files in the picker.
_request_id matches the id passed to ctx.emit.open_file_picker.
_paths is a list of absolute file paths chosen by the user.
on_file_pick_cancelled
def on_file_pick_cancelled(_ctx: RenderContext, _request_id: str) -> 'Coroutine[Any, Any, None] | None'
Called when the user dismissed the file picker without selecting a file,
or if the fs.pick capability was not declared.
on_mcp_call
def on_mcp_call(_ctx: RenderContext, _tool_name: str, _arguments: dict) -> 'dict | Coroutine[Any, Any, dict] | None'
Called when an external MCP client calls a tool declared in [app.mcp].
Override to handle the call and return a result dict. The result should
follow MCP CallToolResult schema: {"content": [{"type": "text", "text": "..."}]}.
Return None to respond with a generic ‘not implemented’ error.
on_ai_stream_chunk
def on_ai_stream_chunk(_request_id: str, _delta: str, _done: bool) -> None
Called for each incremental token chunk from a streaming ai_query response.
Fired before the final ai_response event so apps can display tokens
as they arrive. _delta is the incremental text; _done is True
on the last chunk before AiResponse fires.
Override to stream tokens into a display buffer:
def on_ai_stream_chunk(self, request_id, delta, done):
self.streaming_text += delta
self.emit.schedule_render()
Default: no-op. Apps that only care about the final result can ignore this.
on_midi_input_opened
def on_midi_input_opened(_pipe_id: str, _port_id: str, _port_name: str) -> None
Override to react to a successful OpenMidiInput. Apps that just
want the byte stream typically read directly from the binary pipe
opened alongside this event — Plexi sends pipe_opened first.
tool
def tool(name: str, description: str, schema: dict, timeout_ms: 'int | None' = None) -> Any
Decorator — register a method as an AI-callable tool and expose it.
Tools are functions that AI agents can invoke by name. Each tool has a description and a JSON schema defining its parameters.
Args: name: Tool identifier; used by agents to invoke this tool description: Human-readable description of what the tool does schema: JSON schema object for tool parameters (type: “object” with properties) timeout_ms: Optional timeout in milliseconds; None = no timeout
Returns: A decorator function that registers the method as a tool.
Example:
@app.tool("increment", description="Increment counter", schema={
"type": "object",
"properties": {"n": {"type": "integer", "description": "Amount to increment"}},
"required": ["n"],
})
async def handle_increment(self, args):
n = args.get("n", 0)
self.counter += n
return {"new_value": self.counter}
The decorated method receives a dict of parsed arguments and can return any JSON-serializable value or raise an exception (which becomes the tool’s error response).
view
def view() -> object | None
Override to return a declarative component tree.
Return None (the default) to use the flat draw-command path via
on_render. When overridden, the host renderer will walk the returned
tree instead of calling on_render.
UiNode is defined in plexi_sdk.ui (epic #1897, task A2).
on_suspend
def on_suspend() -> None
on_resume
def on_resume() -> None
on_shutdown
def on_shutdown() -> None
run
def run() -> None
Start the PGAP v3 asyncio event loop. Blocks until Shutdown.
This is the entry point for all Plexi apps. Call it from your main block:
if __name__ == '__main__':
app = MyApp()
app.run()