Skip to content

bridge: recover failure smoothness, nul, idle update and v1→v2 fixes - #156

Open
morajabi wants to merge 17 commits into
mainfrom
claude/inspiring-neumann-15227d
Open

morajabi wants to merge 17 commits into
mainfrom
claude/inspiring-neumann-15227d

Conversation

@morajabi

@morajabi morajabi commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Recovers bridge reliability work that was finished and reviewed on Oct 30–31 but never committed before the old MacBook died. It was ported from the backup onto current main and re-checked against main's newer bridge work (native OpenCode v2 driver, ranked slash commands, quiet markers). Several of these fixes match reports in Niño's feedback thread: progress stuck on "Working", silent failures, the bridge swapping runtimes mid-session.

No protocol, wire-format or database schema change.

Bridge (cli + crates)

  • Codex resume race: a follow-up that lands while Codex is unloading an idle thread used to post a failure ("thread is closing; retry thread/resume…"). That exact refusal is now retried with backoff for up to 10 s. No input is sent until a resume succeeds, an old thread/closed can't close the reopened session, and a turn that completes during the resume isn't reset to running.
  • Pasted slash commands: only a command at the start of a message (optionally after mentions) is acted on. Commands inside pasted transcripts or code spans stay text.
  • Streamed agent edits: each edit of another agent's streamed progress no longer counts as a new message. Edits of a message already acted on only refresh context. A streamed reply that is later edited into a handoff still acts. Only agent messages addressed to this agent count toward the loop cap.
  • Stuck "Working": oversized progress shows recent activity within Inline's limits. After a refused edit, the next progress goes out as a new message at the normal edit pace, and a refused final edit posts a fresh completion summary.
  • NUL in tool output: all dynamic doc text is sanitized before block ranges are measured (control characters and terminal escapes go; newlines, tabs, emoji and ZWNJ stay).
  • Idle updates: a staged update applies once active work and pending replies finish, with no two-minute quiet period. Automatic activations stay six hours apart, as main's fd4d80da9 keeps them, and an explicit inline update still switches at the next idle moment. A deliberate stop or restart during a candidate's probation keeps the candidate retryable. Status shows the runtime version, the supervisor version and any pending update or rollback.
  • v1→v2: unfinished v1 session pickers expire on the move and say the bridge was updated.

Server

  • Text is shown with NUL replaced by U+FFFD only where it leaves the server (encodeMessage, encodeFullMessage, legacy encoder). Stored text stays exact, so the stored block-content copy still matches and fetched messages keep their blocks. This replaces the earlier decrypt-time approach, which dropped blocks on fetch.

Docs

  • Local bridge docs gain an updates/restarts section. Bridge update docs in the CLI and public docs describe the six-hour spacing and explicit inline update.

Not included

Testing

  • cargo test -j 2 -p inline-cli --bin inline bridge::: 560 passed, 9 ignored
  • cargo test -j 2 -p inline-agent-driver-codex --lib harness::: 26 passed
  • cargo test -j 2 -p inline-agent-bridge --lib: 374 passed
  • cargo clippy -p inline-cli -p inline-agent-driver-codex -p inline-agent-bridge --all-targets -- -D warnings: clean; every commit builds on its own
  • Server: encodeMessage.test.ts 22 passed (the new NUL test fails without the encoder change); tsc --build passes apart from the existing missing packages/mcp/dist imports; oxlint clean
  • Not done: live bridge run, transcript corpus replay, device check. The refused-edit resend path has no direct test because the fixtures can't simulate a refused edit.

🤖 Generated with Claude Code

codex unloads an idle thread about a minute after its last turn. a follow-up
that lands during that unload got "thread is closing; retry thread/resume",
which inline posted as a failure.

- open retries only that exact refusal for the same thread, with 100 ms to 1 s
  backoff inside 10 s. start, fork, model errors and transport failures are
  not retried, and no input is sent until a resume succeeds.
- each attempt gets a fresh early listener. the resume response's wire
  sequence tells an old thread/closed from a close of the reopened session.
- a turn completion newer than the resume snapshot is not undone by it, and a
  snapshot with no active turn clears stale working state.
pasted progress carried /model entities deep inside tool output, and the rest
of the transcript became the model argument. a command now needs a leading
position, optionally after structured mentions and their separators. commands
inside code or pre spans stay literal.
each edit of another agent's streamed progress entered intake as a new
message, so about 260 edits tripped the 30-message loop cap.

- an edit of an agent message this agent already acted on only refreshes its
  passive context. it never reruns commands, mentions or loop accounting.
  acted-on messages are tracked in a bounded cache plus the durable input
  receipts, so the first delivery of an already-edited handoff still runs,
  and an observed reply that is later edited into a handoff still acts.
- passive batches replace a message in place instead of keeping its first
  revision.
- only agent messages addressed to this agent count toward its loop cap.
progress could freeze on "working" when inline refused an edit.

- render keeps splitting finished rows until the rest fits. an unfinished
  child or one huge command shows recent activity within the limit, with a
  note that earlier details stay in the agent session.
- a refused or gone progress message frees its slot. later activity posts a
  new message on the edit cadence, so a refusal that also hits sends does not
  turn every event into a request. a refused final edit posts a fresh
  completion summary.
- refused edit logs include the message id.
binary tool output put nul characters into progress text. apple clients read
text as c strings, so the message was cut there and later block ranges broke.
all dynamic doc text now goes through the visible-text sanitizer before its
utf-16 range is measured. finish() removes only its own trailing separator, so
payload newlines inside field ranges survive.
- a staged update no longer waits for a two-minute quiet period or the six
  hour activation throttle. the admission lock and work leases already close
  the handoff, and the quiet window counted unrelated bot events as work.
- a deliberate stop or restart during a candidate's probation keeps that
  candidate retryable instead of rolling it back.
- status and doctor show the runtime and supervisor cli versions and any
  pending update or rollback.
unfinished v1 session pickers now expire on the move, and their cards say the
bridge was updated instead of offering a retry the new runtime cannot handle.
turns interrupted by the move use the update wording. adds a test that a
registered legacy session keeps its folder without overriding a new choice.
apple clients decode text through c strings, so a nul cut older bridge
progress short and broke its block ranges. replace nul with u+fffd, also one
utf-16 unit, only where text leaves the server: encodeMessage,
encodeFullMessage and the legacy message encoder. stored text stays exact, so
the block-content mirror check still matches and fetched messages keep their
blocks.
managed bridge updates now apply once active work and pending replies finish,
with no quiet period or six-hour cooldown.
…ann-15227d

# Conflicts:
#	cli/src/bridge/update_supervisor.rs
automatic bridge activations stay six hours apart; inline update by hand
adopts the staged runtime at the next idle moment. the quiet period is
still gone.
automatic activations stay six hours apart, and running inline update by
hand switches the bridge at its next idle moment.
…ann-15227d

# Conflicts:
#	cli/src/bridge/v2/doc.rs
…ude/inspiring-neumann-15227d

# Conflicts:
#	cli/src/bridge/v2/intake.rs
…ude/inspiring-neumann-15227d

# Conflicts:
#	cli/src/bridge/v2/runtime.rs
…ude/inspiring-neumann-15227d

# Conflicts:
#	cli/src/bridge/v2/runtime.rs

This branch has not been deployed

No deployments
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