Skip to content

feat: add intercom extension API for typed subagent to root messaging#21

Open
avtc wants to merge 1 commit into
nicobailon:mainfrom
avtc:feature/intercom-extension-api
Open

feat: add intercom extension API for typed subagent to root messaging#21
avtc wants to merge 1 commit into
nicobailon:mainfrom
avtc:feature/intercom-extension-api

Conversation

@avtc
Copy link
Copy Markdown

@avtc avtc commented May 11, 2026

What

Adds a public extension API to pi-intercom that allows other extensions running in headless subagent sessions to communicate with the root session (which has UI access) through structured, typed messages.

Why

Previously, pi-intercom had hardcoded handlers for specific message types (permission requests, interview requests). Every new extension that needed to interact with the user through the root session required modifying pi-intercom itself.

This PR replaces that coupling with a generic, extensible protocol. Any extension can now register a handler and send typed messages to the root session — pi-intercom handles routing, serialization, and reply delivery without knowing anything about the specific use case.

How

API surface

The API is emitted via an intercom:ready event on pi.events (fired during session_start, after all extensions have loaded — no load-order issues):

Root session (hasUI=true) — register a handler:

pi.events.on("intercom:ready", (api) => {
  api.registerHandler("ask_user_question", async (ctx, from, message) => {
    // message.payload = { questions: Question[] }
    // ... present to user, collect answer ...
    return { text: JSON.stringify({ questions, answers, cancelled: false }) };
  });
});

Subagent session — send and wait for reply:

pi.events.on("intercom:ready", (api) => {
  const reply = await api.sendAndWait({
    contentType: "ask_user_question",
    payload: { questions: [...] },
    text: "Ask user question from subagent (2 question(s))",
    timeoutMs: Infinity,
  });
  // reply.text = JSON.stringify({ questions, answers, cancelled })
});

Key pieces

  • Typed message routingcontentType + payload fields on Message; a registry (Map<string, Handler>) routes incoming messages to the correct handler
  • sendAndWait() — queued, serialized RPC-style call from subagent to root session; concurrent calls chain sequentially so only one reply waiter is active at a time
  • Configurable timeoutreplyTimeoutMs config option (default 10 min, supports Infinity)
  • Reload-safe listenersglobalThis-backed unsubscribe array prevents listener stacking on /reload
  • Decoupled — removed all hardcoded handlers; pi-intercom is now a neutral transport layer

Tested with

pi-askuserquestion — PR (ghoseb/pi-askuserquestion#1)

Expose a public API (emitted via `intercom:ready` event) that allows
other extensions to communicate with the root session (hasUI=true)
through structured typed messages:

- `registerHandler(contentType, handler)` — extensions register handlers
  for specific content types; pi-intercom auto-routes incoming messages
  and sends replies back to the caller
- `sendAndWait({ contentType, payload, text, timeoutMs })` — queued,
  serialized RPC-style call from subagent to root session with
  configurable timeout
- `contentType` + `payload` fields on Message for structured routing
- `replyTimeoutMs` config option (default 10 min, supports Infinity)
- Reload-safe listener cleanup via globalThis-backed unsubscribe array

This decouples pi-intercom from specific extension integrations — any
extension can now interact with the user through the root session
without pi-intercom knowing about the use case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant