Commit Graph

40 Commits

Author SHA1 Message Date
felixm 7109be8cbf test: cover schedule prompt modal validation 2026-06-11 08:48:35 -04:00
felixm ac7d57bfeb feat: add delayed prompt modal 2026-06-11 08:41:39 -04:00
felixm f10264f695 feat: dispatch due scheduled prompts 2026-06-11 08:40:16 -04:00
felixm 6c7e4e4682 Merge branch 'sandboxed-sessions' 2026-06-11 08:35:14 -04:00
felixm 88a52d0e6d fix: handle scheduled prompts on session deletion 2026-06-11 08:26:33 -04:00
felixm 4169e7fc28 feat: schedule prompts in session service 2026-06-11 08:18:44 -04:00
felixm 5f29418186 fix: make sandboxed sessions usable for real git workflows
Address three integration gaps found in review:

- Bind ~/.gitconfig read-only in the sandbox base layer so `git commit`
  finds user.name/email inside the jail (spec §3).
- Bind a worktree session's repo common .git dir writable: the worktree's
  real gitdir lives at <repo>/.git/worktrees/<branch>, outside the bound
  cwd, so git was broken in sandboxed worktree sessions.
- doctor now gates on sandbox.is_available() (the single source of truth
  shared with the New Session dialog) rather than a raw shutil.which, so
  it agrees with the service on non-Linux platforms (spec §6).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 08:16:48 -04:00
felixm 60612065ff feat: persist scheduled prompts 2026-06-10 22:55:18 -04:00
felixm c574117c71 feat: report tmux prompt injection failures 2026-06-10 22:54:48 -04:00
felixm 09505f0758 fix: keep tmux focus during window index reset 2026-06-10 22:18:13 -04:00
felixm ee4bb4cbfa feat: expose tmux window index reset 2026-06-10 22:02:09 -04:00
felixm 666df40aca test: cover tmux window index reset 2026-06-10 21:51:16 -04:00
felixm 7d851dcb7e feat: doctor reports bubblewrap availability
Adds a non-fatal bubblewrap check to the doctor command that reports whether
the bwrap binary is available. This is necessary for sandboxed sessions and
the check is displayed alongside other system requirement checks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 21:37:30 -04:00
felixm f6ed53ce23 feat: sandbox toggles in New Session dialog, gated on bwrap availability
Adds sandbox_available param to NewSessionScreen, renders Switch/Select/
Switch widgets for sandbox on/off, filesystem access (rw/ro), and network
toggle. When bwrap is unavailable the switch is disabled and a warning label
is shown. The _policy_from staticmethod maps widget values to SandboxPolicy.
The result tuple gains SandboxPolicy | None as its fifth element. app.py
passes sandbox_available=sandbox.is_available() when opening the dialog and
unpacks the new element to forward sandbox= to create_session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 21:33:34 -04:00
felixm e54cbb96b6 feat: wrap sandboxed sessions in bwrap on spawn and resume
- 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>
2026-06-10 21:28:22 -04:00
felixm 080ddd9364 feat: add Session.sandbox_json column and migration
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>
2026-06-10 21:23:45 -04:00
felixm 4c36385045 feat: configurators declare sandbox skip-permission flags and binds
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>
2026-06-10 21:20:50 -04:00
felixm 9db79c68f4 feat: sandbox.wrap builds bwrap argv from policy and binds
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 21:16:44 -04:00
felixm 3130d95cc5 feat: sandbox primitives — Bind, SandboxPolicy, is_available
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>
2026-06-10 21:15:30 -04:00
felixm dff53144c9 Merge branch 'main' into worktree-sessions
# Conflicts:
#	src/hqt/sessions/service.py
#	tests/test_sessions.py
2026-06-10 21:07:20 -04:00
felixm d78d8ee149 test: add isolated real-tmux smoke tests
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>
2026-06-10 20:56:58 -04:00
felixm ace537e3d6 fix: worktree session UI safety and visibility (review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 20:49:12 -04:00
felixm 6ed598b443 fix: serialize Codex spawn->capture with a lock
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>
2026-06-10 20:47:35 -04:00
felixm f6a90e8f4d fix: refuse ambiguous Codex session-id capture
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>
2026-06-10 20:43:52 -04:00
felixm 9efa270422 feat: worktree session UI (dialog checkbox, confirm delete, list marker)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 20:41:11 -04:00
felixm 1b79530214 feat: validate project paths at create/update
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>
2026-06-10 20:40:24 -04:00
felixm 0cf147ee25 fix: robust worktree repo-path derivation and branch typing (review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 20:28:25 -04:00
felixm 61f1a07ba7 feat: worktree-aware session lifecycle in SessionService
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>
2026-06-10 20:21:37 -04:00
felixm 860f1b59bd feat: worktree columns on sessions + migration v2
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>
2026-06-10 20:03:38 -04:00
felixm cc49859b08 fix: harden git worktree module (review findings)
- is_git_repo: check out.strip() == "true" (bare repos / .git dirs exit 0 with "false")
- _validate_branch: new shared helper calls check-ref-format; used in all three
  public branch-taking functions to block option-injection (--detach, --all, etc.)
- _ensure_worktrees_excluded: prepend newline when existing content lacks trailing newline
- WorktreeState: make frozen=True (codebase precedent)
- create_worktree invalid-branch ServiceError now includes git stderr
- remove_worktree: log.debug for silently-swallowed branch -d / worktree prune failures
- worktree_state docstring: document git-failure → clean-state behaviour
- Tests: 9 new regression tests; raw subprocess.run commit calls replaced with _commit helper

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:59:16 -04:00
felixm bb142d05e4 feat: git worktree module
Adds hqt.git.worktree with slugify, is_git_repo, WorktreeState,
create_worktree, add_worktree_for_branch, worktree_state, and
remove_worktree; includes 25 real-git integration tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:51:45 -04:00
felixm 98ea8a1a5c feat: factory-based DB wiring and ServiceError notifications in TUI
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:31:06 -04:00
felixm 208a9028e7 refactor: per-operation DB sessions and ServiceError in SessionService
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:16:15 -04:00
felixm c93135b338 refactor: per-operation DB sessions in Project/Mcp services
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:10:09 -04:00
felixm 5109609b8d feat: add ServiceError and detached-safe session factory
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:05:40 -04:00
felixm 8870b8f3ef feat: add user_version-based schema migrations
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:02:53 -04:00
felixm a7851e7007 Satisfy project quality gates 2026-06-10 18:23:52 -04:00
felixm 9379e1523c Add vim list nav, fix window-title padding toggle, widen title spacing
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>
2026-06-10 17:43:47 -04:00
felixm 5eea909029 Theme tmux status bar to match Catppuccin Frappé TUI
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>
2026-06-10 17:26:34 -04:00
felixm 90944948f5 Initial commit: hqt — HQ Terminal TUI
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>
2026-06-10 17:06:57 -04:00