Skip to content

feat(web): postMessage agent status from /embed/thread route to iframe parent #3

@rororowyourboat

Description

@rororowyourboat

Goal

Extend the existing `/embed/thread/:environmentId/:threadId` route to broadcast agent status events to its iframe parent via `window.parent.postMessage`. Enables the host (t3-canvas) to render per-tile status chips so users can triage agents at a glance.

Context

t3-canvas embeds T3 Code threads as canvas tiles. Phase 4.5 over there needs each tile to show a live status indicator (running / awaiting input / error / etc). The cleanest source of truth is T3 Code itself — we know turn state, approvals, errors. The cheapest channel is `window.parent.postMessage` since the iframe and host are same-origin localhost.

Acceptance criteria

  • EmbedThreadView (or wherever the route component lives) subscribes to the existing thread state in the store via the same hooks used by `_chat.$environmentId.$threadId.tsx`
  • On every relevant state transition, post a message to `window.parent` with shape:
    ```ts
    window.parent.postMessage({
    source: "t3-code-embed",
    type: "agent-status",
    threadId: string,
    environmentId: string,
    status: "idle" | "streaming" | "awaiting-input" | "error" | "completed" | "disconnected",
    timestamp: number,
    // optional payload — leave room for future fields without breaking consumers
    payload?: { errorMessage?: string; turnId?: string }
    }, "*");
    ```
  • Status mapping (start with these, refine if T3 Code's internal taxonomy differs):
    • turn started / first delta arrives → `streaming`
    • turn finished cleanly → `completed` (host will auto-fade to idle)
    • approval/user-input request received → `awaiting-input`
    • turn errored → `error` (include errorMessage in payload)
    • no active turn AND last turn was not just completed → `idle`
    • no thread or thread fetch failed → `disconnected`
  • Initial post on mount with current state
  • Cleanup on unmount: post a final `disconnected` so the parent knows the iframe went away
  • Wrap the postMessage in a `source: "t3-code-embed"` discriminator so the host can ignore unrelated postMessage chatter
  • Existing route behavior (rendering the conversation, no sidebar chrome) is unchanged
  • No new dependencies — read state from existing stores/hooks only

Files (verify against current repo state)

  • `apps/web/src/routes/embed.thread.$environmentId.$threadId.tsx` — add a useEffect that subscribes to thread state and emits postMessage on changes
  • `apps/web/src/components/EmbedThreadView.tsx` (or wherever the conversation component is) — possibly the better location depending on how state is wired

Read `apps/web/src/routes/_chat.$environmentId.$threadId.tsx` to see how the main route subscribes to thread state. Reuse the same hooks (`useStore`, `createThreadSelectorByRef`, `selectEnvironmentState`).

Branch

`feat/embed-status-postmessage` (separate from any other in-flight work).

Non-goals

  • Two-way postMessage (host → embed) — read only for now
  • Authenticated postMessage origin checks — same-origin localhost only
  • Wire status events for tools-use, model thinking deltas, etc. — keep the taxonomy minimal for v1

How to test

  1. `bun run dev` in t3code
  2. Build & run t3-canvas with the matching Phase 4.5 PR
  3. Create a thread in T3 Code, copy env+thread IDs, open via t3-canvas's New Agent dialog
  4. Send a turn → t3-canvas tile border should pulse blue (streaming) → settle green (completed) → fade gray (idle)
  5. Trigger an approval-required tool → border turns amber

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions