{{ message }}
Repository navigation
Tags: Thurbeen/thurbox
Tags
fix(deps): update rust crate tokio to v1.54.1 (#1612) This PR contains the following updates: | Package | Type | Update | Change | OpenSSF | |---|---|---|---|---| | [tokio](https://tokio.rs) ([source](https://redirect.github.com/tokio-rs/tokio)) | dependencies | minor | `1.53.3` → `1.54.1` | [](https://securityscorecards.dev/viewer/?uri=github.com/tokio-rs/tokio) | --- ### Release Notes <details> <summary>tokio-rs/tokio (tokio)</summary> ### [`v1.54.1`](https://redirect.github.com/tokio-rs/tokio/releases/tag/tokio-1.54.1): Tokio v1.54.1 [Compare Source](https://redirect.github.com/tokio-rs/tokio/compare/tokio-1.54.0...tokio-1.54.1) ### 1.54.1 (October 21st, 2026) ##### Fixed - time: revert "remove the runtime handle from timers" ([#​8692]) [#​8692]: https://redirect.github.com/tokio-rs/tokio/pull/8692 ### [`v1.54.0`](https://redirect.github.com/tokio-rs/tokio/releases/tag/tokio-1.54.0): Tokio v1.54.0 [Compare Source](https://redirect.github.com/tokio-rs/tokio/compare/tokio-1.53.3...tokio-1.54.0) ### 1.54.0 (October 19th, 2026) The 1.54.0 release includes all changes up to 1.53.3. The MSRV is increased to 1.85. ([#​8514]) ##### Added - fs: add `File::set_times`, `File::set_modified`, and `fs::FileTimes` ([#​8504]) - net: implement `Default` for `named_pipe::{ClientOptions, ServerOptions}` ([#​8378]) - runtime: add `Builder::max_io_events_per_busy_tick` ([#​8410]) - runtime: stabilize `tokio::runtime::worker_index()` ([#​8431]) - sync: add `SemaphorePermit::semaphore()` ([#​8482]) - sync: add `blocking_acquire` methods to `Semaphore` ([#​8269]) - sync: add `watch::Receiver::is_closed` ([#​8481]) - wasm: support JSPI for Emscripten target ([#​8285], [#​8627]) - wasm: support the `wasm32-unknown-emscripten` target ([#​8281], [#​8422], [#​8528], [#​8628]) ##### Changed - fs: reject zero in `File::set_max_buf_size` ([#​8449]) - io: make `copy_buf` cooperative ([#​8459]) - io: panic when `BufReader` or `BufStream` is created with zero capacity ([#​8494]) - io: panic when in-memory pipe is created with zero capacity ([#​8397]) - io: reject zero buffer size in `copy_bidirectional_with_sizes` ([#​8454]) - io: support vectored writes in `Sink` ([#​8663]) - net: start `UnixStream::pair` and `UnixDatagram::pair` sockets as writable ([#​8444]) - net: start accepted sockets as readable and writable ([#​8435]) - net: support `UCred` on Fuchsia ([#​8257]) - process: use `mio::unix::pipe` for Unix child stdio ([#​8603]) - runtime: cache-pad the shared injection queue and idle state ([#​8622]) - runtime: decide auto-boxing of spawned futures at monomorphization time ([#​8414]) - runtime: make `enable_io` available whenever `rt` is enabled ([#​8387]) - runtime: move `block_in_place` setup into a non-generic function ([#​8315]) - runtime: move the multi-thread inject queue to its own mutex ([#​8382]) - runtime: panic when `max_io_events_per_tick` is zero ([#​8476]) - runtime: suggest `.await` in nested `block_on` panic message ([#​8501]) - sync: avoid atomic reader count in broadcast slots ([#​8357]) - sync: use acquire/release orderings in `Notify` ([#​8325]) - time: remove the runtime handle from timers ([#​8458]) ##### Fixed - fs: restore `File` internal state when op fails ([#​8291]) - io: complete zero-length memory stream operations immediately ([#​8323]) - io: flush the writer when the reader stalls in `copy_buf` ([#​8424]) - io: keep the shutdown bit when clearing readiness ([#​8435]) - io: preserve the full `ScheduledIo` tick in readiness events ([#​8331]) - io: read lines as bytes so a partial line survives an I/O error ([#​8400]) - io: retry on `ErrorKind::Interrupted` in more I/O operations ([#​8417], [#​8432], [#​8448], [#​8453], [#​8455], [#​8460]) - io: use `spawn_mandatory_blocking` for stdout and stderr ([#​8506]) - io: wait for pending operations before `BufReader` seeks ([#​8599]) - net: close `CreateFileW` handle on `SetNamedPipeHandleState` failure ([#​8418]) - net: fix peer credentials on NetBSD ([#​8589]) - net: saturate `TcpSocket` and `UnixSocket` listen backlog ([#​8511]) - net: treat `ENOTCONN` as success in `UnixStream::shutdown` ([#​8526]) - runtime: allow building with `--all-features` without `tokio_unstable` ([#​8401]) - runtime: avoid overflow in `shutdown_timeout()` ([#​8497]) - runtime: fix `spawn_blocking` hang when only scheduler workers exist ([#​8408]) - runtime: fix misleading signal driver panic message ([#​8419]) - sync: preserve FIFO lock order when coop budget is exhausted ([#​8371]) - sync: use `usize` for permit count in `SemaphorePermit` ([#​8405]) - time: avoid interval deadline overflow ([#​8385]) - time: handle wrapped top-level timer-wheel slots ([#​8334]) ##### Unstable - fs: fall back when `io_uring` has no IO driver ([#​8165]) - metrics: cap log histogram precision ([#​8496]) - runtime: add an opt-in sharded `spawn_blocking` queue ([#​8337]) - runtime: expose schedule latency in task hooks ([#​8282]) - taskdump: fix idle bookkeeping when a task dump wakes parked workers ([#​8372]) - taskdump: skip non-idle tasks during taskdumps ([#​8568]) ##### Documented - docs: fix stray braces and broken links in rustdoc ([#​8605]) - fs: document cancellation behavior of `tokio::fs` ([#​8265]) - net: add Safety docs for named pipe `from_raw_handle` ([#​8378]) - net: document that `TcpStream::connect` returns only the last error ([#​8421]) - runtime: add error documentation for `runtime::Builder` ([#​8297]) - runtime: link `available_parallelism` in worker count docs ([#​8625]) - sync: clarify panic behavior of `broadcast::channel()` ([#​8420]) - sync: fix typo in `Semaphore` documentation ([#​8521]) [#​8165]: https://redirect.github.com/tokio-rs/tokio/pull/8165 [#​8257]: https://redirect.github.com/tokio-rs/tokio/pull/8257 [#​8265]: https://redirect.github.com/tokio-rs/tokio/pull/8265 [#​8269]: https://redirect.github.com/tokio-rs/tokio/pull/8269 [#​8281]: https://redirect.github.com/tokio-rs/tokio/pull/8281 [#​8282]: https://redirect.github.com/tokio-rs/tokio/pull/8282 [#​8285]: https://redirect.github.com/tokio-rs/tokio/pull/8285 [#​8291]: https://redirect.github.com/tokio-rs/tokio/pull/8291 [#​8297]: https://redirect.github.com/tokio-rs/tokio/pull/8297 [#​8315]: https://redirect.github.com/tokio-rs/tokio/pull/8315 [#​8323]: https://redirect.github.com/tokio-rs/tokio/pull/8323 [#​8325]: https://redirect.github.com/tokio-rs/tokio/pull/8325 [#​8331]: https://redirect.github.com/tokio-rs/tokio/pull/8331 [#​8334]: https://redirect.github.com/tokio-rs/tokio/pull/8334 [#​8337]: https://redirect.github.com/tokio-rs/tokio/pull/8337 [#​8357]: https://redirect.github.com/tokio-rs/tokio/pull/8357 [#​8371]: https://redirect.github.com/tokio-rs/tokio/pull/8371 [#​8372]: https://redirect.github.com/tokio-rs/tokio/pull/8372 [#​8378]: https://redirect.github.com/tokio-rs/tokio/pull/8378 [#​8382]: https://redirect.github.com/tokio-rs/tokio/pull/8382 [#​8385]: https://redirect.github.com/tokio-rs/tokio/pull/8385 [#​8387]: https://redirect.github.com/tokio-rs/tokio/pull/8387 [#​8397]: https://redirect.github.com/tokio-rs/tokio/pull/8397 [#​8400]: https://redirect.github.com/tokio-rs/tokio/pull/8400 [#​8401]: https://redirect.github.com/tokio-rs/tokio/pull/8401 [#​8405]: https://redirect.github.com/tokio-rs/tokio/pull/8405 [#​8408]: https://redirect.github.com/tokio-rs/tokio/pull/8408 [#​8410]: https://redirect.github.com/tokio-rs/tokio/pull/8410 [#​8414]: https://redirect.github.com/tokio-rs/tokio/pull/8414 [#​8417]: https://redirect.github.com/tokio-rs/tokio/pull/8417 [#​8418]: https://redirect.github.com/tokio-rs/tokio/pull/8418 [#​8419]: https://redirect.github.com/tokio-rs/tokio/pull/8419 [#​8420]: https://redirect.github.com/tokio-rs/tokio/pull/8420 [#​8421]: https://redirect.github.com/tokio-rs/tokio/pull/8421 [#​8422]: https://redirect.github.com/tokio-rs/tokio/pull/8422 [#​8424]: https://redirect.github.com/tokio-rs/tokio/pull/8424 [#​8431]: https://redirect.github.com/tokio-rs/tokio/pull/8431 [#​8432]: https://redirect.github.com/tokio-rs/tokio/pull/8432 [#​8435]: https://redirect.github.com/tokio-rs/tokio/pull/8435 [#​8444]: https://redirect.github.com/tokio-rs/tokio/pull/8444 [#​8448]: https://redirect.github.com/tokio-rs/tokio/pull/8448 [#​8449]: https://redirect.github.com/tokio-rs/tokio/pull/8449 [#​8453]: https://redirect.github.com/tokio-rs/tokio/pull/8453 [#​8454]: https://redirect.github.com/tokio-rs/tokio/pull/8454 [#​8455]: https://redirect.github.com/tokio-rs/tokio/pull/8455 [#​8458]: https://redirect.github.com/tokio-rs/tokio/pull/8458 [#​8459]: https://redirect.github.com/tokio-rs/tokio/pull/8459 [#​8460]: https://redirect.github.com/tokio-rs/tokio/pull/8460 [#​8476]: https://redirect.github.com/tokio-rs/tokio/pull/8476 [#​8481]: https://redirect.github.com/tokio-rs/tokio/pull/8481 [#​8482]: https://redirect.github.com/tokio-rs/tokio/pull/8482 [#​8494]: https://redirect.github.com/tokio-rs/tokio/pull/8494 [#​8496]: https://redirect.github.com/tokio-rs/tokio/pull/8496 [#​8497]: https://redirect.github.com/tokio-rs/tokio/pull/8497 [#​8501]: https://redirect.github.com/tokio-rs/tokio/pull/8501 [#​8504]: https://redirect.github.com/tokio-rs/tokio/pull/8504 [#​8506]: https://redirect.github.com/tokio-rs/tokio/pull/8506 [#​8511]: https://redirect.github.com/tokio-rs/tokio/pull/8511 [#​8514]: https://redirect.github.com/tokio-rs/tokio/pull/8514 [#​8521]: https://redirect.github.com/tokio-rs/tokio/pull/8521 [#​8526]: https://redirect.github.com/tokio-rs/tokio/pull/8526 [#​8528]: https://redirect.github.com/tokio-rs/tokio/pull/8528 [#​8568]: https://redirect.github.com/tokio-rs/tokio/pull/8568 [#​8589]: https://redirect.github.com/tokio-rs/tokio/pull/8589 [#​8599]: https://redirect.github.com/tokio-rs/tokio/pull/8599 [#​8603]: https://redirect.github.com/tokio-rs/tokio/pull/8603 [#​8605]: https://redirect.github.com/tokio-rs/tokio/pull/8605 [#​8622]: https://redirect.github.com/tokio-rs/tokio/pull/8622 [#​8625]: https://redirect.github.com/tokio-rs/tokio/pull/8625 [#​8627]: https://redirect.github.com/tokio-rs/tokio/pull/8627 [#​8628]: https://redirect.github.com/tokio-rs/tokio/pull/8628 [#​8663]: https://redirect.github.com/tokio-rs/tokio/pull/8663 </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/Thurbeen/thurbox). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xODguMiIsInVwZGF0ZWRJblZlciI6IjQ0LjE4OC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
fix(core): resume a custom-named Claude launcher's conversation on re… …start (#1606) Fixes #1603 ## Intent A Claude agent registered under a custom name, launched through a wrapper script that sets its own `CLAUDE_CONFIG_DIR`, keeps its transcripts where thurbox's resume check cannot look. Restart therefore relaunched `--session-id <id>` onto a conversation that already existed. Claude refuses that with "Session ID … is already in use", and the pane dies. Every restart and restore of such a session hit it, including `session restart`. ## What Changed - On a turn-start signal (`THURBOX_SIGNAL_TURN=start`, set by Claude's `UserPromptSubmit` hook), `session signal` reads the hook's stdin payload. When the payload's `session_id` matches the row's agent session id, it records that id as `thurbox.conversation_started` session meta. - Restart resumes when that marker matches the row's id, and otherwise falls back to the existing transcript and `resume_latest` logic. The decision now rests on the agent's own report, not on guessing a config directory. - The marker requires the agent to name the id. A forked Claude conversation runs under an id Claude chose, so it records nothing and keeps the old behaviour. - `session restart` help, `docs/AGENTS.md`, `docs/ARCHITECTURE.md` and the `thurbox-agents` skill now describe the rule. ```mermaid flowchart LR H[UserPromptSubmit hook] -->|payload session_id == row id| M[(thurbox.conversation_started)] R[restart] --> Q{marker == row id?} Q -->|yes| RES[--resume id] Q -->|no| T[transcript / resume_latest check, unchanged] ``` ## Risk Assessment - Existing sessions gain the marker only at their next turn after upgrading. Until then they keep the previous behaviour, so a custom launcher that hides its config directory still needs that one turn first. - Reading stdin only happens on a turn-start signal and never from a terminal. Input is capped at 16 MiB, and anything unparseable records nothing. - Hand-respawned panes that lost `THURBOX_SESSION` cannot report at all, and that remains out of scope. Pinning `CLAUDE_CONFIG_DIR` in the agent's `env`, the documented multi-account shape, still works as before. ## Testing - New end-to-end regression `create_e2e::a_wrapped_claude_resumes_the_conversation_its_hook_reported` spawns a custom-named wrapper agent on a real tmux server. Its transcript sits in a private config dir, and the turn-start hook fires from inside the pane with a Claude-shaped payload. The test asserts that `session restart` relaunches `--resume <id>`. On `origin/main` it failed with `--session-id <id>`, the reported defect. It also asserts that a payload naming a different (forked) id records nothing; disabling that check makes the test fail. - Review: two rounds. Round one found the fork case above (a fork would have been resumed under an id Claude never had), which was fixed test-first. Round two found nothing. - Gate from `.publish.yaml`: `just lint` and `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features` passed. In `cargo nextest run --all`, 4457 of 4471 tests passed. Eight failures came from running two gates concurrently on WSL and pass in isolation. Six fail identically on `origin/main` (086218f) on this machine, so they predate this change: `kernel::diff::tests::requesting_marks_it_pending_immediately`, `create_e2e::failed_launch_rolls_back_its_worktree_and_allows_the_same_branch_retry`, two `event_consumers_e2e` pre-launch tests and two `tui_e2e` pty tests. CI is the authority on those. - Not verified: a real `claude` restart through a real wrapper; the stand-in reproduces the arguments and the hook. — LeTuR's agent <!-- fleet:worker u2137-3bb27005bfe0b988 -->
feat(cli): report whether a session is ready for input (#1607) Fixes #1604 ## Intent Callers that hand a session its first prompt must know whether the agent can take input yet, and thurbox owns the agent runtimes. Until now a caller pieced that together from `state`, and for a fresh agent that fires no hook before its first turn (Codex) it had to carry its own screen probe per agent. Fleet's `initial_codex_ready` is the example. This PR adds one agent-agnostic `ready` field so callers no longer need per-agent knowledge. ## What Changed - `session get` and `session list` now carry `ready` (`Assessment::ready`): - `true`: the hook's last report is `idle` or `done`; or no hook has reported, an agent holds the pane, and its input line shows a drawn, empty composer (one prompt glyph left of the cursor). - `false`: `working`, `blocked` or `background`; parked; a pane with no agent; a contradicted report; or a fresh agent whose composer is not drawn yet. - `null`: nothing can say, for example the pane was not probed or its input line was unreadable. Callers should ask again. - The input line is read only for a `running` session with no hook report, and only by a verb that probes the pane. A reported session costs no extra read. - `composer::shows_prompt` is the stricter twin of `classify`'s `Empty`: a blank line, which is what a booting pane shows, is not a prompt. - The contract is documented in `docs/ORCHESTRATION.md` → Readiness, the `session get` help and the `thurbox-session-status` skill. ## Risk Assessment - The field is additive; existing fields keep their values. A peer reading the mirror shape ignores it. - The pre-hook answer is a screen reading. Holding text, a dialog with a hidden cursor (Codex's update and folder-trust prompts) and a blank booting screen all read `false`. An agent whose composer prompt is not one of the known glyphs (`❯ › > → » $ % #`) would stay `false` until its first hook. - `session list` without `--verify` answers `null` for a never-reported session, because it does not probe panes. ## Testing - New end-to-end regression `cli_session_hook_state::a_session_is_ready_once_its_composer_is_drawn_and_then_as_its_hooks_say`. A stand-in Codex runs on a real tmux server: it boots (`ready: false`), then draws `› ` with a placeholder right of the cursor (`ready: true`, `hook_reported: false`); then `working` gives `false` and `idle` gives `true`. On `origin/main` it failed with `ready: null` (no such answer). A unit test pins `shows_prompt`. - Checked live against the installed CLIs on a private tmux socket, with no input sent: Claude's empty composer reads `❯` left of the cursor, which counts as ready. Codex's update and folder-trust dialogs (hidden cursor, nothing left of it) read not ready. Codex's composer itself was not reached, because every folder tried needed a trust decision that would have saved config. Its shape (cursor at column 2 after `› `) comes from Fleet's existing probe and is not verified here. - Review: one round, no findings. - Gate from `.publish.yaml`: `just lint` and `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features` passed. In `cargo nextest run --all`, 4465 of 4472 tests passed. One failure was a load flake and passes in isolation. Six fail identically on `origin/main` (086218f) on this machine, so they predate this change: `kernel::diff::tests::requesting_marks_it_pending_immediately`, `create_e2e::failed_launch_rolls_back_its_worktree_and_allows_the_same_branch_retry`, two `event_consumers_e2e` pre-launch tests and two `tui_e2e` pty tests. — LeTuR's agent <!-- fleet:worker u2137-3bb27005bfe0b988 -->
fix(git): delete the branch of a worktree thurbox created on teardown (… …#1600) ## Problem When thurbox tears down a worktree it created (`created_by_thurbox`), it runs `git worktree remove --force` but never deletes the branch it cut with `--worktree-branch`. Every deleted session left its local branch behind. ## Change Once a thurbox-created worktree has been removed, teardown also deletes its **local** branch, on the same host (local, ssh or WSL): - `git::delete_created_branch_on` runs `git worktree prune` first (git refuses `branch -D` on a branch that a registered but vanished worktree still has checked out), then `git branch -D`. - `-D`, not `-d`: a squash-merged branch is never an ancestor of its base. Unpushed commits go with it, in the same way `worktree remove --force` already discards uncommitted work. - It is idempotent. If the branch is already gone, that's a no-op and not an error. A worktree whose directory vanished but is still registered gets pruned, then its branch is deleted. - It is best-effort. Results land in the delete report as `deleted_branches` / `branch_errors` (CLI JSON and human output). A branch error does **not** count as owed teardown; the checkout is already gone. - Remote branches are never touched. Where it happens: - `delete.rs` `remove_worktree_into`: runs after a successful removal, or when the worktree is already unregistered or prunable. This covers immediate force delete, the overdue reap, the owed-teardown retry and the purge of a soft-deleted session. - `reconcile.rs` creation rollback: deletes the branch a failed **spawn** cut. It never does this for a **restore** rollback, because there the branch is the soft-deleted session's own work. Not touched: - Worktrees the session merely opened (`kept_worktrees`). - Anything during a **soft delete**: the worktree and branch stay for undo, and go when the session is purged. - A branch another session still names: a live one, or an unpurged soft-deleted one that isn't force-deleted. A fork inherits its parent's checkout, so without this guard a parent's hard delete would destroy the branch a soft-deleted fork's undo needs. `create_e2e::a_saved_fork_recovers_its_checkout_after_parent_teardown` caught this. ## Decision: who owns a branch `create_worktree_on` attached to `create_worktree_on` (and so `create_or_attach_worktree`) attaches to a branch that already exists instead of failing, and the session still records `created_by_thurbox: true`. That flag describes the **directory**, not the branch. **Decision: thurbox owns only branches it cut with `-b`.** A `--worktree-branch` that names a branch the user already had must never delete that branch. The database can't tell the two cases apart, so creation records ownership in the repository: after `worktree add -b`, it sets `branch.<name>.thurboxCreated = true` in the repo's git config. Teardown deletes only marked branches. This needs no schema change, works the same on remote hosts (it is just another `git` call through the host launcher), and `git branch -D` removes the branch's config section, so the mark goes with the branch it vouches for. A branch a rolled-back attempt left behind keeps its mark, so a retry that attaches to it still owns it. Consequences worth knowing: - Branches thurbox created **before this change** have no mark, so they are kept, as today. Only sessions created from now on get cleanup. That's fine for Fleet, which will pass `--worktree-branch` for new tasks. - **Best-effort restore of a force-deleted session** used to reattach the surviving branch. For a thurbox-cut branch there's now nothing to reattach: `recreate_worktrees` skips it and `worktrees_recovered` reports 0. This follows from the force-delete decision (unpushed commits go with it). Soft delete is the undoable path. Docs and the thurbox-cli skill now say so. ## Docs `docs/FEATURES.md`, `docs/ARCHITECTURE.md` (ADR-26 rollback sentence), `.agents/skills/thurbox-cli/SKILL.md`, and the rationale comment on the delete confirmation in `ui/plugins/10_sessions.lua`. That comment claimed force-delete "leaves the branch"; the confirmation policy itself is unchanged, because unpushed commits already trigger the question. ## Tests - `src/git/tests.rs`: created branch deleted after its worktree goes (and a second call is a no-op); a branch with unmerged commits is still deleted (`-D`); an attached pre-existing branch is unmarked and kept; a vanished-but-registered worktree is pruned and its branch deleted; a branch still checked out errors instead of being deleted. - `src/session_ops/delete.rs`: hard teardown deletes the branch; the gone-directory case; branches not created by thurbox are kept (attached branch, and an opened worktree); soft delete keeps both worktree and branch. - `tests/create_e2e.rs`: a failed spawn's rollback leaves no branch, and force-deleting the retried session reports and deletes it. ## Checks - `cargo fmt --all -- --check`: clean. - `cargo clippy --all-targets --all-features -- -D warnings`: clean on this change. The local toolchain (1.96) also flags 3 pre-existing `nonminimal_bool` asserts in `src/kernel/registry.rs`, which this PR doesn't touch. - `RUSTDOCFLAGS="-D warnings" cargo doc`, `cargo test --test architecture_rules`: pass. - `just test`: 4371 passed, 79 failed. All the failures are tmux/pty e2e harnesses (`tui_e2e`, `backend_contract`, `one_window_per_stamp`, …) and fail the same way on unmodified `main` on this machine. 76 of 77 re-ran identically on a baseline checkout; the remaining one, `a_search_result_is_revealed_as_soon_as_it_is_the_selected_one`, is flaky on both. Every test touching this change passes. - `rumdl check .`: 5 pre-existing findings in files this PR doesn't touch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
fix(core): pin interactive Bash in Nix tests (#1599) ## Intent Restore the pinned Nix development shell's real-terminal test contract. The shell supplied stdenv's non-interactive Bash, so the fixtures had no Readline `bind` builtin or bracketed-paste negotiation even though they launch Bash interactively. This blocked canonical verification associated with #1467; it is not a production input-path change. ## What Changed - Add `bashInteractive` to the Nix development shell. - Add a real-tmux regression test that proves the Bash fixture has Readline bracketed paste enabled. - Isolate real-shell fixtures from an inherited `INPUTRC` so personal Readline settings cannot change the security precondition. - Document the shell requirement in the development guide and testing skill. The failing-first test reproduced `bash: bind: command not found` under the pinned shell. With only interactive Bash substituted while retaining pinned Rust 1.98.1, tmux 3.7c, Bash 5.3.15, and `TERM=xterm-256color`, it passed. The five terminal-input failures that motivated this prerequisite then passed together, including the malicious pasted end-marker assertion. ## Risk Assessment Low. The change affects development-shell composition and its tests only; production Rust behavior is unchanged. The added Bash package is from the already locked nixpkgs revision, not a dependency update. This is not visually observable, so there is no UI evidence to capture. One first full run used an overly long private `TMPDIR` and independently hit Unix `SUN_LEN` for per-test UI sockets. The same focused UI test failed under that prefix and passed under the short private `/tmp/tbx-u94`; the unchanged full gate was then run there. The shared Fleet target and live tmux sessions were not touched. ## Testing - `nix develop --command just lint` - `nix develop --command env RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features` - `TMPDIR=/tmp/tbx-u94 nix develop --command cargo nextest run --all` — 4,460 passed, 4 skipped - Regression red: `the_interactive_bash_fixture_enables_bracketed_paste` failed because pinned stdenv Bash had no `bind` - Regression green: the same test passed with locked `bashInteractive` - Hostile `INPUTRC`: the regression and five original focused failures passed 6/6, including the malicious end-marker case - Publish review: two rounds; one inherited-`INPUTRC` finding reproduced and fixed - Independent review: no findings at exact head `6412eeb94eed4c53a65407d33286fce0cc8e19cc` — LeTuR's agent <!-- fleet:worker u94-5d79a2bc9dc9c56d -->
fix(core): write interface config atomically (#1598) ## Intent Prevent a failed `ui.json` save from truncating the interface decisions already on disk. ## What Changed - Reused the atomic config writer for both `settings.toml` and `ui.json`, with native replacement on Windows. - Added an interruption regression test that constrains a child process mid-write and verifies the old file remains byte-for-byte intact with no staged file left behind. - Preserved config-manager symlinks when saving and normalized Windows destinations so native replacement works beyond `MAX_PATH`. - Added Unix symlink and Windows long-path regressions. - Documented the atomicity boundary and the lack of a storage-durability guarantee. - This is not visually observable; verification exercises persisted config files and the real registry path. ## Risk Assessment No format or migration changes. Runtime Windows replacement was not exercised locally; all Windows tests compile for `x86_64-pc-windows-gnu` and run in CI. The helper deliberately does not sync the staged file or parent directory, so it protects readers from partial process writes rather than storage or operating-system failure. ## Testing - Review: two adversarial rounds; 2 findings raised and fixed (Windows long paths and symlink preservation). `thurview` 0.35.0 was current, but its documented `graph` subcommand is unavailable, so optional graph augmentation did not run. - Regression: the new test passes on this branch and fails against the old direct-write call by exposing a truncated `ui.json`. - `cargo check --tests --target x86_64-pc-windows-gnu --all-features` passed. - `just lint` passed. - `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features` passed. - `cargo nextest run --all` passed: 4,459 passed, 4 skipped. - Documentation review passed; `docs/CONFIG.md` and the kernel/testing references already describe the new contract. — LeTuR's agent
fix(deps): update rust crate vt100 to 0.17 (#1595) This PR contains the following updates: | Package | Type | Update | Change | OpenSSF | |---|---|---|---|---| | [vt100](https://redirect.github.com/doy/vt100-rust) | dependencies | minor | `0.16` → `0.18` | [](https://securityscorecards.dev/viewer/?uri=github.com/doy/vt100-rust) | --- ### Release Notes <details> <summary>doy/vt100-rust (vt100)</summary> ### [`v0.18.1`](https://redirect.github.com/doy/vt100-rust/blob/HEAD/CHANGELOG.md#0181---2026-10-19) [Compare Source](https://redirect.github.com/doy/vt100-rust/compare/v0.18.0...v0.18.1) ##### Fixed - actually release the intended changes that were supposed to be in 0.18.0 ### [`v0.18.0`](https://redirect.github.com/doy/vt100-rust/blob/HEAD/CHANGELOG.md#0180---2026-10-18) [Compare Source](https://redirect.github.com/doy/vt100-rust/compare/v0.17.0...v0.18.0) ##### Added - `Screen::contents_formatted_basic`, which is like `Screen::contents_formatted`, but restricts escape sequences to SGR escapes only, and control characters to `'\n'`. - `Screen::write_contents_formatted_basic`, like `Screen::contents_formatted_basic` but writes to any type implementing `std::fmt::Write` instead of returning a `String`. - `Callbacks::on_scroll`, called when a row scrolls off the top of the scrollback. This enables capturing terminal output in a streaming fashion. Currently this can only be used to obtain the row contents in the "basic formatted" style (the same returned by `Screen::contents_formatted_basic`). - `Parser::DEFAULT_ROWS` and `Parser::DEFAULT_COLS`, set to 24 and 80 (the same already used by `Parser::default`). These have type `NonZeroU16`. - `Parser::set_size`, which is like `Screen::set_size` but calls the `on_scroll` callback when a row is pushed off the top of scrollback (which is now possible due to the other `set_size` changes). - `Parser::screen_and_callbacks_mut`, which returns mutable references to both the screen and callbacks object at once. This was not possible with the existing methods. - `capture::basic_formatted_to_plain`, which converts a basic formatted capture (as returned by `Screen::contents_formatted_basic`) into a plain text one without allocating. - `capture::basic_formatted_rows`, which splits a basic formatted capture into individual rows without allocating. - `capture::basic_formatted_to_plain_indexed`, which is like `capture::basic_formatted_to_plain`, but also includes the original index of each plain text substring. ##### Changed - Bumped MSRV to 1.85 and edition to 2024 - `Parser::new` and `Screen::set_size` now take `NonZeroU16`s instead of `u16`s. Passing 0 for `rows` or `cols` was not supported and resulted in panics. Additionally, `Screen::size` now returns `NonZeroU16`s. - The following methods of Screen now return/yield strings rather than byte vectors: - attributes_formatted - contents_diff - contents_formatted - cursor_state_formatted - input_mode_diff - input_mode_formatted - rows_diff - rows_formatted - state_diff - state_formatted The byte vectors were already always UTF-8. - `Screen::set_size` resets the scroll region and scrollback offset when the size changes, matching the behavior of xterm. - `Screen::set_size` has different behavior when the screen height changes. Previously, it would discard rows from or add rows to the bottom as needed, but this does not match most terminal emulators. Now, when the height is decreased, `set_size` discards rows from the bottom only until the cursor is at the bottom. Then, it pushes excess rows from the top of the screen into scrollback. When the height is increased, `set_size` pulls rows from scrollback into the top of the screen. If scrollback is exhausted and the new height hasn't been reached yet, blank rows are added to the bottom. ##### Fixed - Fixed no_std support which was inadvertently broken in 0.17.0 - Changed handling of IL and DL sequences when the cursor is outside the scroll region. These operations are now no-ops in that case, which is the spec-compliant behavior. - Fixed issue where one-row scroll regions could cause unrelated rows to be marked as wrapped or not wrapped. - Improved the performance of various operations, especially escape sequences that insert/delete multiple rows or characters. - `Screen::set_size` only clears each row's `wrapped` when the screen width changes. Previously, it would unconditionally clear that flag. ### [`v0.17.0`](https://redirect.github.com/doy/vt100-rust/blob/HEAD/CHANGELOG.md#0170---2026-10-18) [Compare Source](https://redirect.github.com/doy/vt100-rust/compare/v0.16.2...v0.17.0) ##### Added - Support for DECSCUSR (set cursor style) ([#​21](https://redirect.github.com/doy/vt100-rust/issues/21), reuben olinsky) - Support for HPA (horizontal position absolute) and REP (repeat last printed character) ([#​23](https://redirect.github.com/doy/vt100-rust/issues/23), KacperLa) - no_std and embedded-io support ([#​25](https://redirect.github.com/doy/vt100-rust/issues/25), Chris Pacejo) - `Screen::scrollback_len` ([#​27](https://redirect.github.com/doy/vt100-rust/issues/27), Erick Tryzelaar) - Support for HVP (horizontal and vertical position) ([#​34](https://redirect.github.com/doy/vt100-rust/issues/34), rezigned) - Support for clearing scrollback with `Screen::clear_scrollback` and `CSI 3 J` ([#​31](https://redirect.github.com/doy/vt100-rust/issues/31), Alexey Zhokhov) - Support for VS16 emoji-presentation sequences ([#​32](https://redirect.github.com/doy/vt100-rust/issues/32), Junyi Hou) ##### Fixed - Various bugs with oddly-sized terminals ([#​29](https://redirect.github.com/doy/vt100-rust/issues/29), Evan Almloff) ([#​30](https://redirect.github.com/doy/vt100-rust/issues/30), Junyi Hou) ([#​41](https://redirect.github.com/doy/vt100-rust/issues/41), taylor.fish) - Scrollback now accumulates when a scroll region is active as long as the top of the scroll region is the top of the terminal ([#​33](https://redirect.github.com/doy/vt100-rust/issues/33), L-jasmine) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/Thurbeen/thurbox). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xODAuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjE4MC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=--> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: LeTuR <magicletur@protonmail.com>
fix(core): reconcile session lifecycle and automation ownership (#1593) ## Intent Make session creation, deletion, undo and purge recoverable across crashes and delayed host replies. Refs #1469, #1509 and #1516. ## What Changed Lifecycle operations record durable intent before resource changes and use one reconciler to finish or roll back interrupted work. Creation confirms pane identity before publishing; cleanup respects active pane, checkout and workspace claims. Rollback preserves existing workspaces, and a resumed launch cannot rebuild a workspace used by another active session. Shared-host cleanup carries a lifecycle precondition, and purge retains a minimal tombstone for peers. ```mermaid flowchart LR I[Durable intent] --> R[Guarded resource changes] R --> C[Commit result] R -->|failure or crash| S[Shared reconciliation] S --> R ``` Automations can explicitly name an owner. Soft deletion suspends them without replacing their IDs or history; undo restores their prior enabled state after every owner/target suspension clears. Explicit disable choices made while suspended survive undo. Force deletion and purge remove owned automations and runs. ## Risk Assessment Schema 59 keeps the minimum-reader version at 53. Older hosts without the negotiated cleanup capabilities retain pending cleanup until upgraded. Irreversible purge markers reject legacy revival and deletion attempts until audit retention prunes them. ## Testing Linux with real tmux: crash recovery, stale writers and host replies, pane reuse, ownerless windows, checkout aliases, purge/retention, automation undo and the existing three-process creation budget. Modeled backends cover unstamped identity and host failures. Native Windows/psmux and real remote hosts were not exercised locally. Review is blocked on restore launch context and cleanup of a checkout borrowed through another session's cwd or attached directory. Nine end-to-end cases reproduce those failures on the published implementation, including nested directories and directory lease contention. Corrections and the full #1469 / approved PR-D acceptance audit are in progress; prior review scores do not establish readiness for the changed head. The next revision must pass the full repository gate, normal signed hooks, local current-head review by the independent merge reviewer, and current-head CI and feedback. Earlier runs exposed terminal timing/input failures and the Windows fixture's platform-route mismatch; no assertions or timeouts were weakened. Manual check on Linux: run `just build`, then `scripts/dev/sandbox.sh --fresh --shell`. Use a disposable repository with a `main` branch: ```sh thurbox-cli session create --name lifecycle-review --repo-path /path/to/disposable/repo --worktree-branch review/lifecycle --command sleep --arg 600 # Set this to the UUID printed above. REVIEW_SESSION=replace-with-created-uuid thurbox-cli automation create --name lifecycle-review --trigger hourly --command true --owner-session "$REVIEW_SESSION" thurbox-cli session delete "$REVIEW_SESSION" --soft thurbox-cli automation show lifecycle-review thurbox-cli session restore "$REVIEW_SESSION" thurbox-cli automation show lifecycle-review thurbox-cli session delete "$REVIEW_SESSION" --force thurbox-cli session purge "$REVIEW_SESSION" thurbox-cli session list --deleted --include-purged ``` The automation keeps its ID and resumes its prior enabled state after restore. Force deletion removes it; purge removes the created checkout and leaves a tombstone hidden from the ordinary deleted listing. — LeTuR's agent
feat(ui): clarify the sessions panel hierarchy (#1586) ## Intent Make the Sessions panel easier to scan across hosts, repositories and parent sessions. Refs #1551. ## What Changed Hosts use accent bands, repositories use dotted separators, and session trees use indentation and fold handles. Blocked rows stay visible under folded parents and use a status highlight. Terminal titles stay beside session names in narrow panes; wide panes align them; the optional title setting displays the agent's OSC terminal title. Folding a parent now selects it when the selected descendant disappears, while preserving any selection that stays visible. Explicit focus reveals every ancestor of a deep session; repeated ancestor IDs stop malformed cycles. When a selected blocked child resumes work, its folded ancestors open so the terminal stays on that child. ## Risk Assessment The title setting uses the existing published OSC title, rather than querying literal tmux `window_name`; that part of #1551 remains outside this PR. Edited interfaces retain their existing layout. Native verification is on Linux; Windows, live SSH and WSL rendering were not exercised locally. ## Testing Failure-first PTY and kernel tests reproduced folding onto a blocked sibling, losing explicit focus beyond 64 parents, and switching away when a folded blocked child resumes work. All 70 focused checks pass: the complete session-list suite and three real PTY regressions, including next-action targets and cycle safety. A new real TUI regression first reproduced the terminal title missing from a narrow session row, then passed after restoring it beside the assigned name. Narrow/wide rendering coverage also passes. On the current Linux tree, `just lint`, warnings-denied rustdoc and `cargo nextest run --all` passed: **4,391 tests passed; four existing opt-in tests skipped**. The isolated CLI `plugin check` passed with no warnings. `npm ci` and `npm run lint:website` passed. The built CLI's `plugin check` returned `ok: true` and no warnings. Independent review and CI status are reported separately for the exact head. To try the interaction manually (the automated PTY tests exercise these behaviors): 1. Run `just build`, then `scripts/dev/sandbox.sh --profile hierarchy-review --isolate-home --shell`. 2. In that isolated shell, configure and seed the example: ```bash cat > "$THURBOX_CONFIG_DIR/agents.toml" <<'TOML' default = "shell" [[agents]] name = "shell" command = "sh" args = [] TOML lead_id=$(thurbox-cli session create --name lead --repo-path "$TBX_REPO_ROOT" --agent shell --json | jq -r .id) thurbox-cli session create --name quiet --repo-path "$TBX_REPO_ROOT" --agent shell --parent "$lead_id" waiting_id=$(thurbox-cli session create --name waiting --repo-path "$TBX_REPO_ROOT" --agent shell --parent "$lead_id" --json | jq -r .id) thurbox-cli session signal --session "$waiting_id" --state blocked thurbox-cli config accept-interface THURBOX_UI_DIR="$TBX_REPO_ROOT/ui" thurbox ``` 3. Click `quiet`, then the arrow beside `lead`. `quiet` disappears, `waiting` remains visible, and `lead` is selected. Press `r`: the confirmation must name `lead`; dismiss it with Escape. Expand `lead`, select `waiting`, then fold `lead` again: `waiting` must remain selected. 4. From another terminal, run `scripts/dev/sandbox.sh --profile hierarchy-review --isolate-home -- session signal --session waiting --state working`. The parent opens and `waiting` stays selected; pressing `r` must name `waiting`. Dismiss with Escape. 5. Use F6 → Sessions to try all host/repository grouping combinations. Quit with Ctrl+Q, exit the isolated shell, then run `scripts/dev/sandbox.sh --clean hierarchy-review`. — LeTuR's agent
feat(core): persist per-agent environment defaults (#1584) ## Intent Select an agent account once and keep it stable across restart and fork. References #1526. ## What Changed Add an `env` table to agent definitions, resolve `{home}` on the launch host, and save agent defaults together with session overrides. Shared-host forks run on their owner and preserve its selected conversation, hooks, and account environment. Configuration validation, launch plans, session JSON, and the account setup recipe cover the new contract. ## Risk Assessment Per-account Codex hook provisioning (`{env:CODEX_HOME:-~/.codex}` in hook manifest paths) remains unimplemented; this PR leaves #1526 open for that accepted scope. Existing sessions keep their saved environment; edited defaults apply to new sessions. Saved values are visible in session JSON. A mirror reports `env: null`; session-context launch plans and exec commands must run on the owning instance. Native Windows and WSL were not exercised locally. ## Testing Validation of the rebased review fixes is in progress. Failure-first E2E tests exercise caller-owned direct-remote forks after sharing is enabled, and injected environment/recipe save failures after worktree preparation. Full local gates and current-head CI have not yet completed for this revision. Native Windows, WSL, and real SSH integration are not verified locally. — LeTuR's agent
PreviousNext
