Commit Graph

270 Commits

Author SHA1 Message Date
leetcrypt c83abbee8a feat(ai): calibrate native pruning against real Ollama token counts
Phase 4 (final) of the native-harness working-memory sprint. OllamaProvider
.complete_with_tools now returns (text, calls, usage), surfacing the response's
real prompt_eval_count/eval_count (free — already in the payload). The native loop
EMA-smooths real/estimate into self._tok_ratio (clamped [0.5,3.0]) and prunes
against native_token_budget / ratio, so context budgeting tracks the TRUE window
instead of the systematic bias of the len//4 char estimate. Providers that omit
counts leave the ratio at 1.0, so behaviour is unchanged where unavailable — a
free correctness win, no regression. RAM-only, no disk, no new network frames.

Scope note: touches only cmd_chat/agent/ + providers — disjoint from the parallel
feat(operator) work on this branch, so it merges/reverts independently by path.

Sprint: native-harness-working-memory (Phase 4/4) — see also 46e5620, dc6317f
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 12:27:59 -07:00
leetcrypt dc6317fc46 feat(ai): two-stage native pruning — digest tool outputs before eviction
Phase 3 of the working-memory sprint. _prune_native_messages now compacts in two
stages instead of only evicting whole turns: Stage 1 digests OLD tool-role outputs
to a one-line summary (exit marker + first error line, else first line) via the new
_digest_tool_output; Stage 2 falls back to oldest-first whole-message eviction only
if still over budget. Tool outputs are the biggest context hog, and digesting keeps
the action->result causal chain intact, so whole-turn eviction (which severs it)
becomes a last resort. The pinned head/TASK and the recent keep_recent window
(including the most recent tool output, verbatim) are still never touched.

Return is now (messages, dropped, digested); the sole caller logs both. Clean-room
counterpart to Goose's tool-output condensation track. RAM-only, no disk.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 12:20:02 -07:00
leetcrypt 7e81a059b9 feat(operator): Phase 1 — Claude-driven room bridge (join/read/say)
Invert the agent model: a headless OperatorBridge(Client) owns the
websocket while a Claude Code session drives it via a unix control
socket. Reuses Client SRP/Fernet and AgentBridge's reconnect/serve
shape. Ships an hh-bridge CLI (up/read/say/roster/status/down) with a
seq'd in-RAM inbox + asyncio.Condition long-poll (read --wait) for
in-turn autonomy. ACL/sandbox-status frames are recorded for later
phases. Sandbox drive, delegation and nesting are out of scope here.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 12:19:47 -07:00
leetcrypt 46e5620f89 feat(ai): RAM working-set + failure ledger + stuck/loop detection
Phase 1+2 of the native-harness working-memory sprint. All per-task state is
process RAM only (dies with the task, same lifecycle as MemoryIndex) — no disk,
consistent with the agent's encrypted-transmission / nothing-saved posture.

Phase 1 — _WorkSet dataclass holds what the loop kept re-deriving: sandbox
cwd/shell, files written/read, a failure ledger (cmd -> exit+category), and the
last good command. Discovered cwd/shell carry across tasks in-process via
self._sbx_known (RAM fallback grounding). _render_workset re-surfaces this into
the repair-turn system prompt so it survives context pruning without a NOTES.md
on disk. Folds the old reads_seen set into wset.files_read.

Phase 2 — semantic stuck/loop detection via _action_signature (run_shell keys on
the command, write_file on path+content-hash so real edits aren't repeats,
read_file on path). Aborts honestly when an action fails >=2x verbatim (model
ignoring REPAIR_STANCE) or the same (action,outcome) repeats >=3x, instead of
burning the turn cap re-running a dead action. Verified fix-and-retry does not
false-trip.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 12:18:09 -07:00
leetcrypt d1eb4b0bbb chore(git): keep the benchmark harness local, never push to origin
The bench/ native-harness benchmark suite is dev-internal test tooling tightly
coupled to the local tmux + podman test rig, so untrack it and ignore /bench/
entirely (harness code AND result artifacts) — it stays on disk for local use
but no longer ships to origin. Also keep ignoring /docs/plans/ (local planning).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-15 08:26:43 -07:00
leetcrypt 14469a2649 feat(ai): default to qwen2.5-coder:3b for the sandbox task path
Reorder _CODER_MODELS to prefer the 3b coder build over 1.5b. The 3b roughly
doubles the ground-truth pass rate on the verify-then-repair native harness
(bench: 7/9 vs ~4/9 over the 9 non-net tasks) at a modest CPU-latency cost,
so it is auto-selected ahead of 1.5b when present. 7b was evaluated and
rejected: too slow to first-token inside the engage window on the CPU-only box.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-14 23:12:10 -07:00
leetcrypt dba43e47a2 feat(ai): native-harness repair gate + context discipline
Two clean-room reimplementations layered onto the native `!task` loop
(`_run_native`), aimed at lifting a weak CPU-bound local model's autonomous
pass rate. No code copied from the GPL sources studied; MIT throughout.

NightShift-derived verify-then-repair gate:
- `_classify_failure` maps a failing tool result to a (category, fix-hint) so
  the repair nudge names a concrete cause/next-action instead of "exited N".
- `_relevant_excerpt` keeps the error-relevant tail of a FAILING run_shell
  result within the byte budget (the real error is usually at the tail).
- read-dedupe guard short-circuits repeated idempotent `read_file` of a path
  already read this task.

Exoshell-derived context discipline:
- `_prune_native_messages` budgets the whole message list (~chars/4) and
  evicts oldest removable turns first once over `native_token_budget`,
  pinning index 0, the TASK_MARKER goal, and the most-recent turns — the
  native loop previously grew unbounded, silently pushing the goal out of a
  small model's window on long repair runs.
