Skip to content

rebase: merge main (#66, #61, #69) into fix/tui-noise-and-keychain-prompts - #70

Merged
mulhamna merged 10 commits into
suiflex:fix/tui-noise-and-keychain-promptsfrom
resincode:patch/rebase-fix-tui-noise-and-keychain-prompts
Sep 20, 2026
Merged

mulhamna merged 10 commits into
suiflex:fix/tui-noise-and-keychain-promptsfrom
resincode:patch/rebase-fix-tui-noise-and-keychain-prompts

Conversation

@resincode

Copy link
Copy Markdown
Contributor

Why

fix/tui-noise-and-keychain-prompts predates three merges to main that all land inside functions this branch relocated out of lib.rs into run.rs / turn.rs / picker/prompt.rs (run_turn, native_turn, native_status, resolve_route, dispatch, agent_runtime call sites, TaskNode literals):

A plain git merge origin/main here produces real conflicts in lib.rs, code.rs, and extensions.rs — not just noise, since the functions those PRs touched no longer exist at their old location in lib.rs.

What this branch does

Merges origin/main in and manually re-ports what the merge alone can't carry across the refactor:

  • dispatch_with_refresh / is_stale_oauth_tokenrun.rs, wired into TaskRun::execute in place of the bare dispatch call. Since TaskRun here no longer holds &Invocation, the refresh re-resolves by resolved.endpoint.id directly instead of the CLI's --provider flag — same endpoint either way.
  • native_status_with_refresh, Turn.provider_error, spawn_stream/streamed (StringProviderError)turn.rs, and &mut provider::Resolved threaded through run_turn / native_turn / resolve_route / take_turn (was &Resolved).
  • TaskNode.runtime + agent_runtime's lineage parameter — auto-merged almost everywhere; two call sites in the new files (run.rs's enqueue/execute, turn.rs's record_turn/run_turn) needed the same wiring added by hand since they're new code this branch introduced after feat/phase0-runtime-truth branched.
  • #69's kernel-only fix merges clean, no action needed.

Verified

  • cargo build --workspace --all-features
  • cargo clippy --workspace --all-targets --all-features -- -D warnings — clean
  • cargo fmt --all -- --check — clean
  • cargo test --workspace --all-features — 680 tests, 0 failures, including the token-refresh regression test, the tool-block-order regression test, and the flaky follow-up-typed-mid-turn test feat/phase0-runtime-truth fixed
  • Binary builds and runs (arsy --help)

Feel free to squash/take only the parts you want — this is meant as a rebase reference, not a prescriptive diff.

badrus123 and others added 10 commits September 19, 2026 01:49
…that survives restart

Roadmap Phase 0 — runtime truth and evidence durability.

TaskGraph now records a versioned TaskAttempt per try of a task: parent task
and attempt, retry lineage, role, assignee, model decision, workspace
requirement, base revision, the authority the task held when the attempt
started, reservation, usage, lease epoch, terminal reason, result, and
evidence references. It is written to the session event stream, so a restarted
process rebuilds lineage rather than re-deriving execution from whatever
configuration is loaded later.

Graph commands are now closures over the projection, rebuilt and rechecked
after every optimistic-store catch-up, and all state is applied through replay.
Child budget is reserved atomically with the child's creation and settled at
its terminal event, returning unspent capacity to the parent, so concurrent
reservations cannot exceed the parent's remainder. A result from an attempt
that is no longer current, or whose lease epoch moved, is refused instead of
overwriting the retry that replaced it. Execution `completed` and evidence
`verified` are separate states, replay is paged, and `narrow_authority`
re-derives a recovered task's grants against current policy, narrowing only.

Validation records move to the session stream through a new
`arsy_kernel::validation`: each carries the evidence artifact, a digest of the
command computed here rather than supplied, the operation, the task and
attempt, the grants the call held, and the workspace revision it ran against.
`validate.status` answers `unvalidated`, `passed`, `failed`, or `stale` for the
revision it is asked about, and says whether the log is durable. Workspace
revision is a digest of HEAD and the porcelain status, so an edit made after a
check passed makes that evidence stale.

`plan.commit` is the explicit transition from the scratch plan to durable
commitments: each uncommitted step becomes one TODO, once, with the TODO id
recorded against the step. A turn with no session stream is offered no commit
at all.

Two pre-existing tests depended on their surroundings rather than on the code
under test, and are made hermetic: the card test pins the half-block mark
instead of inheriting whichever mark the running terminal produces, and the
follow-up test queues its keystrokes before the turn instead of racing a sleep
against the provider.
TaskRun resolves its provider once at open() and keeps it for the whole
task (documented tradeoff in open_route's own comment), so a token that
expires partway through a scripted run was never re-checked — dispatch
just failed with the raw provider auth error.

dispatch_with_refresh wraps dispatch(): on ProviderError::Auth from an
OAuth-sourced credential, it re-resolves the same endpoint (exercising
provider::stored's existing refresh-and-write-back path) and retries
the identical request once, rebuilding the delegation supervisor fresh
— safe, because an auth failure happens on the very first model call,
before any delegation could have happened yet to lose. An API key
error, or a refresh that itself fails, surfaces exactly as before.

should_refresh_and_retry is the whole decision extracted to a pure,
unit-tested predicate: retry only an Auth failure, only when the
credential source is OAuth.

Scope note: this covers arsy run (TaskRun), which re-resolves once per
invocation, so the exercised window is a token expiring mid-invocation.
It does not yet cover the interactive TUI session, which resolves its
provider once per session (native_status/run_turn) — that is a larger
change (Turn/Streamed need to carry the typed error, and the
session-level Resolved needs to be mutable in place) tracked as a
follow-up.
… TUI

Bagian 2 (of 2): the interactive session resolves its provider once per
route and caches it (resolve_route's HashMap), unlike arsy run which
resolves fresh per invocation. A token that expires between turns of a
long session was never re-checked, and every turn after that failed
with the raw "OAuth access token has expired" provider error.

The channel spawn_stream/streamed hand events across to the render
thread on now carries the typed ProviderError instead of a stringified
message, so Turn gains a `provider_error` field alongside the existing
display-only `failure: Option<String>` (which two other producers —
the round-limit and external Codex-CLI paths — still populate with a
plain message that is not a ProviderError at all).

native_status_with_refresh wraps native_status the same way
dispatch_with_refresh wraps dispatch: on a stale OAuth token it
re-resolves the same endpoint and repeats the identical round once.
Because resolve_route hands out `&mut Resolved` into its cache now
(get_mut instead of get), the refreshed token is written back into the
session's own cache as a side effect of the retry, so turns after this
one benefit too, not just this one.

should_refresh_and_retry is renamed is_stale_oauth_token and now takes
the error directly, shared by both dispatch_with_refresh (arsy run) and
native_status_with_refresh (interactive session).

Threading &mut through required run_turn, take_turn, native_turn, and
resolve_route's Option<&provider::Resolved> to become
Option<&mut provider::Resolved>; charge_turn and the dispatch match in
run_turn use as_deref()/as_deref_mut() to reborrow without moving the
option out from under the later charge_turn call.

Scripted (the test fake ModelProvider for native_turn) gained a
fail_first queue so a test can make one stream() call fail before
falling through to its scripted rounds; resolved_failing_first builds
one pre-set to OAuth-sourced. The new regression test proves the
failure-and-refresh-attempt path deterministically (the stub endpoint
cannot really be re-resolved, so refresh fails and the original
failure surfaces, with no second stream call) — the same shape of
guarantee Bagian 1's test gives for arsy run.
… tool call

Anthropic's content-block index is shared across every block type in a
message, but EventDecoder.blocks only grew when a tool_use block
started, so its length diverged from index the moment any other block
type (text, thinking) came first. block_start's own order check
(blocks.len() != index) then rejected the very next tool_use block as
out of order, aborting the whole stream mid-turn with
ARSY-PRV-1000 provider response undecodable: content block N started out of order

blocks is now a HashMap<usize, ToolBlock> keyed directly by the wire
index instead of a densely-packed Vec, so a tool_use block's position
in the block list no longer has to match its position among tool_use
blocks specifically.
When a picker offers rows (model, session, effort), Up/Down keys should
always navigate the offered menu, not fall through to history navigation.

Previously, when a picker applied a filter that narrowed matches to zero,
the navigate() function would see !self.menu().is_empty() as false and
delegate Up/Down to history recall instead. This was especially visible
with model selection where typing filtered the list.

Now, when self.offered is Some, Up/Down always navigate the offered menu,
even when filtering narrows matches. This maintains the intended picker
behavior and prevents accidental history navigation.
…mid-session

fix: refresh a stale OAuth token mid-session instead of failing the turn
fix(cli): prevent model picker from falling back to history navigation
feat(kernel): Phase 0 — runtime truth and evidence durability
…-index-desync

fix: text/thinking block before a tool call is misread as an out-of-order block
main gained three features after this branch was cut, all of them
inside functions this refactor relocated out of lib.rs:

- OAuth token mid-session refresh (PR suiflex#66): dispatch_with_refresh,
  native_status_with_refresh, is_stale_oauth_token, and the &mut
  Resolved threading through run_turn/native_turn/resolve_route/
  take_turn now live in run.rs, turn.rs, and picker/prompt.rs.
  TaskRun no longer holds &Invocation here, so the refresh re-resolves
  by resolved.endpoint.id directly rather than the CLI's --provider
  flag — the same endpoint either way, one field access instead of a
  stored reference.
- Durable task attempts and evidence (PR suiflex#61, feat/phase0-runtime-truth):
  TaskNode.runtime and the agent_runtime lineage parameter, both
  auto-merged into orchestration.rs and most call sites; two call
  sites this refactor's new files reintroduced (run.rs enqueue/execute,
  turn.rs record_turn/run_turn) needed the same wiring by hand.
- content_block_start index desync fix (PR suiflex#69): auto-merged clean,
  kernel-only, no overlap with this refactor.

Verified: cargo build/clippy(-D warnings)/fmt/test --workspace
--all-features all clean, 680 tests passing, including the token-
refresh, tool-block-order, and flaky-follow-up regression tests.
@resincode
resincode requested a review from a team as a code owner September 20, 2026 15:52
@suiflex-bot suiflex-bot Bot added commit: feat commit: fix maintainer needs: conventional commit area: cli Changes under crates/arsy-cli · suiflex-bot area: kernel Changes under crates/arsy-kernel · suiflex-bot area: code Changes under crates/arsy-code · suiflex-bot area: docs Changes to Markdown documentation or assets · suiflex-bot area: tests Changes to tests, benchmarks, or fixtures · suiflex-bot labels Sep 20, 2026
@mulhamna mulhamna self-assigned this Sep 20, 2026
@mulhamna mulhamna added this to the Arsy-code v0.3.0 milestone Sep 20, 2026
@mulhamna
mulhamna merged commit 21ad078 into suiflex:fix/tui-noise-and-keychain-prompts Sep 20, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cli Changes under crates/arsy-cli · suiflex-bot area: code Changes under crates/arsy-code · suiflex-bot area: docs Changes to Markdown documentation or assets · suiflex-bot area: kernel Changes under crates/arsy-kernel · suiflex-bot area: tests Changes to tests, benchmarks, or fixtures · suiflex-bot commit: feat commit: fix maintainer needs: conventional commit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants