The artifact tool takes inline: true to also show the produced version below
its transcript card: Markdown/text rendered, HTML/SVG in the sandboxed preview
frame. Drawings fill the frame's width (an SVG with a viewBox or an absolute
size scales to it; an HTML page that is only an SVG or image too) and the frame
takes the content height the preview reports, capped at 70vh. Every viewer sees
it, guests included (owner decision). The TUI renders inline Markdown and text.
SVG previews now carry the diagnostics bridge in CDATA (its && broke the XML).
HTTP servers without an Authorization header sign in with OAuth: stored
tokens in mcp-auth.json, refreshed without a browser. /mcp/sign-in/<server>
returns to a loopback callback when the browser runs on the p2 host (the
device owner, or a relayed/public owner whose browser reaches a nonce probe
on 127.0.0.1), else to /mcp/callback on the origin p2 is reached through.
Pasting the final redirect URL always completes a waiting sign-in.
Web: an MCP sheet (status, restart, sign in/out, paste). TUI: /mcp and
/mcp paste. The sidebar footer's owner entries move into a More menu.
- Switching branches (navigate, select) no longer waits for the main conversation: a branch left while
running keeps working in the background, read-only with Stop allowed.
- Branch rows in the sidebar show the working dot; background branches' answers count as unread.
- Fence refusals name a running background branch instead of a subagent #N.
- Subagent reports go to the conversation that sent the message; team send to "parent" goes to the
conversation that last spawned or messaged the subagent.
- Runtime: ctx.panel shows one component right of the screen's body (about 40%,
hidden below 100 columns, dock stays full width). F2 moves the focus into it
(viewport and select keys scroll it, Escape returns), /close-panel closes it.
Closes on conversation switch and facet reload, like overlays.
- Artifacts: Enter opens the artifact in the panel when the terminal fits it,
else inline as before; the panel follows the artifact's newest version.
- Fix: /artifacts rows are keyed by name and version, so every version of an
artifact can be selected.
Branch rows are hidden until the session row's expand control (the
subagents' chevron) opens them, above the subagents; the control also
appears for a session with two or more branches and no subagents, with
the branch count beside the subagent count. Showing a subagent still
expands the list; showing any other non-main conversation no longer
does (right after a /tree switch the page shows the new branch before
the row names it main).
Pages and TUIs loaded before a deploy hold the contracts they were built
with; without accepts they wait unbound (contract_mismatch) until they
reload. The histories are now written with evolve from the released
versions, and the providers accept them:
- Sessions 3 (rows without main and branches) and 4 (a null branch label
reads as the session title); v4's treeSelf arguments pass as they are
- Catalog 2 and 3, the same row mappings
- SessionTree 1: the tree without labels (no branches, no node
conversation); rename is new and needs nothing
test/p2/contract-versions.test.ts binds the released declarations
against the current server.
p2.main version 2 stores a label per branch: { conversation, label }.
A new branch is labelled in the commit that forks it, with the start of
the message the owner picked (the answer for "Branch from here", the
edited user message for "Edit"). Conversation 1 keeps null, which every
UI shows as the session title. Version 1 migrates to null labels; at
start the worker fills the other branches once from their fork points.
- SessionTree v2: rename (owner), Tree.branches, TreeNode.conversation
- Sessions v5 / Catalog v4: a branch label may be null
- the worker reports p2.main changes; the derived labels are gone
- web: a pencil beside each sidebar branch row opens a rename sheet
- TUI: /tree shows branch labels; Shift+L renames the selected branch
quiet-tools draws every tool call as one line; Ctrl+O, a click or a failure
shows the full content. Its /settings row switches back to the tools' own cards.
TuiSettingFields lets plugins add /settings rows, stored at
settings.plugins.<plugin>.<name> of the visitor's prefs. Tool renderer
resolvers receive the settings; a change redraws every card.
Each branch is a top-level conversation forked from another at an entry;
the session-scoped p2.main document lists them and names the current one.
Earlier branches are read-only. The main agent's per-agent state
(subagents, artifacts, voice and chat mode, pending move) stays on
conversation 1 for every branch.
- tree plugin: SessionTree (tree, navigate, select); the worker reports
main and branches to the catalog (Sessions v4, catalog schema 4)
- TUI: /tree and double Escape (pi's default "tree"), a tree selector
adapted from coding-agent
- web: Edit on user messages, Branch from here on answers, branch rows
under the session in the sidebar
- main-agent checks use the branch list instead of conversation 1
- search indexes each branch's own entries
Design: apps/p2/docs/tree.md
Archiving from the session sheet navigates away first, which unmounted SessionOptions and released its Sessions
reference; Chord cancels a released reference's calls, so the archive call rejected with "p2.sessions: released",
shown as the composer toast over the owner-notice lane. The archive now holds a reference of its own until it is done,
and the server runs its steps in a root of its own.
The archive stopped the worker with Hosts.stop, which restarts it for its holders: the page's lingering
conversation started a worker the archived session refused (resolve_failed, "Worker: Session is archived" notice).
SessionWorkerControl.suspend stops it and leaves its holders suspended until a restore (retry) or their release;
archive, a failed fork's archived copy and an archived session's index rebuild use it.
The setting was saved but never read: thinking always started hidden.
It now sets the view whenever its effective value changes (unset = hidden);
the toggle overrides it until then and survives reloads.
MCP tools are deferred and callable only by other tools, so code mode scripts call them and neither the model nor tool_search sees them. While code mode is off or its plugin is not loaded, tool_search loads them instead, as pi falls back. The mcp_servers prompt section, as pi's, names the servers and how their tools are reached.
The heartbeat no longer counts time the process did not run (a laptop's sleep, a blocked event loop) as silence: after such a pause each open link pings at once and closes only if its peer stays silent for close - ping.
A running worker's lost link is now a stop instead of a failure: worker_link_lost is reported and the held route starts a new worker. A second lost link within a minute of the last recovery still fails the address.
A second Ctrl+C or SIGHUP killed the server without its exit hooks, leaving the macOS lock socket file behind; the next start then refused with a stale lock. A close that does not finish within 8 s now exits too.
A manager on the p2 server owns every MCP connection and process. Session
workers hold one view each and lease the locations their conversations
work at; the manager runs the owner's mcp.json servers there (stdio per
session and location, or shared; HTTP shared by default) and offers their
tools as deferred Durable tools found through tool_search.
Stdio servers on an environment start through its pi-env daemon, one
connection per environment shared by its instances, through cmd.exe on
Windows. A project's .pi/mcp.json, read by the worker at each location,
may add stdio servers that run there and switch the owner's servers on or
off; the manager refuses everything else. A lease gets servers only on
environments its session works on.
p2 (Pi Mobile on Chord v2) moves from pi-private into pi as one commit. Root scripts that loop all workspaces
use --if-present; npm test, ./test.sh and the release script cover packages/* only (p2: cd apps/p2 && npm test);
sync-versions also updates apps' workspace pins.