- TASK_MARKER labels the goal so it is never pruned and re-anchors the model.
- REPAIR_STANCE is appended to the turn system prompt after the first failure
  to swap the whole turn into a diagnose-then-act posture.

Validated on qwen2.5-coder:3b: clean unstitched 7/9 (the local ceiling), no
regression vs baseline; unit-tested pruning (pin survival, oldest-first
eviction, under-budget no-op) and stance trigger. The two remaining fails are
exact-match correctness tasks (a count, a fibonacci string), not harness gaps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-14 23:12:03 -07:00
leetcrypt d211a75616 test(ai): robust TUI driving — fix stale-input + agent-restart keystrokes
The runner cleared the input with only 6 backspaces and fired one unverified
Enter, so a dropped keystroke left a half-typed prompt that corrupted the next
send and lingered after exit. Worse, online/grant detection counted chat events
via `capture-pane -S` — but this is a full-screen alt-screen app whose scrollback
returns stale/empty frames, so detection was unreliable and the restart loop kept
dismissing healthy-but-slow spawns into a churn cycle.

New bench/tui.py exposes verified primitives shared by the runner and a restart
CLID:
  * clear_input / submit — backspace-clear and Enter until the input box reads
    empty (the box has no line-editing; Ctrl-A/U/K arrive as literal letters)
  * capture() now reads only the VISIBLE viewport (no -S) — the live screen is
    the only trustworthy source
  * agent_online() reads the present-tense clergy roster, not scrolled-away chat
  * restart_agent() stops/starts/grants with a generous 180s online wait (cold
    /ai start reloads the model and takes 60-90s on CPU) and retries only a
    genuinely hung spawn

run.py now delegates send/clear/online-check to tui and clears the box on exit.

    python bench/tui.py restart <model>   # one-shot reliable restart+grant

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-13 09:31:47 -07:00
leetcrypt c61413c648 test(ai): screen smollm2/hermes3/mistral — none beat qwen2.5:3b
Pulled and benchmarked three more tool-capable CPU models looking for a better
default. All score 0/12 (vs qwen2.5:3b at 2/12): in the multi-turn agent loop
they leak the positional-in-tags dialect (<tools>run_shell 'cmd'</tools>) the
parser can't recover, even when they emit clean structured tool_calls on a
single-turn probe; smollm2 and mistral also wedge into repeating summaries.

qwen3:4b could not be pulled — Ollama 0.3.9 is too old (HTTP 412), same as
granite3.1-dense:2b. Upgrading Ollama is the highest-leverage next step to test
the qwen3/granite3.x generation. qwen2.5:3b remains the default.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-13 03:28:04 -07:00
leetcrypt e7746e49cf test(ai): benchmark llama3.2:3b + qwen2.5-coder:3b, optimized-harness baselines
Add tracked baselines for two additional CPU models under the optimized
harness (split-tag recovery + greedy decode). Both land at 1/12 — they emit
proper structured calls and fail on capability/content, not parse, confirming
the parser lift is concentrated on the weakest model (0.5b). granite3.1-dense:2b
is incompatible with the installed Ollama version.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 14:01:49 -07:00
leetcrypt 99afff41c6 feat(ai): split-tag tool-call recovery + greedy decode — 3x lift on 0.5b
The first harness changes to move the benchmark off the 1/12 noise floor, on
the model that needs it most (qwen2.5:0.5b: 1/12 -> 4/12, 3/12).

- complete_with_tools now decodes the tool loop at temperature 0 (scoped; chat
  keeps default sampling). At Ollama's default 0.8 the weak model sampled away
  from the tool-call format into prose/fabrication; the nudge prompt changes
  between turns so temp 0 still escapes a failed state on retry.
- Greedy decode made 0.5b's leak deterministic, exposing its real shape: not a
  JSON object with a name key, but the name in a <tools> tag and the args in a
  SEPARATE object — <tools>write_file</tools>{"path":…} — ~5 of 12 tasks/run.
  _NAMED_TAG pairs the tag-name with the following args object, gated on the
  known tool set so it still can't fabricate an action.
- Bridge recovers a ```bash block narrated in prose as a run_shell call,
  non-destructive only (FENCE_DESTRUCTIVE guard); fires on prose-leak turns,
  no-op where the model emits structured calls.

Ablation on 0.5b: structured-JSON-only 0/0 -> fenced+temp0 2/0 -> +split-tag
4/3. The lift is concentrated on the weakest model by design — a 3B emits
proper calls and fails on capability/content (unchanged at 1/12), which no
parser can fix. All recovery paths unit-checked for the positive shapes and
the negatives (prose / unknown tool / destructive block) they must ignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 13:34:21 -07:00
leetcrypt 92c0a07d56 feat(ai): recover JSON tool-call leaks in any wrapper + benchmark verdict
Generalise OllamaProvider._extract_text_tool_calls to recover a tool-call
JSON object regardless of how a small/quantized model wraps it — qwen's
<tool_call> tags, bare JSON, ```json fences, alternate tags (<tools>,
<function_call>), OpenAI {"function":{…}} nesting, and parameters-vs-arguments.
A new _coerce_call gates recovery on the known tool-name set from the tools
schema, so a stray JSON blob in prose (or a hallucinated make_dir) can never
be coerced into an action. 11-case unit check: 8 leak shapes recover, 3
negatives (prose / unknown tool / random config JSON) ignored.

