- Added transcriptMessageComponents to every InteractiveMode ctx test literal; #6033's reuse cache made the field required and addMessageToChat populates it unconditionally.
- Wired setTodoPhases into the eager-todo ToolSession fixture to mirror sdk.ts; the test relied on the stale message_end todo replay #6148 removed.
- Added a Usage.orchestration sidecar for provider-side service tokens so Responses/Codex totals and costs stay accurate without inflating visible prompt input/cache buckets.
- Updated Codex/WebSocket usage, session/status aggregates, and usage reporting to preserve orchestration-aware totals.
- Added regressions for OpenAI Responses accounting, Codex WebSocket terminal usage, cost calculation, and session aggregation.
Fixes#4469
- Omit Next from the guest multi-select ui-request options until at least
one option is checked or a custom answer exists, mirroring the local
dialog's disabled-Next gating. The remote select has no disabled-row
concept, so Next is omitted rather than dimmed (PRRT_kwDOQxs0bc6OFbDW).
- Add the bottomBorder(1) term to the ask dialog's fixed-row budget so the
rendered dialog no longer overflows the viewport by one row
(PRRT_kwDOQxs0bc6OFbDY).
- Add focused tests: guest wire-level Next gating round trip, and dialog
height <= viewport assertion.
- Replace #requestGuestUiString's string|"unavailable"|undefined channel
with a tagged GuestUiResult ({answered}|{cancelled}|{unavailable}) so a
guest answer literally equal to "unavailable" no longer collides with
the transport-unavailable sentinel (PRRT_kwDOQxs0bc6OE3gN).
- Cap in-body question header rendering to MAX_HEADER_ROWS with ellipsis
truncation so long/multiline questions cannot push options off-screen
(PRRT_kwDOQxs0bc6OE3gS).
- Guest Other editor cancellation now continues the loop (multi) / re-shows
the select (single) instead of cancelling the whole ask
(PRRT_kwDOQxs0bc6OE3gU).
- Disable the Next row on a single-question multi-select until at least one
option or custom input is chosen, preventing empty-result submission
(PRRT_kwDOQxs0bc6OE3gY).
- Introduced the `CollabGuestUiResult` type to distinguish between answers and unavailable states during guest UI requests.
- Updated the remote dialog race logic to ignore unavailable guest results and fallback to the local host dialog.
- Centralized the `FakeWebSocket` and `InMemoryRelay` test infrastructure into a shared test helpers file.
- Cleaned up duplicate mock implementations and updated existing test suites to utilize the shared in-memory relay helpers.
- Prevents connection hangs during initial join by immediately failing when a host rejects a guest prior to welcome.
- Short-circuits the connection welcome timeout upon receiving a pre-welcome error frame.
- Surfaces exact protocol mismatch and hello rejection reasons directly to the joining guest interface.
- Ensures `CollabGuestLink` and `GuestClient` transition immediately to ended/failed states with host error details.
- CollabGuestLink now handles ui-request/ui-request-end frames via the existing hook selector/editor dialogs and round-trips ui-response; cancellation, resync replay, and read-only peers are handled.
Per #3740 review: many short strings (e.g. a tool result whose content array holds thousands of small text blocks) could sum past MAX_REPLICATED_PAYLOAD_BYTES without any individual field crossing the per-string floor, so the helper exited the truncation loop and shipped an oversized frame — the relay close/reconnect loop the helper was meant to prevent.
Replace the string-only truncation pass with a single walker that head-truncates strings AND head-clips arrays in one descent, driven by a concrete SHRINK_PASSES schedule that tightens both axes together. The final pass clamps every string to 64 B and every array to one element, so any payload converges. Add direct unit tests for shrinkForReplication covering: identity for small values, single-giant-string clamp, many-short-strings array clamp (no field above floor), and discriminator preservation on a fully-shrunk payload.
CollabHost shipped the first entry of every snapshot-chunk batch unconditionally, and broadcast live entry/event frames verbatim, so a single multi-megabyte tool result (read/bash/search) overflowed the relay's per-frame maxPayloadLength. The relay closed the host's WebSocket with 1006 ("Received too big message"), CollabSocket treated 1006 as non-fatal and reconnected, the next guest hello triggered the same oversized send, and the host status line cycled "Collab relay connection lost, reconnecting…" indefinitely.
Add shrinkForReplication: any host->guest payload whose JSON exceeds MAX_REPLICATED_PAYLOAD_BYTES (1 MB) is deep-cloned with long strings head-truncated and an "[…N chars elided for collab session]" marker; otherwise the original reference passes through. Apply it to snapshot chunk entries, live entry broadcasts, and live event broadcasts (including large tool_execution_end results). Regression test stands up a Bun.serve relay with 8 MB maxPayloadLength and a snapshot containing a 5 MB entry; asserts the host stays connected, the snapshot train finalizes, and the guest sees the entry with the elision marker.
Fixes#3739
- Introduced guest snapshot reconciliation to maintain host state consistency during session switching.
- Improved yield tool reliability by implementing incremental schema validation and strict parameter enforcement.
- Fixed a calculation edge case in the status line to prevent negative time values during activity tracking.
- Expanded the test suite with new validation for session interruption, collab state synchronization, and process error handling.
Address PR review: collab guest reconciles a state frame with
isStreaming === false by stopping the loader when the host's
agent_end never reached us (typically across a reconnect). With the
new active-time meter, the same path also has to call markActivityEnd
or the per-session window stays open and time_spent ticks forever.
Extract the close logic as reconcileGuestIdleHostState so a unit test
can exercise it directly without bringing up the full host/relay
welcome train.
The time_spent segment rendered Date.now() - sessionStartTime, so an
idle session displayed hours of "time spent" while the agent did
nothing — the only inputs were wall-clock and the unmoving session
start.
Replace sessionStartTime with activeMs in SegmentContext and accumulate
inside StatusLineComponent across agent_start -> agent_end windows.
markActivityStart/markActivityEnd are idempotent (reentrant agent_start
events and superseded agent_end events never double-count); the segment
ticks live during an open window and freezes when the agent yields.
The session-boundary hook drops the now-meaningless wall-clock argument
and is renamed setSessionStartTime -> resetActiveTime; it zeroes the
accumulator and drops any in-flight window so /clear / fresh-session /
joined-collab paths start the meter at zero.
Fixes#3681
The chunked welcome path cleared its snapshot progress timer before
writing the replica file and switching sessions. If that apply work
failed, the frame-apply catch only logged the error, leaving the
initial join promise pending with no welcome/progress timer left to
settle it.
Reject the pending initial join when a welcome or snapshot-chunk apply
fails before the join has completed, preserving reconnect-time logging
for already-joined guests. Add a regression test that forces the replica
write to fail and asserts /join rejects instead of hanging.
Fixes#3144
The host used to ship the entire transcript inside a single welcome
frame, so a multi-MB session spent the guest's 30s first-welcome
timeout on the relay transfer itself: ~1.3 MB took ~3s, ~4.2 MB took
~12s, and ~13.6 MB never arrived before the guest gave up with
'timed out waiting for the host's welcome'.
Bump COLLAB_PROTO to 2 and split the welcome:
- welcome carries metadata only (header, state, agents, entryCount,
readOnly) and lands in well under one second.
- a train of snapshot-chunk frames (SNAPSHOT_CHUNK_BYTES = 512 KB,
oversize entries ship alone) carries the transcript. Last chunk
flips final: true; an empty snapshot still emits one final chunk.
- the host queues welcome + chunks synchronously inside #handleHello,
preserving the host comment's ordering invariant (later broadcast
frames cannot interleave between them).
- the TUI guest accumulates chunks under a SNAPSHOT_PROGRESS_TIMEOUT_MS
that resets per chunk; only after final does it write the replica
jsonl, switchSession, and render. The first-welcome timeout still
guards arrival of the small welcome.
- the collab-web GuestClient streams entries into the snapshot as
chunks arrive and flips phase to 'live' on final.
Includes a contract test (in-process relay) asserting the welcome is
metadata-only, the chunk train fans the 1.5 MB synthetic transcript
across multiple frames with only the last marked final, and the
flattened entries match the source snapshot.
Fixes#3144
- Added collab.webUrl and rendered browser links as web UI wrappers whose fragments carry relay links.
- Added one-shot /collab qrcode and /collab qrcode-view commands with terminal QR rendering.
- Updated coding-agent and collab-web parsers to prefer parseable wrapper fragments while preserving legacy links.
- Added regression tests and changelog entries for split-host collab links and QR commands.
- Replaced Bun.sleep and wall-clock timing with fake timers (vi.useFakeTimers), release gates, and deterministic polling across 15+ test files to eliminate flakiness and improve speed.
- Consolidated per-test fixture setup into beforeAll/afterAll lifecycle hooks across 20+ test files, reducing redundant initialization and improving test performance by reusing shared immutable fixtures.
- Stubbed network calls in ModelRegistry and test discovery to prevent unintended outbound requests during test execution.
- Replaced subprocess-based test coordination (file markers, Bun.sleep polling) with in-memory fakes (FakeWebSocket, FakeLspServer, VirtualClock) for deterministic, fast test execution.
- Added session-domain modules and exports for session-entries, context, listing, loader, and migrations.
- Changed persistence to async append writes plus writeTextAtomic, removing sync line APIs.
- Added compaction-aware session context rebuild with dangling tool-call cleanup.
- Added resumable session resolution with status inference, id/stem/suffix matching, and backup recovery.
- Replaced queued-message interrupt flow with session abort calls on empty submit and escape.
- Removed interrupting state and notifyInterrupting teardown paths from abort handling.
- Updated AgentSession queue operations to use shared steering and follow-up queue views.
- Propagated isAborting through session state and collab payloads to suppress late updates.
The host schedules a debounced state broadcast right after hello; on slow
runners it lands between a guest's mutating frame and the directed error
reply, so nextFrame() saw "state" instead of "error". Guests now drop
broadcast-only frames (state/agents/entry/event/bus) and wait solely on
directed welcome/error replies.
- Re-polled steering at the loop yield boundary and included it in the pre-stop pending batch so late messages are processed immediately.
- Added session-side draining for stranded queued messages, scheduling an auto-continue when a prompt settles and follow-ups or steers remain.
- Added a regression test for late steering injection at yield and updated mid-turn collab prompt handling to keep steering messages in the pending display queue until consumed.
- Changed the link grammar from `<roomId>#<key>` / `host[:port]/r/<roomId>#<key>` to dot-joined `<roomId>.<key>` in `formatCollabLink`/`parseCollabLink` (`packages/coding-agent/src/collab/protocol.ts`) and the collab-web mirror (`packages/collab-web/src/lib/link.ts`): RFC 3986 forbids a raw `#` inside a fragment, so strict URL stacks (macOS Foundation behind terminal click-to-open) percent-encoded the second `#`.
- Kept legacy `#`-joined links parseable via `BARE_LINK_RE` and added lenient `%23` → `#` decoding for mangled deep links.
- Updated the `ConnectScreen` placeholder, `app.tsx` deep-link comment, `DEFAULT_RELAY_URL` doc in `packages/wire`, `docs/collab.md` examples, and both package CHANGELOGs.
- Extended `crypto.test.ts` and `link.test.ts` with dot-joined, legacy-hash, and `%23`-mangled link coverage.
- Changed `DEFAULT_RELAY_URL` in `@oh-my-pi/pi-wire` from `wss://relay.omp.sh` to `wss://my.omp.sh` and updated the constants, collab-link, and join-command tests asserting the old origin.
- Added `DEFAULT_SHARE_URL` (`https://my.omp.sh/s`) to pi-wire with its changelog entry; it shares a changed run with the relay constant and is consumed by the upcoming /share work.
- Updated collab-web canonical/OG/twitter URLs in `index.html`, `robots.txt`, `sitemap.xml`, README, and the `local-relay.ts` doc comment to the new domain.
- Refreshed the `/collab` entry in the coding-agent changelog to reference the new relay origin.
- Extended `parseCollabLink` test coverage to reject 16-byte and 40-byte fragments instead of only rejecting short keys.
- Added coverage for full-link fragments to ensure key and write token are parsed when present.
- Updated `GuestClient` test setup so `welcomeFrame` carries a `readOnly` flag into snapshot assertions.
- Enabled read-only mode by wiring snapshot.readOnly through AgentDrawer and Composer to block prompts and controls.
- Added read-only indicators in the header and participant titles for view-only sessions.
- Added SEO and app metadata assets by updating index.html head tags, manifest, robots.txt, and sitemap.xml.
- Updated brand presentation by adding new favicon/OG assets and switching theme tokens to new OMP colors.
- Added write-token generation and validation to distinguish writable and read-only guests.
- Added deep-link support using `https://<relay>/#<link>` with 32/48-byte collab secrets.
- Added full-link and key-only semantics where full links grant writes and key-only links are view-only.
- Added /collab view/status/stop command updates with read-only participant status and join hints.