feat(session): preserve Claude turns and improve live session controls - #44
Conversation
- Keep Claude compaction and context usage aligned with native boundaries. - Preserve account-scoped controls and suppress phantom Codex sessions. - Track receipt-backed timed messages and display their sidebar activity. - Keep sidebar menus visible above mobile and footer boundaries. - Cover state transitions and UI behavior with regression tests and docs.
- Move active tasks into a compact composer indicator with desktop previews and centered mobile details. - Follow native task completion, preserve detail actions, and cover keyboard focus and task lifecycle behavior.
- Set the native thinking display option for new, resumed and private-fork sessions. - Preserve native thinking mode, token budget and effort. - Cover native SDK launch arguments and connection lifecycle regressions.
- Route child task updates to their agent and prevent replayed completions from reopening sessions. - Bind autonomous replies to the originating turn and preserve source completion times. - Rebuild affected Claude history projections and cover managed, live, and recovery paths.
- Add six muted palettes with per-engine preferences and a responsive theme picker. - Add a persistent bold text toggle that preserves typography hierarchy across dialogs and engines. - Include a static theme preview and desktop, mobile, storage, and contrast regressions.
- Raise reading weights while preserving emphasis and font metrics. - Add a subtle text stroke without changing SVG icon geometry. - Cover cross-tab preferences, engine switches, and storage failures.
- Send image and file payloads through query, steer, and deferred delivery. - Scope drops and clipboard imports to the intended draft across tab switches. - Keep attachment-only sends distinct from interruption and respect input locks. - Add Codex service-tier controls scoped to the side chat.
- Normalize Claude and Codex usage with exact turn ownership. - Preserve bounded replace-only readings across replay and reconnects. - Show live input, output, and cache counts beside the working indicator. - Add accounting and browser regressions and coordinate protocol v68.
- Preserve internal Claude recovery boundaries and rebuild stale cached history. - Show autonomous continuations and stream subagent details with bounded retries. - Present readable command and tool results with raw payloads available on demand. - Preview Mermaid artifacts and align muted process shimmer with response text. - Keep token counts immediate and open usage details only on click or tap. - Validate the release snapshot with the complete local gate and the development branch with 166 Python regressions and Web reliability checks.
- Recover accepted turns and pending questions through an optional independent SDK service. - Support priority-next steering with exact native message, attachment, and usage ownership. - Reserve private BTW identities, refresh live context, and retry busy session-list reads. - Replace replayed text prefixes and bound history summaries with protocol v70.
- Replace replayed message prefixes in the terminal projection while preserving later deltas. - Use protocol v71 for the combined queue reordering and session recovery schema. - Synchronize deployment references and exercise Linux service guards on every test host.
- Keep automatic checks for pull requests and pushes to master. - Remove Playwright from CI and the required local PR gate. - Keep lint and reliability checks local and align release workflow coverage.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8b3b8c4dcc
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if key not in self.callback_tasks: | ||
| self.callback_tasks[key] = asyncio.create_task(self._callback(call)) |
There was a problem hiding this comment.
Retry failed persistent-service callback tasks
When a permission/MCP handler raises, or the answer RPC times out while the connection remains open, _callback finishes exceptionally but its entry remains in callback_tasks. Subsequent polls include that ID in known, so the still-pending server callback is never returned and the native Claude turn waits indefinitely. Remove failed/completed tasks or exclude them from known so the pending callback can be retried.
Useful? React with 👍 / 👎.
- Retry callback failures with capped backoff while preserving cancellation. - Reuse completed callback results and request IDs after answer timeouts. - Cover permission and MCP retries, independent callbacks, and controller replacement.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb53584888
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| for socket, item in sessions: | ||
| await _restore_session(machine, socket, item) |
There was a problem hiding this comment.
Continue restoring sessions after an isolated failure
When one listed service session cannot be reattached—for example, its transcript is unavailable and _spawn() returns None—_restore_session raises here and aborts this entire loop; WrapperMachine.start() catches the exception only outside restore(), so every later healthy session remains controller-less, including sessions with already accepted turns. Isolate and log per-session attachment failures after the global duplicate scan so remaining sessions are still restored.
AGENTS.md reference: AGENTS.md:L44-L50
Useful? React with 👍 / 👎.
- Continue restoring healthy sessions after an individual attachment fails. - Preserve global identity validation and cancellation propagation. - Cover accepted-turn and permission recovery without resubmitting queries.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 17d9b26e02
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| sessions = [] | ||
| identities = set() | ||
| for socket in sockets: | ||
| for item in await _list_sessions(socket): |
There was a problem hiding this comment.
Continue recovery when one service socket is unavailable
When both primary and drain sockets are configured, a stale or temporarily unavailable drain socket makes this uncaught _list_sessions() call abort restore() before the healthy primary socket is inspected; likewise, a primary failure prevents already-listed drain sessions from reaching the attachment loop. Because startup invokes this recovery only once, accepted turns on the healthy service remain controller-less until another Wrapper restart. Isolate listing failures per socket and continue with sessions from reachable services.
AGENTS.md reference: AGENTS.md:L44-L50
Useful? React with 👍 / 👎.
| if directory: | ||
| ctx.claude_steer_attachment_dirs.append(directory) | ||
| await ctx.sdk.steer(prompt, native_id=native_id, metadata=metadata) |
There was a problem hiding this comment.
Clean steering attachments after abnormal turn exits
When a file-bearing steer is accepted or has an unknown delivery result, this directory is retained in claude_steer_attachment_dirs, but claude_steer.cleanup() is called only on the normal authoritative-Result path. If the turn instead exits through a reader/control failure or drain timeout and the session is then closed or evicted, the uploaded files remain in the temporary directory indefinitely. Add cleanup to abnormal terminal/session teardown paths while preserving files across an ordinary persistent-service detach.
Useful? React with 👍 / 👎.
- Isolate unavailable service sockets while preserving identity and cancellation checks. - Clean accepted steering files after confirmed native teardown or exact terminal acknowledgement. - Preserve uploads across service detach and cover recovery and cleanup with regressions.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b6610c4e30
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| try: | ||
| await _restore_session(machine, socket, item) | ||
| except Exception as exc: | ||
| # All reachable services passed the identity check. A failed session | ||
| # must not strand other sessions' already accepted turns. | ||
| log.warning("persistent Claude session recovery failed", | ||
| service_id=item.get("id"), error_type=type(exc).__name__) |
There was a problem hiding this comment.
Retry sessions while the old controller lease is closing
During an ordinary Wrapper restart, the old client only closes its Unix socket and does not receive an acknowledgment that the service has cleared session.controller; the replacement can therefore list the session and attempt _spawn() before the old connection's finally block runs, causing open to return “already has a controller.” This catch only logs that failure, so the already-accepted turn continues without a controller until another Wrapper restart. Retry this specific lease-conflict briefly or add an atomic detach handshake before treating recovery as failed.
AGENTS.md reference: AGENTS.md:L44-L48
Useful? React with 👍 / 👎.
- Retry explicit worker attachment conflicts within a bounded window without taking over live controllers. - Negotiate strict worker identity and preserve older service and controller reconnect behavior. - Keep cancellation and unknown-response failures terminal and verify handoff over real Unix sockets.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 572e5c4d06
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| await callback(message, turn_id) | ||
| await self.ack_service_message(message) |
There was a problem hiding this comment.
Preserve failed background events before advancing the ACK
When background_message_callback throws for one service-journaled frame, this worker catches the error and continues without acknowledging that frame; however, the next successful frame calls ack_service_message() with a higher sequence number, and Session.ack() cumulatively prunes every journal row through that sequence. A transient projection or relay failure can therefore permanently discard a task notification or background result that reattachment was supposed to recover. Stop processing after the failure or track contiguous acknowledgements so later successes cannot skip the failed sequence.
AGENTS.md reference: AGENTS.md:L44-L50
Useful? React with 👍 / 👎.
- Stop background projection and cumulative acknowledgements after a service delivery failure. - Defer human terminal commits until pending background callbacks finish. - Require controller replay before new input without restarting native work. - Cover callback, ACK, terminal ordering and reattachment over local sockets.
|
LGTM, can be merged. |
Wrapper reconnects and autonomous Claude replies can lose live-turn ownership or expose internal recovery prompts. This PR adds an optional independent Claude session service so accepted turns and pending questions survive reconnects without resubmitting prompts, and native steering preserves message, attachment and usage ownership.
Protocol v71 requires Relay, Wrapper, Web and TUI to be upgraded together. The independent Claude service is opt-in; existing in-process turns must finish before first migration. The SDK stays pinned to 0.2.151 and uses the daily Claude Code CLI (2.1.263 or newer).
Validation on the PR head with Node 24.21.0 and Python 3.13.5: