Commit Graph
8262 Commits
Author SHA1 Message Date
Brennan Benson 5ea6f86dab fix(mobile): keep structured chat provider-aware 2026-08-10 04:26:07 -07:00
Brennan Benson 41616eb32e feat: enable Claude structured chat on mobile 2026-08-10 04:26:07 -07:00
Brennan Benson 0ee46a3024 fix: decode addressed Claude prompt text 2026-08-10 04:25:54 -07:00
Brennan Benson abfb4ac752 fix: preserve Claude free-text prompt answers 2026-08-10 04:25:54 -07:00
Brennan Benson c30acb771b fix: fence Claude structured compatibility 2026-08-10 04:25:54 -07:00
Brennan Benson 83d73bd0f4 fix: harden Claude structured runtime 2026-08-10 04:25:54 -07:00
Brennan Benson 23ce5e5a67 feat: wire Claude structured sessions 2026-08-10 04:25:54 -07:00
Brennan Benson 2a0d5bc32f fix: start Claude init deadline after launch 2026-08-10 04:25:39 -07:00
Brennan Benson 5af3b7aca9 fix: close Claude adapter edge cases 2026-08-10 04:01:46 -07:00
Brennan Benson ae756c5e45 fix: harden Claude structured adapter 2026-08-10 03:42:19 -07:00
Brennan Benson a500e62668 feat: add Claude structured session adapter 2026-08-10 02:28:23 -07:00
Brennan Benson 9202192f10 fix: restore structured chat parity 2026-08-10 01:37:59 -07:00
Brennan Benson 5435cf7257 Join structured attach teardown through journal bind 2026-08-09 22:42:02 -07:00
Brennan Benson 8623f12957 Fix paired mobile structured session creation 2026-08-09 22:34:13 -07:00
Brennan Benson 73c7f95457 Join structured session teardown work 2026-08-09 22:05:07 -07:00
Brennan Benson cbd79a61e9 Keep mutation context updates out of render 2026-08-09 21:39:10 -07:00
Brennan Benson dcd26ef677 Close remaining structured mobile RC blockers 2026-08-09 21:35:24 -07:00
Brennan Benson ec917184c3 Satisfy the changed-line React Doctor gate 2026-08-09 20:18:43 -07:00
Brennan Benson 19df0ced4c Align mobile wire fixtures with create intents 2026-08-09 20:08:32 -07:00
Brennan Benson 8778fdedad Harden structured mobile writes before RC 2026-08-09 19:58:58 -07:00
Brennan Benson bbc6aba927 Resolve structured mobile read review blockers 2026-08-09 19:58:23 -07:00
Brennan Benson 59ad53fecf Add mobile structured session write flow 2026-08-09 19:11:12 -07:00
Brennan Benson b4bcd20caa Add mobile structured session read flow 2026-08-09 18:22:23 -07:00
Brennan Benson 76cce58478 Drive a structured Codex session end to end over agentSession.*
One test walks create, send, stream, approval, cancel, reconnect, and paged
history against the real dispatcher, the real schemas, the real capability
gate, the real record store, journal, lease, adapter, and translation. Only
the Codex child is scripted, and it answers the same JSON-RPC calls the real
app-server does.

It is the first thing in the stack that proves the parts fit: that the
provider's echo of a user message reconciles into the bubble the client
already has instead of appending a second copy, that a client reconnecting
from its cursor replays only what it missed while a takeover advances the
fence and reaps the old child, and that the whole conversation reconstructs
from the durable journal alone with no page overlapping another.
2026-08-09 16:54:49 -07:00
Brennan Benson b4ed53f7c6 Install the structured agent-session host on first use
The record store and the journals live under the profile's user-data path,
which is not final until Electron is ready, and a runtime that never serves a
structured session should not pay for a store it will never read. So the host
is built by the first `agentSession.create` or `.ensure` rather than at
startup, and the memoized slot clears on failure so the next call retries.

The capability gate runs first: building the store and spawning a child is an
observable side effect, and a client that cannot read structured sessions must
not be able to cause it.

Owner probing is scoped to this host. Checking another host's pid against this
machine's process table is exactly how a live owner gets declared dead, so a
foreign owner reports `indeterminate` and routes to manual recovery instead.
So does a reservation that named no process — freeing it needs positive proof
that nothing spawned under its token, and this host has no spawn-token process
scan yet.
2026-08-09 16:54:49 -07:00
Brennan Benson d28861f718 Resolve a Codex launch from the durable session record
Command, cwd, pinned account home, and the thread to resume all come back
out of the record the lease already wrote, never from the client-declared
identity: a caller that could name the thread could point a session at
someone else's conversation.

It refuses rather than guesses — a record pinned to another execution host or
a WSL distro is not this host's to spawn for, a record for another provider or
another account-home variable is not this adapter's to speak for, and a
workspace that no longer resolves surfaces instead of falling back to a
default cwd.
2026-08-09 16:54:49 -07:00
Brennan Benson 61cb67c7c5 Translate Codex app-server events into journal rows
The adapter now writes what Codex streams straight into the session's
journal, so nothing between the child and the durable record has to remember
state: streamed text folds through the coalescing window, lifecycle events
flush it first, and every write is a highest-revision-wins upsert on the
durable key, which is what lets a completed body land on the row its own
deltas opened.

Journal first, observers second — a tap must never see a row the journal has
not been told about. A session with no sink still runs, unjournaled, which is
how the existing adapter tests keep working unchanged.

Starting or resuming the thread moved into its own module on the way past the
adapter's line cap; the check it carries is unchanged, and still refuses a
resume that lands on a different thread, since recording that would make the
durable handle chain lie about what the session proved.
2026-08-09 16:54:49 -07:00
Brennan Benson 115ef40f72 Key a Codex journal row by thread, turn, and ordinal
Codex item ids are not stable across resume, so a journal keyed by them
cannot reconcile a recovered thread against a live one: the same message
would arrive under a new id and append a second copy of itself.

The durable key is instead the position the item holds — thread, turn, and
ordinal within that turn, counting message items only. Non-message items get
no ordinal at all rather than a number from a second counter, because a
counter a resumed history cannot reproduce is worse than no key: it looks
reconcilable and reconciles wrongly. Those rows fall back to a live-session
namespace that a recovered journal simply will not contain, which is correct,
since Codex does not persist them either.

Approvals and questions become journal rows the same way, naming the command
the item already announced so the user is not asked to approve an opaque id,
and one row per question rather than one row per request.
2026-08-09 16:49:06 -07:00
Brennan Benson 5b34f3116f Coalesce streamed agent text on a short server-side window
Providers emit one notification per token. Journaling each one writes a row
and wakes every subscriber per token, so one long answer costs thousands of
appends and thousands of stream frames on a phone.

Deltas accumulate and flush on a 60ms window, and the row carries a SNAPSHOT
of the text so far rather than the increment — which is also what makes
dropping intermediate frames safe for a reconnecting client. All streams share
one timer, so the deadline bounds latency the same way at one wakeup per
window instead of one per stream. Lifecycle bypasses the window by flushing
first, so nothing can be journaled ahead of the text that preceded it.
2026-08-09 16:49:06 -07:00
Brennan Benson 4a9004f0b8 Buffer the provider events an adapter streams before its journal exists
A provider starts streaming the moment its process exists, and that moment
is inside `adapter.acquire` — before the journal is open and before the host
has registered the session. The adapter now receives a deferred sink whose
writes queue in arrival order and drain once the journal is bound.

One sink lives for the session rather than for one acquisition, so an adapter
can hold it across a re-acquire without noticing the new journal object. It is
bound last in `onAttached` so nothing the provider streamed can interleave with
the attach result the client is about to read, and a refused attach that never
reached a journal closes it instead of leaving writes nothing will drain.

The host was one line under the 300-line cap before this, so the mutation
admission pipeline moved to its own module. That is a straight extraction:
recompute fingerprint, admit through the ledger, check the lease, run the
plan — the same route, now somewhere a new method cannot sit next to.
2026-08-09 16:49:06 -07:00
Brennan Benson 4d70b189de Harden the Codex app-server session against partial acquisition
Six findings from the review of the structured Codex session, all of them
about a session that is live in the map while the thing it names is not:

- A prompt is addressed by its own approval id, not by the tool item it is
  about. A shell bridge re-asks per command under one parent item, and the
  second ask used to evict the first, leaving that turn blocked on a request
  nobody could answer.
- A subagent runs its own thread over the same connection. Its `turn/started`
  no longer satisfies the root thread's waiter, and every event now carries the
  thread it actually came from.
- Publication is one step at the end of acquire: the process identity is in
  hand, the child is still alive, and the attempt still owns the session. A
  failure reaps only its own child, so a replacement already published keeps
  running.
- Codex talks before its thread is named. Those events wait in the acquisition
  window and are delivered in arrival order once the session is published,
  instead of being dropped.
- A broken stdin pipe ends the transport rather than one write: the child is
  reaped and the owner learns its lease is worthless.
- A death that reaches us through several listeners reports once, keeping the
  first cause rather than the generic exit that follows it.
2026-08-09 16:48:51 -07:00
Brennan Benson b6c98f8ca5 Run a Codex session under the agent-session lease
Implements the structured agent-session adapter for Codex: one long-lived
app-server child per session, started or resumed under the lease the host
already reserved, reporting the process identity the lease re-proves and the
provider handle link the journal binds to.

The thread the adapter reports is the one Codex actually opened, never the one
a client declared, and a resume that lands on a different thread is refused
rather than recorded as a resume. A turn is named from the start response when
Codex sends an id there and from the turn-started notification when it acks
first; when neither names it in time the submission settles unknown, which the
journal renders as delivery unconfirmed rather than failure. Only a refusal
Codex actually answered becomes a rejection.

Model, effort, and policy are per-turn overrides — there is no thread-settings
write — so an option set between turns is applied to the next turn/start, and
unlisted keys are refused instead of becoming arbitrary client-controlled
parameters.
2026-08-09 14:54:39 -07:00
Brennan Benson a32e11bd08 Reply to Codex approval and input requests
Codex blocks a turn until every approval and tool-input request it sent is
answered, but the answer arrives much later and through a durable journal item
id rather than the live JSON-RPC request id. This holds the live requests for a
session and turns a chosen option back into the reply Codex expects.

Three cases the journal boundary forces: one request can carry several
questions but takes a single reply, so answers are buffered until the request is
complete; a request this build does not model is refused rather than left
blocking or silently approved; and a prompt is forgotten before its reply is
written, so a second answer finds nothing instead of replying twice.
2026-08-09 14:51:36 -07:00
Brennan Benson 8b86ac374f Open a long-lived codex app-server connection
The existing app-server client is request-scoped: one deadline for the whole
session, and every inbound message that is not a pending response is dropped. A
structured chat session needs the opposite — a child that outlives any single
call, per-request deadlines, and both traffic directions, because Codex streams
a turn as notifications and asks for approvals by sending requests back.

Adds a connection that spawns the child, completes the initialize handshake,
and stays open until closed, keeping the same environment overlay and reap
semantics as the short-lived client. A refusal is now distinguishable from a
call that never settled, so callers can tell "Codex declined" from "the child
died" instead of reporting both as failure.
2026-08-09 14:51:22 -07:00
Brennan Benson 2d66ded357 Merge remote-tracking branch 'origin/brennanb2025/native-chat-1d-wire' into brennanb2025/native-chat-structured-base 2026-08-09 14:16:37 -07:00
Brennan Benson 3e4fa050a1 Harden structured agent-session wire recovery 2026-08-09 14:12:33 -07:00
Brennan Benson c82bcbbe6b Reuse the shared lease fixture in the envelope tests
The hand-written lease drifted the moment the record grew a field. Same reason
the fixture exists for the write-admission tests.
2026-08-09 13:36:08 -07:00
Brennan Benson 1a4cea1fd1 Merge remote-tracking branch 'origin/brennanb2025/native-chat-structured-base' into brennanb2025/native-chat-1d-wire 2026-08-09 13:35:34 -07:00
Brennan Benson 775fee768e Pair the agent-session wire across versions
Extends the cross-version harness, which covered the terminal stream only, with
the structured surface. The old side's advertised capabilities and registered
method names are read from the extracted release checkout rather than written by
hand: a build cannot advertise a string its own source never names, so "the old
side does not have it" stays a fact about a real release.
2026-08-09 13:35:30 -07:00
Brennan Benson 7924a339e0 Publish agentSession.* behind the structured capability
Ten methods on the runtime manifest, including a paged `agentSession.history`
over the journal cursor and a subscribe that streams cursor-qualified batches.
A declared client that did not advertise `agent-session.structured.v1` is told
the whole surface does not exist, so an old client never sees a session it
cannot render; an in-process caller negotiates nothing and is served.
2026-08-09 13:35:28 -07:00
Brennan Benson fc36d58bbb Add the structured agent-session host
One place where the lease, the journal, and the provider adapter meet: every
mutating call recomputes the fingerprint, admits through the durable operation
ledger, checks the lease, then acts, so no method can grow its own admission
rules. Journal recovery rehydrates an unreadable journal from provider history
into a fresh epoch and tells subscribers which reset they are seeing.

Attach adjudicates the leases it loaded from disk before granting any writer.
Without that step every lease stays unreconciled for the life of the process and
a restarted host refuses every persisted session forever.
2026-08-09 13:35:19 -07:00
Brennan Benson a0d829194e Define the structured agent-session wire contract
Adds the request/refusal shapes every `agentSession.*` method speaks, the
four-field idempotency envelope and its lease admission, the item-key inverse an
upsert needs, and the `agent-session.structured.v1` capability. The protocol
version stays at 3: the surface is additive, and bumping it would strand every
paired device on the current release rather than degrade one feature.
2026-08-09 13:35:08 -07:00
Brennan Benson 2cda9b8870 Merge remote-tracking branch 'origin/brennanb2025/native-chat-1b-lease-enforce' into brennanb2025/native-chat-structured-base 2026-08-09 13:19:49 -07:00
Brennan Benson 0f97a9414e Fence PTY writes across async admission gaps 2026-08-09 13:14:41 -07:00
Brennan Benson c69b12b3dd Enforce the agent-session lease at every PTY write choke point
Route every byte that can reach a PTY through the single-writer lease
adjudicator, so a session owned by one runtime cannot be typed into by
another. Nothing binds a PTY to a durable session record yet, so the gate
short-circuits and today's shells, legacy agent terminals, and unbound
sessions are untouched.

Gated paths: terminal.send RPC, the renderer IPC write paths
(pty:write / pty:writeAccepted), plugin sendTerminalText, the agent-prompt
paste, and ptyController.write as a backstop for runtime writes that never
passed a typed gate. Every yield inside a write - inter-chunk, the
text/suffix pause, the agent-prompt submit delay, deferred size
measurement - re-checks the fence, so a handoff mid-paste stops the
remaining chunks instead of interleaving two owners.

Refusals are typed and additive on the wire: an optional
agentSessionRefusal on RuntimeTerminalSend and on the existing
pty:writeUnavailable payload, refusal codes drawn only from the existing
agent-session error codes, and stream-input refusals mapped onto the
WriteUnavailable frame old clients already decode.
2026-08-09 13:00:56 -07:00
Brennan Benson a2b20acfcc Merge branch 'brennanb2025/native-chat-1c-journal' into brennanb2025/native-chat-structured-base 2026-08-09 12:29:13 -07:00
Brennan Benson ce06e5055b Merge branch 'brennanb2025/native-chat-1a-session-store' into brennanb2025/native-chat-structured-base 2026-08-09 12:29:13 -07:00
Jinjing 082cc31703 Exclude idle current tabs from Cmd+J recent; add live attention badges (#13299)
* Show attention badges on recent chats in Cmd+J palette

Keep current tabs visible only when they have a scannable badge
(working, permission, unread, done). Snapshot unread maps alongside
status maps to freeze recent-section membership on open instead of
churning with live updates. Unify badge logic across tab strip and
palette, and extract test fixtures for reuse between suites.

* Exclude idle current tabs from Cmd+J recent; add live attention badges

Current tabs no longer appear in Recent Chats when idle—only working, blocked, or
unread agents keep the current slot visible. `done` no longer admits current tabs;
the user watched it complete on screen, so that slot goes elsewhere.

Add live attention badges to recent rows (working/permission/unread/done), matching
the tab-bar ladder. Freeze recent-section membership at open-time to keep row order
and inclusion synchronized instead of changing live.

* minor fix
2026-08-09 12:18:14 -07:00
Jinwoo HongandJinwoo-H 394e4bf1c0 Polish Artifacts management UI (#13356)
* refactor(artifacts): polish artifact management UI

* fix(artifacts): address UI polish review

---------

Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
2026-08-09 12:11:33 -07:00
Jinwoo Hong bd9addb449 fix(tasks): restore GitHub page and scroll position on reopen (#13096) 2026-08-09 11:08:04 -07:00