Skip to content

Protocol Package

The @pizzapi/protocol package (packages/protocol) contains the shared TypeScript type definitions that every PizzaPi component — CLI runner, relay server, and web UI — uses to communicate over Socket.IO. It has zero runtime dependencies (dev-only typescript) and exports only types, constants, and small pure-function helpers.

By keeping wire types in a single package, any breaking change to the protocol surfaces as a compile error everywhere it matters, before anything ships.

packages/protocol/
├── src/
│ ├── index.ts # Re-exports everything
│ ├── shared.ts # Types used across multiple namespaces
│ ├── relay.ts # /relay namespace (CLI ↔ Server)
│ ├── runner.ts # /runner namespace (Runner daemon ↔ Server)
│ ├── runners.ts # /runners namespace (Browser runner feed)
│ ├── viewer.ts # /viewer namespace (Browser viewer ↔ Server)
│ ├── hub.ts # /hub namespace (Session list feed)
│ ├── terminal.ts # /terminal namespace (Browser terminal ↔ Server)
│ ├── meta.ts # Session meta-state & meta relay events
│ ├── password.ts # Password validation (shared UI/server/CLI)
│ └── version.ts # Socket protocol versioning & semver helpers
└── package.json # @pizzapi/protocol

PizzaPi uses six Socket.IO namespaces, each with strongly-typed event maps for client→server, server→client, inter-server (Redis adapter), and per-socket metadata. Every namespace interface follows the same four-interface pattern:

Interface suffixDirectionPurpose
ClientToServerEventsClient → ServerEvents the client emits
ServerToClientEventsServer → ClientEvents the server emits
InterServerEventsServer → ServerReserved for Redis adapter cross-node communication
SocketDataPer-socket metadata attached during handshake

File: relay.ts

The primary control channel between a running pi agent session (TUI) and the relay server. This is the highest-traffic namespace — every agent event (heartbeats, message updates, tool calls) flows through here.

Key client→server events:

EventPurpose
registerRegister a new or existing session (sends cwd, sessionName, optional parentSessionId)
eventForward an agent event with a sequence number for ordering
session_endSignal that a session has ended
session_messageSend an inter-session message (with optional deliverAs: "input")
session_triggerFire a child→parent trigger (questions, plans, completion)
trigger_responseParent responds to a child trigger
cleanup_child_sessionRequest teardown of a completed child session
delink_childrenSever all child links (e.g. on /new)
delink_own_parentChild severs its own parent link
exec_resultReturn the result of a remote exec command

Key server→client events:

EventPurpose
registeredConfirm registration — returns sessionId, token, shareUrl
event_ackAcknowledge receipt of a sequenced event
inputDeliver viewer input to the agent (with optional attachments)
model_setInstruct agent to switch model
execRemote command execution request
session_triggerDeliver a child trigger to the target session
trigger_responseDeliver a trigger response back to source child
parent_delinkedNotify child that its parent ran /new
session_expiredNotify that a session has expired

File: viewer.ts

Connects the web UI to a specific session. Viewers receive replayed event history on connect and live events afterwards.

Key client→server events:

EventPurpose
switch_sessionLogically switch the socket to a different session (with generation counter)
resyncRequest a fresh snapshot replay
inputSend user input (collab mode)
model_setSwitch model (collab mode)
execRemote exec command
service_messageForward a service message to the runner
trigger_responseHuman viewer responds to a child trigger (with Socket.IO ack)
mcp_oauth_pasteSubmit a pasted OAuth callback code for MCP servers

Key server→client events:

EventPurpose
connectedConfirm viewer attachment — includes lastSeq, replayOnly, isActive
eventForward an agent event (with optional replay flag and generation)
disconnectedAgent disconnected (with reason code)
service_messageService message from runner
service_announceAvailable services on the connected runner
trigger_errorError delivering a trigger response (carries triggerId for UI matching)

File: runner.ts

The largest namespace — handles runner lifecycle, session spawning, terminal management, file operations, skill/agent/plugin CRUD, model listing, usage data, and service messaging.

Key client→server events:

EventPurpose
register_runnerRegister with name, roots, skills, agents, plugins, hooks, version, platform
runner_session_eventForward a session event from a worker process
session_ready / session_error / session_killedWorker lifecycle signals
skills_list / agents_list / plugins_listRespond to listing requests
skill_result / agent_result / file_resultRespond to CRUD operations
models_listReturn available models
usage_data / usage_errorUsage dashboard data
service_message / service_announceService communication and discovery
runner_warning / runner_warning_clearReport/clear runner warnings
terminal_ready / terminal_data / terminal_exit / terminal_errorTerminal PTY output
disconnect_sessionRequest relay to disconnect an adopted session

Key server→client events:

EventPurpose
runner_registeredConfirm registration — includes existingSessions for re-adoption
new_sessionSpawn a session (with cwd, prompt, model, skills, agent config, hiddenModels)
kill_session / session_endedSession lifecycle commands
list_skills / create_skill / update_skill / delete_skill / get_skillSkill CRUD
list_agents / create_agent / update_agent / delete_agent / get_agentAgent CRUD
list_pluginsPlugin discovery
list_files / search_files / read_fileFile operations
list_models / get_usageModel and usage queries
new_terminal / terminal_input / terminal_resize / kill_terminalTerminal PTY control
service_messageForward a service message from a viewer
sandbox_get_status / sandbox_update_configSandbox configuration
restart / shutdown / pingRunner lifecycle

File: hub.ts

A mostly read-only feed that pushes session list updates to the web UI dashboard.

Server→client events:

EventPurpose
sessionsFull session list snapshot (sent on connect)
session_added / session_removedSession lifecycle
session_statusStatus change (active/inactive, model, runner, name)
state_snapshotFull SessionMetaState for a subscribed session
meta_eventIncremental meta-state patch for a subscribed session

Client→server events:

EventPurpose
subscribe_session_metaStart receiving meta-state updates for a session
unsubscribe_session_metaStop receiving meta-state updates

File: runners.ts

A read-only feed that pushes runner daemon status to the web UI. Clients emit no events.

EventPurpose
runnersFull runner list snapshot (sent on connect)
runner_addedA runner daemon connected
runner_removedA runner daemon disconnected
runner_updatedRunner metadata changed (skills, agents, plugins, hooks, warnings)

File: terminal.ts

Connects the web UI’s terminal panel to a PTY process spawned on the runner.

Client→server: terminal_input, terminal_resize, kill_terminal

Server→client: terminal_connected, terminal_ready, terminal_data, terminal_exit, terminal_error

File: shared.ts

Types used across multiple namespaces. These are the core data structures of the protocol.

interface SessionInfo {
sessionId: string;
shareUrl: string;
cwd: string;
startedAt: string;
sessionName: string | null;
isEphemeral: boolean;
isActive: boolean;
lastHeartbeatAt: string | null;
model: ModelInfo | null;
runnerId: string | null;
runnerName: string | null;
parentSessionId?: string | null;
viewerCount?: number;
userId?: string;
userName?: string;
expiresAt?: string | null;
}
interface ModelInfo {
provider: string;
id: string;
name?: string;
}
interface RunnerInfo {
runnerId: string;
name: string | null;
roots: string[];
sessionCount: number;
skills: RunnerSkill[];
agents: RunnerAgent[];
plugins?: RunnerPlugin[];
hooks?: RunnerHook[];
version: string | null;
platform?: string | null;
serviceIds?: string[];
panels?: ServicePanelInfo[];
triggerDefs?: ServiceTriggerDef[];
warnings?: string[];
}
interface Attachment {
attachmentId?: string;
mediaType?: string;
filename?: string;
url?: string;
}

The service system enables runner-side plugins to communicate with the web UI without the relay needing to understand service-specific semantics. Messages are wrapped in a generic ServiceEnvelope:

interface ServiceEnvelope {
serviceId: string;
type: string;
requestId?: string;
sessionId?: string; // Attached by relay when forwarding viewer→runner
payload: unknown;
}

Services can expose UI panels (proxied via WebSocket tunnels) and declare trigger types:

interface ServicePanelInfo {
serviceId: string;
port: number; // Local port, proxied via tunnel
label: string; // Button label in UI
icon: string; // Lucide icon name
}
interface ServiceTriggerDef {
type: string; // e.g. "godmother:idea_moved"
label: string;
description?: string;
schema?: Record<string, unknown>; // JSON Schema for output payload
params?: ServiceTriggerParamDef[]; // Subscriber-provided config params
}
interface TunnelInfo {
port: number;
name?: string;
url: string; // Relay tunnel URL fragment
pinned?: boolean; // Auto-registered by daemon — hidden from UI tunnel list
}

File: meta.ts

The meta-state system tracks ephemeral session UI state — todo lists, pending questions, token usage, model info, etc. — and synchronizes it from CLI to server to viewers via discrete events.

The authoritative shape stored in Redis. Key fields:

FieldTypeDescription
todoListMetaTodoItem[]Agent’s task list
pendingQuestionMetaPendingQuestion | nullActive AskUserQuestion prompt
pendingPlanMetaPendingPlan | nullActive plan awaiting approval
planModeEnabledbooleanWhether plan mode is on
isCompactingbooleanContext compaction in progress
retryStateMetaRetryState | nullAPI retry error info
pendingPluginTrustMetaPluginTrustPrompt | nullPlugin trust prompt
mcpStartupReportMetaMcpReport | nullMCP server startup timing/errors
tokenUsageMetaTokenUsage | nullInput/output/cache token counts + cost
providerUsageMetaProviderUsage | nullPer-provider usage breakdown
modelMetaModelInfo | nullCurrent model
versionnumberMonotonic counter for change ordering

A discriminated union of 17 event types. The CLI emits these as regular relay events; the server intercepts them (via isMetaRelayEvent()), applies them to Redis state (via metaEventToPatch()), and broadcasts to hub subscribers.

Event types: todo_updated, question_pending, question_cleared, plan_pending, plan_cleared, plan_mode_toggled, compact_started, compact_ended, retry_state_changed, plugin_trust_required, plugin_trust_resolved, mcp_startup_report, token_usage_updated, thinking_level_changed, auth_source_changed, model_changed.

File: password.ts

Shared password validation used by the server, UI, and CLI. Ensures consistent enforcement of password rules everywhere.

  • Minimum 8 characters, maximum 128 characters (scrypt DoS protection)
  • At least one uppercase, one lowercase, and one digit
  • Uses Unicode codepoint counting (emoji = 1 character)

Exports validatePassword() (detailed per-rule results) and isValidPassword() (simple boolean).

File: version.ts

The SOCKET_PROTOCOL_VERSION constant (currently 1) is sent during Socket.IO handshake. The server checks it via isSocketProtocolCompatible() — missing or unparseable versions are treated as compatible (graceful degradation), while mismatched integer versions are flagged so the UI can show an update banner.

Also exports semver utilities (parseSemverTriplet, compareSemver) used for runner version comparisons.

Here’s how the pieces fit together:

CLI Agent Relay Server Web UI
───────── ──────────── ──────
/relay register ──────────► Store in Redis ◄──────── /hub sessions
(session hash)
/relay event ─────────────► Append to stream ────────► /viewer event
(seq N) event_ack(N) (replay: false)
isMetaRelayEvent?
▼ yes
metaEventToPatch()
Update Redis meta ──────────► /hub meta_event
state_snapshot
Runner daemon /runner namespace
register_runner ──────────► Store RunnerInfo ────────► /runners runner_added
service_announce ─────────► Cache panels/triggers ───► /viewer service_announce
service_message ──────────► Forward verbatim ────────► /viewer service_message
◄──────── /viewer service_message (reverse path)
/viewer input ────────────► Forward to relay ────────► /relay input → CLI
/viewer trigger_response ─► Route to child ────────► /relay trigger_response

When adding new events or types:

  1. Add the type in packages/protocol/src/ — put it in the appropriate namespace file, or shared.ts if used across namespaces.

  2. Re-export from index.ts — every public type must be re-exported.

  3. Run typecheck across all packagesbun run typecheck from the repo root. The server, UI, and CLI all import from @pizzapi/protocol, so any breaking change surfaces immediately.

  4. Update both sides — every event needs a sender and a handler. Adding a client→server event means updating the server’s socket handler. Adding a server→client event means updating the consuming component (UI store, CLI handler, etc.).

  5. Bump SOCKET_PROTOCOL_VERSION only for handshake-level breaking changes (new required auth fields, incompatible connection semantics). New events added to existing namespaces do NOT require a version bump — unknown events are simply ignored by older clients.

Protocol fileServer handlerCLI extension
relay.tspackages/server/src/socket/relay.tsRemote extension (packages/cli/src/extensions/remote/)
runner.tspackages/server/src/socket/runner.tsRunner daemon (packages/cli/src/runner/)
viewer.tspackages/server/src/socket/viewer.ts— (consumed by packages/ui/)
hub.tspackages/server/src/socket/hub.ts— (consumed by packages/ui/)
runners.tspackages/server/src/socket/runners.ts— (consumed by packages/ui/)
terminal.tspackages/server/src/socket/terminal.tsRunner daemon terminal handler
meta.tspackages/server/src/socket/relay.ts (meta interception)Remote extension (meta event emission)
password.tsAuth routesCLI auth commands, UI signup/password forms
version.tsSocket handshake middlewareCLI/runner connect handshake