- Import `hqt.sandbox` module and `SandboxPolicy` in service.py
- Add `_sandbox_policy` helper to decode persisted JSON policy from a session row
- Add `_wrap_command` helper that calls `sandbox.is_available` / `sandbox.wrap` and raises `ServiceError` when bwrap is unavailable for a sandboxed session
- Add `sandbox: SandboxPolicy | None = None` parameter to `create_session`; persist as `sandbox_json`, pass `sandboxed=` to `build_spawn_config`, wrap command before spawning
- Update `_get_resume_config` to pass `sandboxed=` to `build_resume_config` and wrap the resulting command
- Update rung-2 path in `_respawn_with_fallback` to pass `sandboxed=` and wrap the fresh-spawn command
- Add three new tests covering: sandboxed create wraps command, unsandboxed create does not wrap, and ServiceError on missing bwrap
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a nullable sandbox_json TEXT column to the sessions table via a new
version-3 migration. Updates LATEST_VERSION to 3 and adjusts existing
migration tests to account for the new column and version number.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add sandbox_skip_permission_flags class attr and sandbox_binds() method
to HarnessConfigurator ABC; thread sandboxed: bool = False through all
build_spawn_config / build_resume_config signatures. Claude and Codex
append their respective skip-permission flags when sandboxed=True; Kiro
and Generic accept the param but add no flags.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Core sandbox infrastructure for isolating harness sessions. Bind defines
read-only/writable path bindings, SandboxPolicy specifies filesystem and
network isolation modes, and is_available() is the single source of truth
for bubblewrap availability across the application.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ProjectService now rejects non-existent paths (path-validation feature), so
the TUI and integration tests must seed projects with real tmp_path dirs.
Completes the path-validation test updates beyond test_services.py.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drive TmuxRunner against a throwaway tmux server (isolated via TMUX_TMPDIR)
so the tests never touch the user's live tmux. Register the 'tmux' pytest marker.
Skips when no tmux binary is present.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lead status-right with the focused pane's terminal title (truncated to 21
cols) and widen status-right-length to 64, restoring the preview tmux shows by
default.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a single tmux Alt+p fzf palette that opens nvim/lazygit/shell as styled
tool windows for the current session's project, or clones a fresh harness with
the same project+model. Bridges via run-shell (expands #{window_name}) into a
new 'hqt palette' CLI subcommand, since display-popup does not format-expand its
command (verified on tmux 3.6b).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hold an asyncio.Lock across the spawn->capture window for harnesses that
capture a session id, so two concurrent hqt starts in the same project
never produce overlapping capture windows. Completes the P2 capture-race
fix alongside the ambiguity guard.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When more than one rollout matches the project cwd within the capture
window, return None and warn instead of guessing the earliest. The worst
case is now a kept placeholder id, never a wrong one. Part of the P2
capture-race fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reject non-existent or non-directory paths with ServiceError (already
surfaced as a TUI notification), and store the resolved absolute path.
Closes the P2 finding where bad paths became dead sessions later.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add worktree support to SessionService via the hqt.git.worktree module:
- create_session(worktree_branch=...): creates the worktree before the row
(ServiceError → no row, no spawn); spawn/capture use the worktree path;
blank nickname defaults to the branch name.
- _session_path / _repo_path_for_worktree helpers route every harness-facing
path through the worktree when set, with a parent.parent repo fallback when
the project row is gone.
- attach_session rung 0: recreate a missing worktree from its branch, else
raise "delete the session".
- delete_session(remove_worktree=, force=): remove worktree before deleting
the row; removal failure keeps the row.
- worktree_state_for(session_id): None for plain/missing sessions, else the
module's WorktreeState.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add worktree_path and worktree_branch nullable columns to the Session
model, with a migration v2 that ALTERs the existing sessions table.
Tests cover fresh DB stamping at v2, v1→v2 upgrade path, and row-level
defaults (None when unset, populated when provided).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve all three P1 items: per-operation DB sessions, ServiceError
user-facing failures, and user_version schema migrations. Add the
implementation plan doc.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TUI:
- VimListView: j/k now move down/up in the project and session lists
(arrow keys still work); used by both list widgets.
tmux status bar:
- Widen the gap between window cells via a 2-space window-status-separator
(WINDOW_STATUS_SEPARATOR).
- Fix the current-window cell toggling between padded " 1: name " and
unpadded "1: name". The per-window status format is static, but
set_window_label re-asserted it on every 3s poll — letting a competing
writer (e.g. a stale TUI process running older code with a differently
padded format) flip the cell each cycle. The format is now owned by a
single write-once path (new_window at creation, apply_theme for existing
windows); the poll updates only the dynamic @hqt_label.
Also lands pre-staged scaffolding: AGENTS.md quality gate, TODO.md backlog,
and ty dev dependency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Style the hqt session's status bar with the same Frappé palette the TUI
uses: blue session badge, Mantle background, peach current-window
highlight, muted inactive windows, styled clock/date. The TUI window
shows just its house glyph (no index/flag/text). Applied from the TUI's
on_mount since the os.execvp bootstrap can't run follow-up tmux commands.
All options are session-scoped (never -g), so the user's other tmux
sessions and personal config are untouched. Window-scoped options
(current-window highlight, pane borders, mode style) have no session
scope in tmux, so they're painted per-window on every existing window in
apply_theme and on each new harness window in new_window — otherwise a
window spawned after mount would render with no peach highlight.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TUI for orchestrating AI coding harness sessions (Claude Code, Codex,
Kiro, etc.) via tmux. Click CLI bootstraps a Textual TUI over
ProjectService/SessionService backed by SQLite, spawning harness
sessions as tmux windows through TmuxManager.
Includes recent fixes:
- Visible Tab focus highlight on dialog OK/Cancel buttons
- Auto-select first project on launch
- Auto-select first session + per-project session-selection memory
- tmux new-window targets an explicit free index, fixing
"index N in use" failures (broken spawn/attach in attached sessions)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>