@devnazim/pi-cmux0.2.5
cmux notifications and status integration for pi.
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
pi-cmux
cmux notifications and status integration for pi.
pi-cmux is a standalone pi extension/package. It sends generic pi lifecycle updates to cmux and exposes an optional in-process notifier API that other pi extensions can use for semantic notifications.
Package name: @devnazim/pi-cmux. See release notes.
Compatibility: pi-cmux requires Pi 0.80.4 or newer and is tested against Pi 1.2.0. Its cmux integration is source-checked against the v0.65.1 CLI/RPC contract, with mocked CLI tests and isolated socket tests. The cmux-tui daemon accepted a live test notification, and the user confirmed seeing it. Live Mac CLI/RPC relay delivery has not been tested. It uses the agent_settled lifecycle event so retries, compaction, and queued continuations do not trigger premature completion notifications. Input-wait alerts require Pi 0.84.4 or newer, which provides the UI prompt events.
cmux-tui notifications use its separate resource API v2 over an explicit local socket. No cmux executable is required for that transport. Mac CLI and SSH relay support remain separate; cmux-tui IDs are not Mac workspace or surface IDs.
Current cmux releases also provide a first-party Pi extension. cmux's Pi launcher can load it automatically, without cmux hooks pi install or cmux hooks setup. Enabling both lifecycle integrations can produce duplicate completion and input-wait notifications.
Set "lifecycle": false in this package's configuration when using the first-party hook. This keeps the cross-extension notifier API and /cmux-status without competing automatic updates.
To use pi-cmux for lifecycle updates instead, set CMUX_PI_HOOKS_DISABLED=1 when starting Pi. This prevents cmux launcher injection and makes the v0.65.1 first-party hook skip delivery even if separately installed. It does not uninstall the hook.
pi-agent-suite compatibility
The integration was source-reviewed against pi-agent-suite 2.13.5, but both packages have not been runtime-tested together. The suite includes its own enabled-by-default cmux completion notifier. To keep pi-cmux's settled-run notifications without duplicate success popups, disable the suite's cmux notifier with "enabled": false in its cmux/config.json. Alternatively, set "lifecycle": false in pi-cmux to retain only its optional notifier API and diagnostics.
Suite 2.13.5 lets subagents load discovered extensions by default. If a subagent loads pi-cmux, headless completion popups stay disabled by default, but activity reports and logs can still affect the parent's cmux surface. Use the suite's agent-selection/config.json setting subagents.extensions.mode of "none", or "explicit" with an include list that omits pi-cmux, when child sessions should not report cmux activity. These are configuration recommendations; this package does not change suite settings.
Install
From npm:
pi install npm:@devnazim/pi-cmux
From a local checkout:
pi install /path/to/pi-cmux
Or try without installing:
pi -e /path/to/pi-cmux
What it does
| pi event | cmux action |
|---|---|
| Agent starts | report the cmux workspace/surface as running (surface.report_shell_state) |
| Agent run ends with queued messages | keep/report the workspace/surface as running |
| Agent settles successfully | completion notification with session name + report prompt/idle |
| Agent settles after failure | error notification + error log + report prompt/idle |
| Agent settles after cancellation | report prompt/idle without a completion popup |
| Blocking Pi UI prompt opens | at most one "Pi needs input" popup per observed waiting episode |
| Blocking Pi UI prompt closes | cancel any pending input-wait popup |
| Session shuts down/reloads | discard queued deliveries and clear owned activity/status |
| Optional extension notification | popup/status/log best-effort, controlled by the caller |
The cmux-tui transport delivers completion, error, input-wait, and explicit notifications. It skips Mac shell-state, sidebar status, and log commands, and does not install native agent hooks or change agent badges.
All cmux calls are best-effort. Lifecycle handlers enqueue delivery without waiting for cmux, so a slow CLI does not delay agent startup or settlement. The queue preserves report order. A new run cancels obsolete completion delivery. Shutdown waits for in-flight delivery and owned cleanup, but discards pending notifications. Failed cleanup remains owned so shutdown can retry it. Abrupt process exits, including Pi's dead-terminal emergency exit, can skip extension shutdown. Subprocesses and cmux-tui socket requests have three-second timeouts. Socket mutations are sent once and are not retried after an uncertain result.
Each Pi session uses its own sidebar status key, registered with its local process PID and captured panel. Finishing one session does not clear another session's sidebar entry. cmux can remove owned entries after the process exits or the panel closes. Failed clears from retired sessions remain in a process-local retry list across extension reloads and retry on later session/start events.
Aborted runs do not generate success popups; failed runs, including responses truncated at the model's output token limit, use notifications.error, independently of notifications.done. On Pi 1.1.0 and newer, the settled event's cancellation flag takes priority over the last assistant message. Older versions infer cancellation from the assistant message's stop reason. Automatic completion and input-wait popups are disabled outside TUI mode unless notifications.headless is enabled. Activity reporting and logs are unaffected by this popup guard.
Input-wait alerts cover blocking ctx.ui dialogs, not assistant text asking a question. They use a generic body, without dialog titles, contents, or answers. Closing a prompt cancels a queued or in-flight delivery where possible; an already-delivered desktop notification cannot be withdrawn. Alerts follow Pi's delivered prompt events. Delayed handlers in other extensions can cause separate waiting episodes to be grouped together.
cmux-tui notifications
A Pi process inside a cmux-tui terminal receives its own socket and terminal identity. pi-cmux recognizes:
CMUX_TUI_SOCKET, or compatibilityCMUX_MUX_SOCKETCMUX_TUI_TERMINAL_ID- optional
CMUX_TUI_SESSION_ID
It sends a resource API v2 notification.create request directly to that socket, naming the explicit terminal. A supplied session ID is retained; without one, the socket's current session is used. No terminal focus lookup, helper executable, or machine-specific installation path is needed.
Titles and subtitles are limited to 512 Unicode characters, bodies to 4096. Oversized requests skip delivery.
Lifecycle delivery captures the socket, terminal, and session together. Later environment or focus changes cannot redirect a pending completion. Missing or invalid targets skip delivery instead of sending a session-global notification or falling back to another transport. Unsupported protocols and failed sockets are best-effort failures, not reasons to retry against another endpoint.
Socket and terminal identity remain process-scoped. pi-cmux does not import cmux-tui socket capabilities or terminal IDs from tmux's shared environment, where they can belong to another attachment. Start Pi from the intended cmux-tui terminal if a pre-existing tmux pane lacks those variables.
cmux-tui has its own separately installed Pi hook adapter. Do not enable automatic notifications from both integrations for the same process. This package neither installs nor changes that adapter. The ssh-tmux control-mode mirror is another transport; its OSC notification path is not implemented here.
Mac cmux, SSH relay, and tmux behavior
pi-cmux detects the Mac CLI/RPC environment with:
CMUX_WORKSPACE_IDor compatibilityCMUX_TAB_IDCMUX_SURFACE_IDor compatibilityCMUX_PANEL_ID- any non-empty
CMUX_SOCKET_PATH(including remote relay values like127.0.0.1:<port>) - deprecated
CMUX_SOCKETas a compatibility signal - the current
~/.local/state/cmux/cmux.sockpath and cmux's user-scoped socket variants, then legacy/tmp/cmux.sock, as filesystem fallbacks
It resolves an executable cmux CLI from CMUX_BUNDLED_CLI_PATH, falling back to cmux on PATH.
For SSH/tmux/surface-aware notifications, it targets the active cmux surface by preferring explicit env vars:
CMUX_SURFACE_IDCMUX_PANEL_ID
If neither is present but CMUX_WORKSPACE_ID or CMUX_TAB_ID exists, lookup depends on the connection:
- Local connections use
surface.list, exclude explicitly non-terminal panels such as browsers, and choose the focused surface, then the selected-in-pane surface, then the first eligible surface. Legacy entries without a type remain supported. - Relay connections use
surface.currentwith an explicitworkspace_id.pi-cmuxaccepts only a non-empty terminal surface whose returnedworkspace_idorworkspace_refmatches the requested workspace. UUID comparisons ignore letter case. Failed commands, malformed responses, ownership mismatches, and non-terminal results leave the surface unresolved.
Relay discovery is workspace-scoped and best-effort. Current cmux relay routing returns the first active remote terminal surface, or a mirrored tmux pane. It does not use the Mac's locally focused panel and cannot guarantee exact pane attribution when a workspace has several remote surfaces.
Lifecycle delivery captures this target once per Pi session. Once a surface is resolved, later focus changes cannot send the completion to another terminal or leave the original terminal marked busy. Connection details still refresh before every call. A changed workspace does not silently replace the captured target. If the initial lookup cannot resolve a surface, lifecycle shell-state reports and popups are skipped instead of falling back to current focus. If cmux no longer accepts a captured target, delivery remains best-effort; reload Pi to resolve a new one. The optional notifier API resolves its target for each request.
SSH relay notifications use the targeted RPC required by cmux v0.65.0:
cmux rpc notification.create_for_target '{"workspace_id":"...","surface_id":"...","title":"..."}'
Both workspace and surface IDs are required. If either is unresolved, relay popups are skipped, including direct optional notification requests. Relay requests never fall back to notification.create, which cmux v0.65.0 rejects even with both IDs present.
Local notifications continue to use notification.create, including workspace and surface IDs when known. With no surface, the workspace scope is retained for direct optional notification requests; captured lifecycle delivery skips the popup. With no routing context, local cmux resolves the notification from caller/focus context. pi-cmux does not use the local-only notification.create_for_surface method.
If TMUX_PANE is set, pi-cmux asks tmux for a readable pane label and prefixes notification bodies with it, e.g. [dev:1 %2] Ready for input. If tmux lookup fails, it falls back to the raw pane id.
When running inside tmux, pi-cmux also refreshes cmux's managed shared environment values from tmux show-environment before each cmux call. It does not import socket passwords/capabilities or stale surface IDs from tmux; those remain process-scoped, and the active surface is resolved from the refreshed workspace. This helps after SSH relay reconnects, such as when a laptop sleeps and wakes with a new CMUX_SOCKET_PATH port. Existing processes cannot recover if tmux itself still has stale shared cmux environment values; in that case, start a new cmux/tmux pane or restart pi from a shell with fresh CMUX_* variables.
This avoids terminal OSC notifications and works through SSH/tmux when the cmux shell integration exposes the needed env/socket/CLI access in the remote environment. Without that cmux environment, the extension silently no-ops.
Current cmux builds expose notification and shell-state RPCs as well as the top-level set-status, clear-status, and log commands. pi-cmux uses the shell-state RPC for lifecycle activity with the captured surface ID. Reports include CMUX_TERMINAL_LIFECYCLE_ID only when both the surface and lifecycle ID still match the explicit runtime environment. Inferred surfaces, including those resolved after tmux refresh, omit it because the lifecycle ID may belong to another terminal. Local connections probe cmux --help before optional sidebar status/log calls, accepting both legacy command rows and v0.65.0's grouped help layout. Successful probes are cached per executable; failed probes retry on a later call. Restricted remote relays do not accept the legacy sidebar protocol, so the extension skips set-status, clear-status, and log on those transports. Notification and shell-state RPCs remain available.
Configuration
Create ~/.config/pi-cmux/config.json or set PI_CMUX_CONFIG to another path.
{
"lifecycle": true,
"notifications": {
"done": true,
"error": true,
"xplan": true,
"input": true,
"headless": false
},
"status": true,
"logs": true
}
| Option | Default | Description |
|---|---|---|
lifecycle |
true |
Enable automatic activity, completion, and input-wait integration. Set false for notifier-only mode. |
notifications.done |
true |
Show successful-run "Pi done" notifications. |
notifications.error |
true |
Show failed-run notifications and allow error-level popups from optional callers. |
notifications.xplan |
true |
Allow popup notifications from source: "xplan". |
notifications.input |
true |
Show automatic popups for blocking Pi UI prompts. |
notifications.headless |
false |
Allow automatic completion and input-wait popups outside TUI mode. |
status |
true |
Report cmux workspace/surface activity, and allow supported optional status commands. |
logs |
true |
Write cmux log entries when the local CLI exposes cmux log; skip restricted relays. |
Malformed or omitted values fall back to defaults. Reload Pi after changing the file. These options do not change or install cmux's own hooks.
With lifecycle: false, explicit notifier requests still follow status, logs, and the relevant notification filters. Explicit requests are not blocked by notifications.headless; callers control whether to send them. Cleanup already owed by retired sessions can still retry.
Diagnostics
Run /cmux-status in Pi to inspect detection, CLI path where used, transport, target, optional command support, configuration, and pending retired-status cleanup count. cmux-tui diagnostics identify the tui transport and terminal/session IDs without exposing its socket capability. It also shows up to five recent cmux failures as operation names and numeric failure codes. Failed capability probes appear as null; unsupported commands appear as an empty list.
The command does not send a desktop notification. It excludes credentials, notification contents, and raw subprocess output. It does not enable cmux's first-party hooks or change configuration.
Optional notifier API
Other extensions can request cmux notifications without importing or depending on pi-cmux:
const notify = (globalThis as any)[Symbol.for("pi.cmux.notify.v1")];
if (typeof notify === "function") {
await notify({
source: "xplan",
type: "step_ready",
title: "xplan step ready",
body: "S2 is ready for review",
level: "success",
status: { key: "xplan", text: "review", icon: "check", color: "#22c55e" },
});
}
If pi-cmux is not installed, the symbol is absent. Callers should treat notifications as optional and never require them for workflow state.
Supported payload fields:
title— required notification titlesubtitle,body— optional body parts, joined with—source— log/status source, e.g.xplantype— caller-defined event typelevel—info,success,warning,error, orwarnnotify: false— skip popup notificationlog: false— skip cmux log entrystatus— optional keyed status set/clear request; used only when the installed cmux CLI supports those commands
Development
npm install
npm test
npm run check
The implementation no-ops outside cmux, scopes relay-safe notifications and shell-state reports to the active workspace/surface when possible, waits for Pi's fully settled lifecycle state, resolves SSH/tmux surfaces with workspace-scoped lookup, adds tmux pane labels and Pi session names for disambiguation, probes optional commands before using them, keeps sidebar status calls best-effort in the background, and executes commands without shell interpolation.