Benchmark verdict (honest): this does NOT move the weak-CPU-model pass rate
— 3b went 2/1/0 of 12 across three passes (baseline 1/12, noise), 0.5b went
0/0 (baseline 1/12). A direct /api/chat probe shows the hypothesis was wrong
about the FORM of the leak: the weak models emit either malformed structured
tool_calls (write_file content:null) or a fenced bash block in prose with no
tool call at all — not JSON-as-text. The structured-JSON recovery is still a
correct, safe hardening for any model that does leak JSON; the real
weak-model lever (parse ```bash fences -> run_shell) is documented as an
explicit safety decision, not folded in here.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 11:07:10 -07:00
leetcrypt 58d405c518 test(ai): baseline 4 local CPU models + fix prompt-fairness bug
Capture native-harness benchmark baselines for qwen2.5 0.5b/1.5b/3b and
qwen2.5-coder:7b (all probed tool-capable; deepseek-r1 and NL2SH reject the
tools field). All cluster at 1-2/12 with high variance; the 7B buys no
pass-rate gain at ~3x latency, so qwen2.5:3b stays the default. The single
biggest score sink across every model is bare tool-call-as-text leaks — a
harness parse gap, the clear next improvement.

Also drop "in your home directory" from the shell prompts: it made literal-
minded models create a home/ subdir (/root/home/a/b/c/...) or use ~/, which
the benchmark itself surfaced. Findings doc + bench README carry the model
comparison table and recommendations (llama3.2:3b / llama3.1:8b for a non-qwen
data point).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 09:30:36 -07:00
leetcrypt df45303f5c test(ai): ground-truth benchmark for the native harness + first baseline
Add bench/ — a 4-category × easy/medium/hard task matrix (shell, code, git,
multi) and a runner that drives the live TUI over tmux and grades each task by
a `podman exec` verify snippet (exit 0 == PASS), never by the model's
self-reported summary (which the weak CPU model fabricates). Tasks run in the
agent's real cwd with bare filenames so the suite measures task completion, not
the model's absolute-path discipline. Completion is detected off the viewport-
independent `is thinking…` footer (the TUI is full-screen, so capture-pane
scrollback is not chat history).

First baseline (qwen2.5:3b): 2/12 PASS, high variance. Surfaces the next
harness-addressable improvements — `<native>` tag leakage and bare
tool-call-as-text — now measurable against this suite. Findings doc updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 01:30:09 -07:00
leetcrypt 156e9fe176 feat(ai): output-aware nudge loop with DONE: terminator for native harness
Replace the overloaded "text + no tool call = done" terminator that made the
weak CPU model stall mid-task or give up after a failing command. Termination
is now a structural `DONE:` text sentinel; a text-only turn is resolved by an
output-aware verdict (DONE: marker / unresolved non-zero exit / no action /
filler language) and re-prompted with an exit-code-aware nudge, bounded by
MAX_NUDGES on top of max_turns. On exhaustion the summary is honest rather than
echoing the model's false "run successfully" — it reports when no tool ran or a
command exited non-zero. Live-validated on qwen2.5:3b: the multi-step stall is
fixed (proj3 completes end-to-end, ground-truth confirmed); the nudge fires on
a 126; residual give-up is model-bound. 23 offline unit assertions pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 00:31:19 -07:00
leetcrypt d5f423024e docs(ai): 2026-06-10 native-harness findings + nudge-loop design
Live 3B-vs-7B command-entry results, observed failure modes (early stall,
give-up-on-error, tool hallucination), Goose/opencode loop-termination
research, and the output-aware dynamic-nudge-loop design to implement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 00:13:14 -07:00
leetcrypt a0aa14d7fd feat(ai): run_shell executes in the real shared PTY + tight action context
Two native-harness improvements, both live-validated against qwen2.5:3b
and qwen2.5-coder:7b on a podman/Kali sandbox:

- §3 PTY-sentinel: run_shell now runs in the REAL shared terminal via
  _run_shell_in_pty (stage cmd out-of-band to a hex-token temp file, type
  a `{ sh CMDF; echo $? >RCF; } 2>&1 | tee OUTF` wrapper into the live PTY,
  poll the rc sentinel out-of-band, then read OUTF). The whole room now
  watches commands execute live instead of an inert `# ▸` comment, while
  output + exit code are still captured for the loop. tee+poll (not
  stream-sentinel) avoids deadlocking the serve loop; the wrapper line
  carries only our own temp paths so room text never reaches the shell
  parser. _exec_tool takes ws to reach the PTY.

- NATIVE_CONTEXT=4: action tasks now get a tight, RAG-free window (last few
  transcript turns only, no semantic recall). A weak model fed prior chat
  chatter latched onto nearby noise (wrote a "grant permissions" script for
  "write a bash script"); feeding just the instruction fixes it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 00:12:01 -07:00
leetcrypt c09b428718 feat(ai): /grant ai grants drive to every AI agent at once
CI / rust client (hh) (macos-latest) (push) Has been cancelled
CI / rust client (hh) (ubuntu-latest) (push) Has been cancelled
CI / rust coverage (push) Has been cancelled
CI / python server (3.10) (push) Has been cancelled
CI / python server (3.11) (push) Has been cancelled
CI / python server (3.12) (push) Has been cancelled
CI / headless e2e smoke (push) Has been cancelled
CI / dependency audit (push) Has been cancelled
CI / secret scanning (push) Has been cancelled
So the owner never has to name each model. Tracks an ai_agents set
(populated from `_ai` typing/stream frames and the "(ai) online" announce,
pruned on leave); `/grant ai` intersects it with the live roster and grants
all in one ACL broadcast. Help text gains a /grant ai row.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 17:02:45 -07:00
leetcrypt 70d6e26b24 feat(ai): prompt-anchor native harness for the fast CPU model
Optimize the native tool-calling loop for qwen2.5:3b on CPU, where it
previously invented paths (/ai/bin/bash), ran scripts it never wrote, and
silently dropped valid actions. Three changes:

- NATIVE_SYSTEM rewritten directive: explicit write→chmod→run workflow,
  relative paths only, never run an uncreated file, never guess interpreter
  paths, fix the cause on non-zero exit.
- New _sandbox_facts() probe injects LIVE SANDBOX STATE (real cwd, bash
  path, current files) into the system prompt so the model anchors to
  ground truth instead of guessing.
- OllamaProvider recovers tool calls qwen emits as <tool_call>{json}</…>
  TEXT in content (brace-balanced JSON scan), so a correct action isn't lost.
- Bump Ollama timeout 120→240s: the tool turn is non-streaming and a long
  write_file can exceed a tighter cap on a contended CPU box.

Live-validated (podman/Kali): 0/3 incoherent → reliable write/run with
self-correction on exit=126 for both single- and multi-script tasks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 17:02:39 -07:00
leetcrypt 0c04ac74ee chore(theme): swap ⛧ sigil for † across UI, scripts, docs
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 15:44:55 -07:00
leetcrypt 06a87e8e76 docs(readme): Parrot OS docker, Kali podman, AI-acts-in-sandbox
Reflect current backends (local/docker/podman/multipass — Docker=Parrot
OS Security, Podman=Kali rootless no-sudo), the backend-first /sbx
grammar, and the AI agent's ability to drive the sandbox via
/ai <name> !<task> (advisory when ungranted, acting once granted).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 15:44:49 -07:00
leetcrypt 0b1d09f0b5 feat(ai): visible native harness — PTY mirror + readable chat
Display-mirror hybrid (docs/plan-harness-visibility.md §2): native tool
calls now show up in the shared sandbox terminal again via inert `# `-
prefixed comment lines (comment-prefix = anti-double-run/anti-escape),
mirroring only each command. Chat de-flooded to opener + final summary.
write_file mkdir -p parent dir so relative/absolute paths both work
(fixes the regression where script creation silently failed). ui.rs
fmt_line returns Vec<Line> splitting on \n so multi-line agent output
renders as an indented block instead of one garbled row.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-09 15:44:40 -07:00
leetcrypt 946df65b72 feat(ai): native host-side tool-calling harness (Phase 2)
CI / rust client (hh) (macos-latest) (push) Has been cancelled
CI / rust client (hh) (ubuntu-latest) (push) Has been cancelled
CI / rust coverage (push) Has been cancelled
CI / python server (3.10) (push) Has been cancelled
CI / python server (3.11) (push) Has been cancelled
CI / python server (3.12) (push) Has been cancelled
CI / headless e2e smoke (push) Has been cancelled
CI / dependency audit (push) Has been cancelled
CI / secret scanning (push) Has been cancelled
Implement the bounded native harness from docs/spec-native-harness.md §1.3 and
make it the default granted-!task path. The model runs host-side (no container→
host Ollama hop); only its tool calls exec in the sandbox.

providers.py:
- OllamaProvider.complete_with_tools(system, messages, tools) -> (text, calls):
  one non-streaming /api/chat turn with a `tools` schema; parses message.tool_calls
  (dict or JSON-string arguments). Caches tool capability (_tools_ok / supports_tools).
- ToolsUnsupported raised when the model rejects `tools` ("does not support tools").

bridge.py:
- NATIVE_SYSTEM + a 3-tool schema (run_shell / write_file / read_file), turn/byte caps.
- _run_native: seed transcript window + task → loop up to max_turns; exec each tool
  call in the sandbox, feed captured output back as a `tool` message; stop on a plain
  answer or the cap; stream per-call progress to chat. Degrades to _run_simple when the
  provider has no complete_with_tools or the model rejects tools.
- _exec_prefix/_exec_capture/_exec_tool: <engine> exec into docker/podman/multipass/local;
  paths passed as positional args + content via stdin (no shell interpolation); combined
  stdout+stderr byte-capped + time-bounded. run_shell is the only intentional shell.
- Guards: DESTRUCTIVE run_shell commands are blocked (not run — no human in the loop;
  use simple + /ai confirm for destructive intent); MAX_COMMANDS budget per task.
- _run_in_sandbox dispatches native|simple; default harness flipped to native.

__main__.py: default harness native (self-degrades to simple, so safe).

Offline-tested: full write/run/read loop on the local backend; destructive block
(rm -rf never executed); ToolsUnsupported → simple fallback. Live Ollama wire
validation deferred to Phase 3 bench (daemon was down). py_compile clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 13:17:21 -07:00
leetcrypt e49dbca451 refactor(ai): strip Goose harness (Phase 1) — native/simple host-side only
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Remove the Goose agentic harness across the codebase per
docs/spec-native-harness.md §3. Goose made N sequential model calls inside the
sandbox (slow on CPU-only hardware) and forced an in-container→host Ollama
gateway that tripped the rootless-Podman slirp4netns loopback bug.

- bridge.py: delete _run_goose/_goose_argv/_goose_present + GOOSE_* consts and
  the present-cache; __init__ now takes harness="simple"/max_turns=5; granted
  !task runs _run_simple until the native loop lands (Phase 2).
- __main__.py: --harness {native,simple} (was {goose,simple}); drop
  --goose-max-turns, add --max-turns; default harness simple.
- app.rs: /ai start accepts native|simple (plain aliases simple) instead of a
  bare plain flag; refresh harness comments.
- sbx.rs: remove the in-container Ollama gateway (Docker host-gateway / Podman
  slirp4netns host-loopback) and the dk_bootstrap OLLAMA_HOST env — kills the
  slirp4netns loopback bug; drop Goose comments.
- bootstrap.sh: drop goose from the prereq probe.
- bootstrap-ai.sh: remove the entire Goose install block, --no-goose flag,
  GOOSE_INSTALLER_URL, host config writer, and goose_bin helper.
- sandbox-bootstrap.sh: remove the in-sandbox Goose binary install + config.
- spec-goose-harness.md: banner — harness portion superseded; Podman stays.

cargo check + py_compile clean. No Goose refs remain (headroom/ untouched).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 13:03:41 -07:00
leetcrypt cf6b0b5b73 fix(ai): agent reconnects instead of vanishing; spec the native harness
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
The /ai agent held a single, un-shielded websocket with no retry. Any
close — server restart, ping/idle reap, laptop sleep, a transient blip —
ended the serve loop, so run_async returned and the process exited
silently: the agent dropped from the roster with no /ai stop and no
goodbye.

- run_async now wraps the connection in a backoff-reconnect loop (1s→30s,
  resets after a healthy ≥30s session). The server frees our session+name
  on drop, so each attempt re-runs SRP to mint a fresh token. Only Ctrl-C
  / process kill (KeyboardInterrupt / CancelledError, how /ai stop ends
  us) breaks the loop.
- _serve shields each frame via _handle_frame so one malformed/poisoned
  frame — or a handler error — can't unwind the loop; ConnectionClosed
  and cancellation propagate up to the reconnect loop.
- Forgiving keepalive (ping_interval=20, ping_timeout=60) so a heavy
  CPU-only Ollama generation doesn't trip a false drop.

Also adds docs/spec-native-harness.md: replace the heavyweight Goose
harness with a lightweight host-side Ollama-native tool-calling loop
(model runs host-side, only commands exec in the sandbox — the
slirp4netns→host-Ollama bug disappears), and a file-by-file plan to strip
all Goose integration. Supersedes the harness portion of
spec-goose-harness.md (Podman backend stays).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 12:49:04 -07:00
leetcrypt f93c8c5e4f feat(sandbox): VM library — pointer catalog + local build via /sbx vmlib
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Add an installable-VM catalog that ships pointers only (no multi-GB images
in the repo). When a VM is chosen it is BUILT LOCALLY on the caller's own
machine — nothing is relayed to the room.

- scripts/vbox-library.json: 7-entry manifest (Windows 11, macOS Sonoma,
  Kali, Parrot, Ubuntu 24.04, Fedora 41, Debian 12) with download
  pointers, ostype, cpu/mem/disk, and build kind.
- scripts/vbox-library.sh: --list / --info / --plan / --install. Build
  kinds: iso (download/--iso + createvm + boot installer; EFI+TPM for
  Win11), cloudimg (delegate to vbox-new.sh, unattended), ova (import),
  manual (pointer-only, e.g. macOS per Apple licensing). Detect-first:
  --plan changes nothing, install refuses to clobber and rolls back
  half-built VMs, direct URLs fall back to the page + --iso <path>.
- sbx.rs: vbox_library()/library_vm()/vbox_library_install() loaders +
  running_vms() for live-state markers.
- app.rs: /sbx vmlib (catalog ✓installed/↓available), /sbx vmlib <id>
  (pointer/notes), /sbx vmlib <id> install [--iso path] (local build);
  /sbx vms now flags running VMs (▶). Registered in SBX_SUBCOMMANDS +
  usage.
- ui.rs: help entry for the VM library.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 12:09:54 -07:00
leetcrypt 15aa2027c4 refactor(sandbox): remove container browser-GUI (noVNC), keep vbox VM GUI
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
The docker/podman headless-container desktop (XFCE + TigerVNC + websockify
+ noVNC published on host 127.0.0.1:6080) is removed. It was unreliable: the
desktop stack installed asynchronously during provisioning with every step
wrapped in `|| true` (silent failures), while the host port mapping existed
the moment the container ran — so opening the browser before websockify bound
6080 inside the container reset the connection (ERR_CONNECTION_RESET), with no
readiness signal and no loud failure. Containers also can't be rendered as a
real desktop by VirtualBox (no framebuffer/display), so the only desktop path
that stays is the native VirtualBox VM GUI.

Removed:
- sbx.rs: GUI_PORT const; PortHolder/port_holder()/kill_port_holder() (the
  port-consent gate existed only for the noVNC publish); the `gui` param +
  `-p 127.0.0.1:6080:6080` block in prepare(); HH_SBX_GUI env in dk_bootstrap();
  the `gui` param on provision().
- app.rs: PendingGuiLaunch + PortPrompt structs; the port_prompt App field; the
  `gui` field on PendingSudoLaunch; the port-consent modal + the sudo-submit
  port check; want_gui/gui parsing + the container-GUI launch message; `gui`
  threaded through spawn_launch and all call sites.
- sandbox-bootstrap.sh: the entire HH_SBX_GUI=1 desktop block.
- ui.rs / usage strings: dropped "[gui]"/"noVNC desktop" from the docker/podman
  help and the /sbx usage line.

Kept (unchanged): the VirtualBox VM GUI — gui_launch() (VBoxManage startvm
--type gui), launch_vbox_gui, `/sbx vbox gui <vm>` and the `/sbx gui` alias. The
`gui` keyword is still stripped from positionals so `/sbx vbox gui <vm>` parses.

cargo check passes clean (no warnings).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 11:38:43 -07:00
leetcrypt 18a8936caa feat(sandbox): masked sudo modal for VirtualBox install path
The VBox GUI launch path previously had no in-TUI password capture — it
relied on cached creds (`sudo -n`) and otherwise aborted with guidance.
Generalize the existing masked sudo modal to cover it: `SudoPrompt.pending`
becomes a `PendingPrivileged` enum (container `Launch` | `VboxInstall`), so
the same local-only, never-logged password buffer now authorizes a VBox
install too. On submit the captured password is fed to `ensure_vbox_install`
via `sudo -S`, then the VM boots. Empty/Esc still cancel cleanly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 11:13:18 -07:00
leetcrypt c6eeb9b897 feat(sandbox): port-in-use consent gate + uniform sudo capture
GUI launch (docker/podman) now pre-flights the noVNC port (6080): if it's
already bound, a modal names the holding pid and asks before killing it and
proceeding — never a blind `bind: address already in use` collision.

Sudo capture is now uniform across every backend installer. Previously only
Docker fed the masked-modal password through `--stdin-pass`/`sudo -S`; the
captured password was silently dropped for Podman/Multipass and never wired
for VirtualBox, and those scripts used bare `sudo` (which hangs/corrupts a
raw-mode tty). Now:
- shared `run_ensure()` feeds the password to any ensure-*.sh via stdin
- podman/multipass/vbox scripts gain the docker sudo ladder
  (interactive `sudo` / `--yes` `sudo -n` / `--stdin-pass` `sudo -S -p ''`)
- podman's apt path wrapped in `sh -c` so one sudo covers update+install
- vbox GUI path preflights `sudo_ready()` with actionable guidance, falling
  back to fail-fast `sudo -n` instead of a tty hang

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 11:00:09 -07:00
leetcrypt ad13ec14ba docs(sandbox): specs for goose harness + GUI distros, command ref, themes
Add the goose-harness and sandbox-distros-GUI design specs, a consolidated
command reference and demo-reels plan, the AI-harness planning note, and
two new client themes (blue-orange, pink-red-gray).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 10:38:36 -07:00
leetcrypt 20091a9c26 feat(sandbox): podman backend + goose AI harness + noVNC GUI sandbox
Add Podman as a rootless/daemonless sandbox backend alongside Docker,
Multipass and Local, and wire Goose in as the default agentic harness
for the granted `!task` path (bridge execs `<engine> exec <name> goose
run` and streams output to chat; auto-degrades to the simple one-shot
injector when goose is absent).

Add an optional GUI sandbox track (XFCE + TigerVNC + websockify/noVNC on
:6080) summoned via `/sbx <engine> gui`, plus container-side provisioning
in sandbox-bootstrap.sh and a host-side ensure-podman.sh prereq helper.

Refresh the in-app command help to the backend-led `/sbx <engine> [gui]`
grammar and minor ui tweaks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-08 10:38:18 -07:00
leetcrypt eec1714735 feat(layout): input-bar + chat/clergy resize, masked sudo-for-docker, edit-mode unlock
Make the message-input bar part of the F5 resize cycle so its height is
adjustable without a sandbox (multi-line/wrapped inputs are now readable),
and let chat/clergy borrow height from the compose box when no terminal is
present. Add Option C masked sudo prompt that feeds `sudo -S` over stdin to
install/start Docker — the password never reaches chat, the PTY, or outbound
frames, and Docker Desktop on Linux is detected so no sudo is requested.

Fix a freeze where clicking the compose box entered layout-edit mode and
silently swallowed every keystroke: clicking the input bar no longer enters
edit mode, and typing any printable char now drops out of edit mode and types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 16:10:27 -07:00
leetcrypt 2ae4adfde4 docs(layout): spec for BSP pane tree (resize panes in both dimensions)
Design for replacing the two-scalar layout (pty_pct + roster_width) with a
binary space-partition pane tree so every pane is resizable on both axes,
fixing the "only roster width adjusts" limitation. Rolls a small in-repo tree
(no new dep) and drives the PTY grid from the terminal pane's actual Rect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 14:07:05 -07:00
leetcrypt 9a038455ca docs: correct /send syntax, add /sendroom + /clear, fix Ctrl+Alt+P note
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
README command table claimed `/send <path>` sends to the room; actual code
(app.rs) makes `/send <user> <path>` a targeted send and `/sendroom <path>`
the room-wide offer. Add the missing `/sendroom` and `/clear` rows (both
already in the in-app help) and drop the inaccurate "save to disk" claim on
Ctrl+Alt+P — saving is `/theme save`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 13:48:44 -07:00
leetcrypt 556ba5f23a docs: drop inaccurate private-beta note — git.churchofmalware clone is public
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Verified anonymous clone from git.churchofmalware.org succeeds; only the
GitHub mirror is private. Restore the plain public clone instructions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 13:37:33 -07:00
leetcrypt 7cba32560c docs: document private-beta clone (token/SSH) for git.churchofmalware.org
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
The repo is private, so anonymous clone 404s. Add token + SSH clone
instructions under the quick-start so invited users can install while we
promote the Gitea instance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 13:32:29 -07:00
leetcrypt f1467b00ac docs: point clone URL at git.churchofmalware.org; refresh /sbx command list
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
- Update the git clone URL in README and CONTRIBUTING from the old
  GitHub remote to the project's Gitea instance
  (git.churchofmalware.org/trilltechnician/hack-house.git). Verified the
  clone succeeds over HTTPS against that path.
- CONTRIBUTING: fork step now points at the Gitea instance, not GitHub.
- Refresh the /sbx command table to match the client: `/sbx launch` now
  documents the `install` consent token and `--start` daemon boot, and a
  new row covers `/sbx launch vbox [new [name]]` (VM picker / cloud-init
  build).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 13:24:24 -07:00
leetcrypt 9959e09fe2 docs(scripts): add --help to remaining scripts; document scripts + layout in README
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
- Add a -h/--help guard (prints the usage header) to the five scripts
  that lacked one: smoke.sh, smoke-e2e.sh, test-features.sh,
  demo-save-load.sh, sandbox-bootstrap.sh. All 15 hh/scripts/*.sh now
  respond to --help.
- README: new "Scripts" section detailing every script (setup, hosting,
  sandbox provisioning, tests) and noting each takes --help; plus an
  archive note for the film-* recording scripts.
- README: new "Window layout" section documenting live pane resizing
  (F4 fullscreen, click/F5 to select + arrows to resize) and the
  /layout save/load/list/rm/reset presets, with matching keybinding-table
  rows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 13:20:07 -07:00
leetcrypt cab266588d chore(scripts): archive film demos, fold join.sh into connect.sh
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Trim newcomer-facing clutter in hh/scripts/ without changing the running
product or CI.

- Move the one-off demo-recording scripts (film-save-load.sh,
  film-virtualbox.sh) to scripts/archive/ — they depend on external
  personal tooling (asciinema, video-toolkit, edge-tts, xdotool) and were
  never wired into the README or CI.
- Delete join.sh: it duplicated connect.sh's join with fewer options
  (hardcoded port, plaintext-only). Its only unique behaviour — a
  pre-join git pull (gitea→origin, ff-only) + rebuild — is now an opt-in
  `--sync` flag on connect.sh, leaving one canonical joiner.

No scripts or docs referenced the removed/moved files, so nothing else
needed updating.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 13:12:46 -07:00
leetcrypt 37d5916b2e feat(layout): fullscreen + interactive pane resizing
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Add a window-management layer so the chat, roster and sandbox-terminal
panes can be resized live and fullscreened, with named presets for
recall.

- New layout.rs: single source of truth for the body split (pty_pct,
  roster_width) and a Zoom state (Normal/Term/Chat), persisted to
  layouts/<slug>.toml like themes. Both ui::draw and app::sbx_dims read
  from it, so resizing just mutates state and clears announced_dims —
  the per-tick loop re-syncs and broadcasts the new PTY grid.
- F4 cycles terminal/chat fullscreen (F-keys aren't forwarded to the
  shell, so nothing is stolen from in-shell apps).
- Interactive editing: click a pane (or F5 to cycle terminal -> chat ->
  roster) to select it; arrows then resize it live, Esc/Enter finishes.
  The selected pane gets a bold accent border and an ✎ title marker.
  ui::pane_at hit-tests against the same body_areas rects draw() paints.
- /layout slimmed to presets only (save/load/list/rm/reset); the old
  numeric pty/chat/roster and full/chatfull/normal verbs are replaced by
  the interactive flow.
- Help menu updated: LAYOUT cluster + KEYS line document F4, click/F5,
  arrows and Esc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 12:53:44 -07:00
leetcrypt c9cdd2feca feat(sbx): install Docker/Multipass on demand from /sbx launch
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Previously, launching a sandbox on a machine without the backend binary
died with a raw "not installed" error and no path forward. Now the
missing backend can be installed in-line, gated on explicit consent, so
a fresh checkout can go from zero to a running sandbox without leaving
the TUI.

Folded into the existing `/sbx launch` verb rather than a new command
(menu is already dense): `/sbx launch docker|multipass [image] install`.
The `install` token opts in; without it the user gets an actionable
error naming the exact retry. The token is filtered out of positional
image parsing so it never shadows a custom image.

The install runs off-thread inside the existing spawn_launch task,
before provisioning, so the TUI never blocks and the *launching guard is
cleared via BrokerMsg on failure. A fresh Docker install also leaves its
daemon up, so launch proceeds straight through.

Scripts (detect-first, never silent, --plan dry-run, idempotent if
already present):
- ensure-multipass.sh (new): Linux→snap (clear failure if snapd absent),
  macOS→brew cask, Windows→winget.
- ensure-docker.sh: new --install mode using Docker's OFFICIAL,
  GPG-verified apt repo (docker-ce) on Debian/Ubuntu, with dnf
  (Fedora/RHEL) and pacman (Arch) fallbacks. Deliberately avoids piping
  get.docker.com into a root shell. Existing daemon-start path intact.

sbx.rs: docker_installed()/multipass_installed() detectors and
ensure_docker_install()/ensure_multipass_install() wrappers; ENSURE_MULTIPASS const.

ui.rs: help text documents the [install] option on both backends.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-07 00:35:19 -07:00
leetcrypt 764c827d07 fix(ux): forward Esc and Ctrl-X to the sandbox shell while driving
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Two interactive shell apps were unusable in the shared sandbox because the
TUI swallowed their core keys:

- Esc released drive mode and was never mapped in key_to_pty, so vim could
  never leave insert mode. Esc now forwards 0x1b to the PTY; F2 (already a
  drive toggle) is the release key.
- Ctrl-X (owner kill switch) was intercepted globally, so nano could never
  quit. It's now gated on !app.driving — while you hold the shell Ctrl-X
  reaches the PTY (0x18, nano's quit); release with F2 to arm the kill
  switch.

Updated the on-screen hints and /help KEYS cluster to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 22:53:45 -07:00
leetcrypt b8a06077a4 fix(sbx): make /sbx save work on a live multipass sandbox
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Multipass can only snapshot a powered-off instance, so `/sbx save` on a
running multipass sandbox previously just surfaced multipass's "instance
must be stopped" error — making the snapshot that `/sbx load` needs
impossible to create through the UI.

Now multipass save powers the instance down before snapshotting, and the
handler tears the shared session down first (same as `/sbx stop`, but
WITHOUT purging so the instance — and thus the snapshot — survives for a
later `/sbx load`). Docker is unchanged: `docker commit` captures a live
container, so its save stays non-disruptive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 22:38:55 -07:00
leetcrypt 6160d957f5 feat(sbx): create fresh VirtualBox VMs via cloud-init (/sbx launch vbox new)
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
The existing vbox paths only open pre-made images; there was no way to
build one from scratch. Add `/sbx launch vbox new [name]` backed by
scripts/vbox-new.sh: download an Ubuntu cloud image, convert it to a VDI,
build a cloud-init NoCloud seed that creates a sudo login user and installs
the sandbox-tools.json toolchain on first boot, then create + boot the VM.

VirtualBox guests can't be exec'd into like docker/multipass, so cloud-init
is the provisioning channel. Generates a one-time login password (or takes
--pass) and authorizes a local SSH key if present. Runs off-thread; rolls
back a half-built VM on failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 22:33:37 -07:00
leetcrypt d448314e5e feat(sbx): VM dev-toolchain bootstrap + save/load parity across backends
Reorganize the help menu into one VIRTUAL MACHINES cluster covering all
backends, and bring docker/multipass/vbox to save+load parity:

- Launch-time dev toolchain: sandbox-bootstrap.sh + editable
  sandbox-tools.json (vim/curl guaranteed), installed in docker AND
  multipass sandboxes at provision time.
- Vbox load: vm_restore + `/sbx vmload <vm> [label]` (restore snapshot
  then boot the GUI).
- Multipass load: `/sbx load` is now backend-aware (locate_snapshot +
  SnapKind), mp_restore re-attaches the shared shell; teardown stops
  (not purges) an instance that still has snapshots so they survive
  `/sbx stop`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 22:29:24 -07:00
leetcrypt cb1f6134f6 feat(sbx): apt-get update on docker launch so installs just work
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Base ubuntu images ship without /var/lib/apt/lists, so apt-get install
failed with "Unable to locate package" until the user ran update by hand.
Refresh the index once during Docker provisioning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 21:54:46 -07:00
leetcrypt 7624cb04f7 feat(ux): F1 toggles help, --user flag, connect.sh fresh-build
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
- app.rs: F1 now closes the help overlay (toggle), not just opens it
- host-house.sh: add --user/--name flag to override the $USER default seat
- connect.sh: incremental cargo build before launch so the UI is never a
  stale release binary; --no-build escape hatch for toolchain-less joiners

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 21:51:54 -07:00
leetcrypt d948cdb63e fix(scripts): host-house.sh builds from main by default
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
Add a BRANCH guard (default: main) so launching the house never demos a
stale checkout. ensure_branch() switches to $BRANCH before the cargo build,
refuses when the tree is dirty (won't clobber uncommitted work), and no-ops
when already on-branch or when BRANCH= is passed to keep the current checkout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 21:40:41 -07:00
leetcrypt 4a73eb04b9 feat(scripts): all-in-one host-house.sh; free stale port; fail-fast join sync
CI / rust client (hh) (macos-latest) (push) Waiting to run
CI / rust client (hh) (ubuntu-latest) (push) Waiting to run
CI / rust coverage (push) Waiting to run
CI / python server (3.10) (push) Waiting to run
CI / python server (3.11) (push) Waiting to run
CI / python server (3.12) (push) Waiting to run
CI / headless e2e smoke (push) Waiting to run
CI / dependency audit (push) Waiting to run
CI / secret scanning (push) Waiting to run
- host-house.sh: one tmux session hosting a room (server window) plus your own
  GUI client window — builds, frees the port, waits for /health, then attaches.
- host-room.sh: free_port() kills any stale LISTENer squatting on the port so
  the bind can't fail with "address already in use".
- join.sh: cap each remote pull with `timeout` + GIT_TERMINAL_PROMPT=0 so an
  unreachable remote fails fast instead of hanging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 19:45:52 -07:00
leetcrypt 98e202e0fe feat(sbx,ft): share VirtualBox VMs — vbox launch grammar, picker & streaming transfer
Unify sandbox summoning under `/sbx launch <docker|multipass|vbox>`, each
running on the invoker's own machine. For vbox:
- `/sbx launch vbox` opens an arrow-navigable VM picker (↑↓/Enter/Tab/Esc).
- `/sbx launch vbox [gui] <vm>` boots frictionlessly for a host that already
  has VirtualBox + the VM imported; a non-host appends `yes` to install
  VirtualBox, import the shared appliance, and/or free VT-x first.
- `/sbx gui <vm> [yes]` kept as an alias.

Add `vm_registered()` + `import_appliance()` (VBoxManage import) in sbx.rs.

Make file transfer stream disk-to-disk so multi-GB VM images can be shared
(the old 50 MB in-memory cap blocked them): `STREAM_MAX` = 16 GiB for `/send`
(`MAX_SIZE` now guards only the in-memory tar_path for sandbox injection).
`prepare_send` stat+stream-hashes off the UI thread; `Sink`/`commit` write
incoming chunks straight to a temp `.part` file and verify SHA before moving
into place. Wire frames (offer/accept/chunk/done, 64 KB, base64) unchanged.

A received `.ova`/`.ovf` auto-imports so the recipient can immediately
`/sbx launch vbox gui <vm>`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 19:45:45 -07:00