62 Commits

Author SHA1 Message Date
leetcrypt 8ba04316c1 fix(mobile-web): collapse carriage-return redraws in the console terminal
readline repaints its prompt in place with a lone \r (…└─# \r└─# cmd); the
browser turned that \r into a newline, stacking the pre-redraw prompt as a
phantom extra line (the mobile-only "extra --#"). Add a collapseCR() mini-vt
pass that resolves \r as a column-0 overwrite (ANSI escapes treated as
zero-width so colours survive; honours \x1b[K erase-to-EOL) before ansiToHtml,
so the real prompt stays and only the duplicate is dropped. TUI was already
correct via its real vt emulator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-16 11:07:17 -07:00
leetcrypt 012b7c15e5 docs(mobile): point quickstart setup at termux-bootstrap.sh
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 22:40:06 -07:00
leetcrypt 3a90ee985a feat(mobile): termux-bootstrap.sh + robust ADB serial detection
termux-bootstrap.sh: one-shot on-device installer (pkg cryptography to avoid a
rust build, pure/wheeled pip deps, no srp C-ext, wake-lock, hh alias, Phase-0
preflight) — verified green on the Fairphone.

phone-capture.sh: auto-detect the adb device serial instead of assuming :5555,
since Wireless Debugging uses a dynamic port; target that serial for
screencap/screenrecord/pull. Clarified the pairing help to use the Tailscale IP.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 22:39:47 -07:00
leetcrypt e97a45b78d docs(mobile): operator quickstart — hh launcher, direnv, web console, capture, skills
User-facing how-to distinct from the design spec: the port-probe reachability
gate, one-time Termux setup + tar-over-ssh sync, the hh launcher grammar,
per-directory direnv auto-host, the mobile web console, phone-capture.sh routes
(non-root file/term/webshot vs ADB shot/screen), and the Fairphone skill
ship-list. Linked from termux-operator.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 22:21:30 -07:00
leetcrypt f4572c985e feat(mobile): phone-capture.sh — pull screenshots/files/terminal from the Fairphone
Laptop-side capture tool that SSHes into the phone. Non-root routes work from
the Termux app uid (file pull via tar-over-ssh, tmux capture-pane, headless
chromium webshot of the hh web console); shot/screen use ADB (Wireless
Debugging) since screencap/screenrecord can't reach SurfaceFlinger as an app
uid. --tg [target] ships any artifact to Telegram via video-toolkit tg-send.sh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 22:17:53 -07:00
leetcrypt 2cd61ee598 feat(mobile): hh launcher + direnv auto-host for Termux/Fairphone
Add a single `hh` launcher (host/join/web/status/stop + operator
passthrough) and a direnv snippet so `cd`-ing into a project dir
auto-hosts a room named after it. Verified live on the Fairphone:
two rooms coexist on port-scoped tmux windows (manual :8799 +
direnv :8801), status health-checks each via curl (ss/netstat are
unreliable on Android), stop-all tears down cleanly.

Termux notes baked in: no /usr/bin/env (invoke via the `hh` alias,
not the shebang); status enumerates operator daemons under
$TMPDIR/hh-bridge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 22:10:53 -07:00
leetcrypt 4a17f1c6f1 docs(mobile): harden Fairphone/Termux SSH operations doctrine
Encode the robust way to reach + drive the phone in CLAUDE.md and the
hh-operator skill (which had no mobile section at all):

- One canonical hardened ssh prefix (ConnectTimeout/BatchMode/accept-new)
  that never hangs and dodges the wrong-alias trap.
- Reachability gate probes the TCP port, NOT `tailscale status` — the
  tailnet shows fairphone-6 "active" even when Android has killed
  backgrounded Termux and sshd is dead (observed live).
- Fix-is-on-device guidance (termux-wake-lock) instead of retrying a dead
  port; Termux env quirks (no /tmp, no rustc, skip srp).
- Status advanced: phone can both JOIN and HOST rooms (server-side SRP
  shim) + the stdlib mobile web console mirrors a real room.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 21:54:54 -07:00
leetcrypt 7bd972f39c feat(srp): pure-Python server Verifier so Termux can HOST a room
The srp package's C-extension has no aarch64/bionic wheel, so a phone in
Termux couldn't run the room server. Add the server side of SRP-6a
(create_salted_verification_key + Verifier) to the pure-Python shim and
fall srp_auth.py back to it on ImportError.

Proven live: a laptop (C-ext srp client) authenticated against a room
HOSTED ON THE PHONE (pure Verifier) over Tailscale — cross-implementation
handshake with matching session keys. Locked in with interop tests
covering real-client-vs-pure-Verifier, pure-vs-pure, and a wrong-password
negative control.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 21:39:17 -07:00
leetcrypt 1c36325d07 feat(operator): mobile hack-house console — terminal + keys + chat
Rebuild the web UI into a room mirror that looks/feels like the TUI:
tabbed Terminal + Chat, a live shared-sandbox terminal pane (polls
/api/screen, client-side ANSI/SGR render), keystroke drive with quick
keys + a command line (POST /api/keys), roster and a driver/watching
badge. Read-only until the room /grants this operator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 16:19:53 -07:00
leetcrypt 35da940cc8 feat(operator): mobile web chat UI in front of the daemon
Add `operator web` — a stdlib-only http.server shim over the control
socket so a phone browser can drive a room: message bubbles, live
long-poll updates, roster, and a real text box (GET /, /api/events,
/api/status, POST /api/say). Solves the Termux input gap where the
tmux `read --wait` loop is read-only. Binds 127.0.0.1 by default since
the socket grants room-send.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 16:00:10 -07:00
leetcrypt 2d6258cab0 docs: record the Fairphone/Termux SSH path in the operating guide
The phone is a live SSH host on the tailnet, so on-device Phase-0 work can be
driven directly — not "paste it back". Document the verified reachability
(fairphone-6 100.95.202.68:8022, user u0_a203, ~/.ssh/phone-deploy), the
wrong-alias trap (`phone` points at an offline device), Termux env facts, the
rust-free dep install, and the tar-over-ssh staging so future sessions check
reachability first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:40:11 -07:00
leetcrypt 2cfc7112fc build: add on-device Termux Phase-0 preflight script
scripts/termux-preflight.py runs the docs/termux-operator.md §0.1-0.5 checks on
the phone and prints a PASS/FAIL report: python>=3.11, requests/rich/websockets
import, cryptography+Fernet round-trip (version floor treated as soft), srp
C-ext OR the pure shim, that client/operator import without the server stack,
and an AF_UNIX bind under the $TMPDIR runtime root. Makes the one remaining
on-device Phase-0 step a single command. Exits non-zero on any hard failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:21:29 -07:00
leetcrypt 3fddc17454 fix(operator): honor $TMPDIR for the runtime root (Termux has no /tmp)
runtime_root() fell back to /tmp, which does not exist on Android/Termux, so the
daemon's control.sock would land in an unwritable path. Fall back
XDG_RUNTIME_DIR -> TMPDIR -> /tmp; Termux sets TMPDIR to $PREFIX/tmp. No change
on a normal box (TMPDIR is typically unset there). Enables the Phase 1
join/read/say socket to work on-device.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:20:39 -07:00
leetcrypt 3d085eaec4 fix(operator): don't import the server stack on the operator path
cmd_chat/__init__.py eagerly imported run_server (sanic/pydantic) and Client,
so any `cmd_chat.*` import — including `python -m cmd_chat.operator` — pulled
the server stack at package init. On a phone/Termux operator that installs only
the operator deps (and where srp may be unbuildable), this failed before the
client's pure-SRP fallback could help.

Make both imports lazy inside main()'s serve/connect branches. The operator
path now imports cleanly with srp/sanic/pydantic all absent (client falls back
to _srp_pure). Add a subprocess regression test to keep it that way.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:14:13 -07:00
leetcrypt 4bd38d9c30 build: add requirements-operator.txt for phone/Termux operators
Operator-only dependency subset (requests, rich, websockets, cryptography>=41,
srp) so the C-heavy cryptography>=46 server pin can be satisfied by Termux's
packaged build. Server requirements.txt is left unchanged. srp is best-effort:
the client falls back to the pure-Python shim when it can't be built.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:06:39 -07:00
leetcrypt 750692b6aa test(client): interop-gate the pure SRP shim against a real server
SRP fails silently on any constant/hash mismatch, so gate the shim with a live
handshake: (1) pure User vs the real srp.Verifier, (2) a negative wrong-password
control, and (3) the full /srp/init -> /srp/verify HTTP flow against the real
server routes (backed by the real srp lib). All assert the phone operator
authenticates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:06:15 -07:00
leetcrypt ffb8ef52f0 feat(client): fall back to pure-Python SRP when srp is unimportable
Wrap `import srp` so a missing C-extension (e.g. Termux/aarch64) transparently
loads _srp_pure as srp. No call-site changes — the shim mirrors the srp API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:04:31 -07:00
leetcrypt a7b4840e9e feat(client): pure-Python SRP-6a fallback for Termux/aarch64
The `srp` PyPI package is a C extension with no reliable aarch64 wheel and
often fails to build under Termux. Vendor _srp_pure.py, a stdlib-only port of
srp._pysrp with rfc5054_enable() active, exposing the exact client surface
client.py uses (rfc5054_enable, SHA256, User with start_authentication /
process_challenge / verify_session / authenticated).

Constants (N/g/k), x/u derivation, M1 and H_AMK match srp byte-for-byte —
verified by a live handshake of this pure User against the real srp Verifier
(the same backend the server runs).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:04:21 -07:00
leetcrypt 88e5dfa0cb docs: spec for running a hack-house operator from Android/Termux
Phased plan (P0 dependency spike → P1 read-only presence → P2 sandbox drive →
P3 remote-provider autonomy → P4 persistence) for joining a room as a user from
Termux. Grounded in the real SRP/Fernet/ws client and the operate provider path.
Covers the srp aarch64 build risk + pure-Python SRP fallback, the cryptography
pin workaround, and Tailscale / reverse-SSH / port-forward reachability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 14:58:38 -07:00
leetcrypt 7afc38232b feat(cardex): population-curve rarity + interactive flip gallery
Rarity was inflating to 65% Legendary: the loop mass-produces ~70 near-clone
VMs that look near-identical on every axis, so no absolute PowerScore threshold
can create scarcity within that cluster. Fix (option C, hybrid):

- grade_curve() is now the primary rarity mechanism — a population quota
  (Leg 8 / Epic 15 / Rare 25 / Uncommon 27 / Common 25%) ranked by PowerScore
  with a deterministic label-seed tiebreak. Yields a clean 8/15/26/27/24%
  pyramid. Rebuilds each card's rarity-dependent name + flavor via _apply_rarity.
- Absolute RARITY_TIERS kept as a single-card (--label) fallback, recalibrated.
- Dead axes repaired: _pedigree now derives from author lineage (human/hh-loop/
  pulled/smoke) instead of a flat 2.0; _heft rescaled to a 512MB log reference
  so it spreads instead of pinning mid-size VMs at the cap.

cardimg: apply grade_curve to the gallery so rendered cards match hh-cardex,
plus the interactive 3D flip-card gallery (--flip: front dossier / back stats).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 09:51:14 -07:00
leetcrypt 2cfa388879 feat(cardex): image-search harvester + genre-diverse character library
Grow the hh card-art library so common VM types stop reusing one portrait.

- scripts/imgharvest.py: keyless DuckDuckGo image-search harvester — downloads
  many stills per character and runs them through charvest's crop/quality/dedup
  pipeline (static art crops far cleaner than motion-blurred video frames).
- scripts/harvest_roster.sh: batch roster (Pokemon/Digimon/Yu-Gi-Oh) sized to
  the live VM card types. Library now 37 characters / 99 portraits.
- charvest: top-anchor the head+shoulders crop for tall full-body art so faces
  are no longer clipped.

chardex matcher — stop same-type VMs collapsing onto a single face:
- fold a per-(VM,character) hash jitter INTO the score, not just a tie-break
- diminishing returns on extra type matches so a strong single-type face can
  compete with a dual-type one
- halve tier affinity and cap the keyword bonus
- stop-word rarity/boilerplate words that were polluting keyword overlap
Result: 20 distinct faces across the library. The one remaining cluster
(Sigilyph on the Psychic/Flying recon-scanner VMs) is coherent purpose-
matching — a scanner-themed bird on the scanner VMs — not a bug.

NOTE: assets/characters/ are third-party franchise IP with NO established reuse
license — quarantined, internal WIP only, do not distribute/merge/publish
(licensing record in docs/character-art-licensing.md).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-03 11:04:37 -07:00
leetcrypt 987985b203 feat(loop): retune hh-loop rubric to the cardex 5-axis PowerScore
Map the loop's "high value" guidance onto the measurable cardex model
(completeness/reusability/richness/pedigree/heft) and target Epic≥650 /
Legendary≥825. Stamp usage/setup/entrypoint + ≥4 provenance notes in the
manifest, drop --todo at done, and score each card before moving on.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 15:59:53 -07:00
leetcrypt 9c1ec55e0b feat(cardex): carry VM status onto cards + browser gallery
Add a status field to Card (from the registry entry) and render it as a
done/in-progress pill on the card art. Add `cardimg --gallery`: render every
VM as an inline-SVG card and lay them out in one self-contained index.html
grid (sorted by rarity, with a spread tally) for browser viewing / screenshot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 15:12:14 -07:00
leetcrypt f993a41cd7 feat(cardex): retune value model for spread + add card image renderer
Retune cardex against the live 24-VM library: the old rubric tied 11 VMs at
615 Rare (completeness/reusability maxed for any done+shareable VM, only heft
varied). Rebalance the five sub-score caps (richness 20→30 becomes the
discriminator via per-tag CAPABILITY_WEIGHT; heft log2-scaled) and recalibrate
the rarity thresholds. Library now spreads 2 Legendary / 3 Epic / 16 Rare /
2 Uncommon / 2 Common instead of a single fat Rare band.

Add cmd_chat/cardimg.py: render a Card as a collectible trading-card image.
Free + local-first — a hand-built SVG frame (rarity holo, type badges, 6 stat
bars, dex#, flavor) plus a deterministic procedural creature sigil that needs
no network, rasterized to PNG via local cairosvg/ImageMagick. Pluggable art
backends are opt-in: free hosted Pollinations flux, and key-gated paid
Stability / OpenAI / RunwayML. Every backend only fills the portrait window,
so a card always renders fully offline; failures fall back to the sigil.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 14:58:27 -07:00
leetcrypt c41d85f8ec feat(loop): security-10 brief queue — drove 10 deep VM-library builds
Curated 10-brief queue (crypto-ctf, osint-recon, web-fuzz, log-ir-triage,
pcap-forensics, password-audit, threat-intel, vuln-flask, net-scan,
yara-triage) used by hh-loop to build + publish 10 in-depth, verified,
self-describing security VMs to the host registry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 14:28:39 -07:00
leetcrypt dc8b68315e feat(loop): curated 10-brief deep-build queue for the VM library
Each brief encodes a meticulous-value contract (real software install + real
baked dataset + offline self-test + status:done manifest) so loop output scores
high on the cardex rubric instead of being shallow stubs. Security-focused and
distinct from the existing library.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 14:13:46 -07:00
leetcrypt e5fb820e63 feat(loop): headless sbx save/publish + hh-loop VM-library skill
Adds the autonomous /loop foundation: a headless `hack-house sbx save|publish`
subcommand so a non-TUI operator can persist a built VM to the host library
through the same canonical registry path the room UI uses (no schema drift).

- snapshot.rs: hoist register_saved_snapshot/publish_snapshot/oci_image_size out
  of app.rs into a shared module used by both the TUI and the new CLI.
- registry.rs: advisory cross-process lockfile (~/.hh/registry.lock, O_EXCL spin
  + stale-reclaim) around every read-modify-write, plus atomic temp+rename store,
  so concurrent /loop wave members can't clobber each other.
- main.rs: `Sbx { Save, Publish }` subcommand wired to the shared snapshot logic.
- skills/hh-loop: the loop doctrine — value rubric, adaptive 1-3 operator
  topology, visible-tmux-by-default run flow, --record logs/film, wave scaling,
  and safe `tmux -L hh-loop` teardown.

Proven end-to-end: built+verified a stdlib VM in a sandbox, headless
save+publish -> registry entry shareable:true with a portable tar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 14:06:19 -07:00
leetcrypt 354205cc16 feat(cardex): score saved VMs as collectible mythic-creature cards
Deterministic value model over the host VM registry: a weighted five-axis
rubric (completeness/reusability/richness/pedigree/heft) yields a PowerScore
0-1000, an absolute-threshold rarity tier, a 6-stat block, an elemental type
from tags, and a stable procedural name + dex number. Pure stdlib, no model,
no network — same VM always mints the same card. Image pipeline lands next.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 14:05:24 -07:00
leetcrypt 0798db7941 feat(operator): harness-mode operator — drive a room with any function-calling model (P2)
The operator brain was Claude-only: spawn launched the claude CLI and let its
own loop drive the room. This adds the other first-class brain — a native
tool-calling loop (cmd_chat/operator/harness.py) that runs ANY function-calling
Provider (cmd_chat.ai) against an OPERATOR_TOOLS schema wired to the existing
bridge control verbs (say/exec/write/get/keys/screen/watch/manifest). No CLI
install, no creds carry.

The harness is a driver, not a new side-effect surface: every tool handler sends
the same control-socket request the hh-bridge CLI already sends, so the bridge
keeps enforcing grant-before-drive, the sandbox blast radius, and the recursion
budget. Layer-1 capabilities come from CAPABILITIES.md (the same portable
contract), and a per-agent token ceiling is enforced as a hard stop. Providers
that can't function-call are refused with a clear message rather than degrading
to prose.

New `operate` verb joins via the existing daemon socket and runs the loop with a
--profile or --provider/--model brain. Chat's native loop is left untouched
(self-contained harness over the shared Provider core, not a risky extraction).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-28 23:23:17 -07:00
leetcrypt 2091bcefed feat(operator): portable CAPABILITIES.md as the single Layer-1 prompt (P4)
The operator's capabilities contract was Claude-only — delivered via the
hh-operator skill, which non-Claude runners cannot load. Extract it into a
portable CAPABILITIES.md and make compose_directive runner-aware: the claude
runner still loads its skill, every other runner gets the capabilities prompt
inlined. Same contract, no skill machinery required.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-28 23:15:56 -07:00
leetcrypt 09cea40494 feat(operator): runner registry — spawn any agent CLI, not just Claude (P3)
Generalize the three Claude-hardcoded spots in bootstrap (launch argv,
install plan, creds/config-dir) behind a Runner registry so a nested
operator can be spawned with any agent CLI. The Claude path stays the
default and byte-identical.

- bootstrap.Runner + RUNNERS{claude,codex,gemini,cmd} + get_runner/runner_present
- build_run_argv / install_plan / creds_source / plan_creds / child_env all
  take an optional `runner` (default claude → unchanged behaviour)
- claude validated end-to-end; codex/gemini are best-effort defaults
  overridable via $HH_<RUNNER>_INSTALL etc.; generic `cmd` runner reads a
  full launch template from $HH_OPERATOR_CMD ({directive} placeholder)
- bridge _op_spawn resolves req["runner"], rejects unknown; plan now carries
  runner/runner_present (was claude_present)
- CLI: `spawn --runner {claude|codex|gemini|cmd}`
- 7 new unit tests; full suite 132 passed

Note: compose_directive still emits the Claude-flavoured ("hh-operator
skill") directive — the portable Layer-1 prompt is P4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-28 23:11:41 -07:00
leetcrypt 300e33980a refactor(ai): hoist Provider core to shared cmd_chat/ai/ (P1)
Move providers.py + profiles.py from cmd_chat/agent/ to a shared
cmd_chat/ai/ package so the operator bridge and the /ai chat agent can
consume one model-agnostic Provider core (groundwork for harness-mode
operators). Pure refactor — no behaviour change.

- cmd_chat/ai/{providers,profiles}.py: the canonical modules (moved verbatim)
- cmd_chat/ai/__init__.py: re-exports the public API
- cmd_chat/agent/{providers,profiles}.py: thin back-compat shims re-exporting
  from cmd_chat.ai (keeps `from cmd_chat.agent.providers import …` working,
  e.g. hh/scripts/bench-native-harness.py)
- internal agent consumers (memory/bridge/__main__/__init__) point at cmd_chat.ai

125 tests pass; shim identity verified (re-exports are the same objects).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-28 22:57:29 -07:00
leetcrypt 7fbae3ca84 docs: add operator guide + model-agnostic operator plan
CLAUDE.md operating guide (room/operator/manifest/VM-registry map +
demo-toolkit pointers) and docs/model-agnostic-operator-plan.md — the
phased spec to let any function-calling model operate a room, not just
Claude (hoist Provider core, harness-mode operator, runner registry,
portable CAPABILITIES, role presets, MCP).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-28 22:53:47 -07:00
leetcrypt 12ae553ca1 feat(operator): VM marketplace — Phase A registry + Phase B trading
Phase A: host-global VM registry (~/.hh/registry.json) joining opaque
snapshots to their .hh-agent manifests. New src/registry.rs (serde_json),
one Entry per saved VM with cached purpose/status/todo scraped from the
live container at save time. Reconcile-on-read self-evicts pruned images.
/sbx browse TUI lister + operator-side `registry list|show` reader.

Phase B: trading/skill VMs. Entry gains shareable/tags/share_path +
publish/get/list_shareable helpers. New TUI verbs /sbx publish <label>
[tag...], /sbx catalog @user, /sbx pull @user <label>. Wire protocol adds
_sbx:catreq/catalog/pullreq frames (parse_sbx). Receiver auto-loads a
received hh-snap-*.tar, reads the in-image manifest, and self-registers
(kept shareable for re-trade). Reuses the existing E2E /send + ft.rs
streaming transport.

podman gotcha fixed: `podman load` prints registry-qualified
`localhost/hh-snap:<label>` vs docker's bare tag — parse_loaded_tag now
finds the hh-snap: marker anywhere in the line (unit-tested both shapes).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-27 15:21:53 -07:00
leetcrypt b998d28ebf fix(ai): repair native-harness bench — 3-tuple preflight + PTY-mirror stub
First functional bench run crashed 0/3 on two latent bench-only bugs:
(1) the Phase-4 token-count change made complete_with_tools return
(text, calls, usage), but the bench preflight still unpacked 2 values;
(2) make_bridge never stubbed _send_sbx_input, so the PTY-mirror
visibility feature hit ws=None and raised AttributeError on every tool
call. Stub it to capture mirrored lines for --verbose.

After the fix: 2/3 pass (write+read, script+run). mkdir+list still
fails — local-backend run_shell CWD divergence + 3B model churn, not a
working-memory regression.

Sprint: native-harness-working-memory (bench) — see also 46e5620, dc6317f, c83abbe

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-27 10:16:30 -07:00
leetcrypt 19d5b80a2c feat(operator): hh-agent manifest — VMs carry a self-describing handoff record
Add ICM-aligned agent-manifest tooling so a hack-house sandbox/VM can be
shared, traded, and resumed without a human briefing. manifest.py is a
dependency-light (stdlib + optional PyYAML) library + CLI that writes a
`.hh-agent/` bundle: manifest.yaml (canonical machine record) plus rendered
AGENT.md / last-state.md / summary.md / goals.yaml views.

Wire a `manifest` op into the operator bridge (push/pull/update) that moves the
bundle in and out of the *target sandbox* via the same exec path as write/get,
so the VM itself carries its purpose, goals, user intent, live state, and a
provenance chain — the unit of agent-to-agent work transmission. Pairs with
`spawn`: stamp → push → hand a child "load the .hh-agent manifest and continue."

Proven end-to-end against a real Kali podman container (push → on-disk verify →
update → pull-back) and covered by 6 new offline tests (34 green total).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 20:28:48 -07:00
leetcrypt 338e843e85 feat(operator): Phase 6 — relay-mode read (screen) + stop-condition engine (watch)
Completes the remote-drive triad for a broker-owned sandbox the operator can't
exec into directly: keys (in) → watch (wait) → screen (out).

- _sbx:data PTY-relay frames are absorbed into a capped rolling terminal buffer
  (not surfaced as chat); `screen` returns it ansi-stripped (or raw). sandbox.
  strip_ansi handles CSI/OSC/CR noise so output greps cleanly.
- `watch` is a formal stop-condition engine: blocks until a regex matches in the
  screen buffer or chat events, or an idle-quiescence window, or a hard timeout,
  then reports which fired — the autonomy loop's principled wait.
- Skill doctrine updated with the type→wait→read relay loop.

28 offline tests green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 20:13:08 -07:00
leetcrypt 60215bbdd4 feat(operator): Phase 5 — bootstrap + budgeted recursive spawn
Lets an operator stand up a nested Claude Code operator against another room —
a tree of operators — behind hard guardrails:

- Budget(depth, fanout, cost): depth/fanout are hard caps decremented on every
  descend(); a leaf (depth 0) or a spent level (fanout 0) refuses to spawn,
  stopping a runaway tree. Cost is carried down as a soft ceiling.
- detect_system / install_plan: decide install from what's present; prefer the
  documented npm package `@anthropic-ai/claude-code` or a $HH_CLAUDE_INSTALL
  override — never a guessed URL.
- Credentials are gated OFF by default: a child authenticates itself unless the
  operator explicitly passes allow_creds, and only its own creds, only to a path
  it was handed.
- compose_directive bakes the objective, stop conditions and inherited budget
  into the child's `claude -p` prompt so it self-limits.

`spawn` op + CLI verb default to a dry-run plan (inspect the tree before growing
it); `--go` launches detached on the host. `up`/`serve` take --depth/--fanout/
--cost. 24 offline tests green; dry-run plan verified live through the socket.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 13:38:42 -07:00
leetcrypt 669266dc55 feat(operator): Phase 3 — hh-operator skill doctrine + /loop autonomy
Reusable SKILL.md any Claude Code session can invoke to drive a hack-house
room via the hh-bridge CLI: join, the read --wait → decide → say operator loop,
the permission-gated sandbox drive (exec/write/get + shared-PTY keystrokes),
and an embedded keystroke cheat-sheet centred on the stop-vocabulary
(ctrl-c/ctrl-d/ctrl-z/ctrl-\, esc, pager q, vim :q) so every session knows how
to end a stuck program up-front.

/loop autonomy is folded in as the "indefinite watch" pattern with an explicit
stop-condition vocabulary — the long-poll primitive + persistent daemon from
Phase 1 already support it, so no extra machinery. Symlinked into
~/.claude/skills/ for global reuse.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 13:25:07 -07:00
leetcrypt 9c1e2921e9 feat(operator): Phase 2 — sandbox drive (exec/write/get) + keystroke relay
Co-located mode: the operator launches its own podman/docker container and
execs into it out-of-band (argv-identical to the native harness), exposed as
`sbx launch|status|down`, `exec`, `write`, `get`. When granted, it can also
drive the room's broker-owned container directly on the same host.

Relay mode primitive: `keys` injects raw bytes into the shared PTY via the
same `_sbx:input` frame a human driver emits — full keyboard control incl. the
stop-vocabulary (ctrl-c/ctrl-d/ctrl-z/ctrl-\, esc, arrows, pager q). A compact
per-session cheat-sheet (`sandbox.KEYS_HELP`, `keys --help-keys`) documents
what each inject does and how to end a stuck program, token-efficiently.

Gating: exec/write/get refuse a host (`local`) inherited from the room; the
room sandbox is only driven when `granted`. Keystrokes are inert until granted.

17 offline tests green; e2e verified against real podman (launch→exec→write→
get round-trip→teardown) and through the CLI socket.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 12:34:24 -07:00
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
171 changed files with 14163 additions and 608 deletions
+11
View File
@@ -23,6 +23,10 @@ err.log
# Sandbox save/load snapshots (large runtime tarballs, not source)
/hh/hh-snapshots/
# VM-library /loop artifacts — exported snapshot tars (repo-root, when `sbx`
# is run from here) + per-run logs. Generated; the VMs live in the host registry.
/hh-snapshots/
/.loop-runs/
# Out-of-tree experiments (not part of hack-house)
/experiments/
@@ -31,6 +35,13 @@ err.log
# Heavy / superseded demo-build kit (real demos live in video-toolkit)
/docs/demo/
# Native-harness benchmark harness + results — dev-internal test tooling, kept
# on disk but never pushed to origin (coupled to the local tmux+podman test rig)
/bench/
# Local-only planning/design docs (kept on disk, never pushed to origin)
/docs/plans/
# Project scratch
/i-try/
test_rsa.py
+102
View File
@@ -0,0 +1,102 @@
# hack-house — Claude operating guide
Encrypted collaborative terminal sessions with a summoned, shareable sandbox.
**Rust TUI client** (`hh/`) + **Python server & operator bridge** (`cmd_chat/`).
A Claude Code session can join a room as a **first-class member**: read/answer
humans and other AIs, and — once granted — drive the shared sandbox with full
shell + keystroke control. VMs carry a self-describing `.hh-agent` manifest so
work + memory hand off between agents (and humans), and saved VM states are
tradeable via a host-global registry.
## Operating a room — use the `hh-operator` skill
To enter/run/watch a room, bridge a Claude session into one, or autonomously
monitor and respond, **use the `hh-operator` skill** (`~/.claude/skills/hh-operator/`).
It documents the full doctrine: the daemon + `hh-bridge` CLI, the read→think→act
loop, sandbox drive (exec/write/get), the keystroke relay + stop-vocabulary, the
`watch` stop-condition engine, and the `.hh-agent` manifest for handoffs.
```bash
# Run from this repo root; pin the interpreter to the venv.
HH=".venv/bin/python -m cmd_chat.operator" # = the hh-bridge CLI
$HH up <host> <port> <name> --password <pw> --no-tls # join (spawns the daemon)
$HH read --wait --timeout 30 # long-poll for activity
$HH say "…" # chat into the room
$HH keys "make build" enter ; $HH watch --for 'PASS|FAIL' --in screen # drive
$HH manifest push --root /root --name <vm> --purpose … --objective … # handoff
$HH spawn "<objective incl. the full join cmd>" --room-host … --go --skip-permissions
```
## Mobile operator on Android / Termux — the phone is REACHABLE, use it
Spec: `docs/termux-operator.md`. The operator side is pure Python and runs on
the **Fairphone-6** in Termux — it can both **join** and **host** rooms (the SRP
shim now covers the server side too, so no Rust / no `srp` C-ext). You can drive
the device directly over SSH — so **verify reachability before ever telling the
user "this can only run on-device, paste it back."** That framing is wrong when
the phone answers SSH.
One canonical, hardened invocation (use this prefix everywhere — it sidesteps
the wrong-alias trap and never hangs):
```bash
PHONE='ssh -i ~/.ssh/phone-deploy -p 8022 -o ConnectTimeout=8 -o BatchMode=yes -o StrictHostKeyChecking=accept-new u0_a203@100.95.202.68'
$PHONE 'uname -m; python --version' # aarch64 / Python 3.13.x
```
- **Reachability gate — probe the PORT, not `tailscale status`:**
`timeout 8 bash -c 'cat </dev/null >/dev/tcp/100.95.202.68/8022' && echo REACHABLE || echo OFFLINE`.
Tailscale reports `fairphone-6` **`active` even when sshd is dead** — Android
kills backgrounded Termux, taking `sshd` with it. The tailnet line is NOT
proof; the TCP probe is. If OFFLINE, the fix is **on-device** (wake the phone,
relaunch Termux, `termux-wake-lock` + start `sshd`) — say so, don't retry a
dead port.
- **Trap:** the `~/.ssh/config` aliases `phone` / `phone-deploy` point at
`100.95.180.14` (`trilluminati`), a *different, usually-offline* phone —
`ssh phone` will time out. Use the Fairphone IP `100.95.202.68` (the `$PHONE`
prefix), not the alias.
- Termux facts: Python 3.13 (≥3.11); **no `/tmp`**`$TMPDIR` is
`$HOME/.claude-temp`; `$PREFIX=/data/data/com.termux/files/usr`; `rustc`/
`openssl` not installed by default.
- Deps that work: `pkg install python-cryptography` (avoid a rust build),
`pip install requests rich websockets`; **skip `srp`** — both client and
server fall back to `cmd_chat/client/_srp_pure` (pure-Python SRP-6a shim).
- Push-restricted work: copy this tree to the phone with tar-over-ssh rather
than a stale gitea clone —
`tar czf - cmd_chat scripts requirements-operator.txt | $PHONE 'tar xzf - -C ~/hh-mobile'`.
- Phase-0 gate: `python scripts/termux-preflight.py` on the phone → expect
`RESULT: PASS`. Operator files staged at `~/hh-mobile` on-device.
- **Proven live (2026-07-07):** phone JOINS a dev-box room; the phone HOSTS a
room the laptop joins (cross-impl SRP: laptop C-ext ↔ phone pure Verifier);
and the stdlib mobile web console (`python -m cmd_chat.operator web`) mirrors a
real room (terminal + keystroke drive + chat) in a mobile browser.
## Key components
| What | Where |
|------|-------|
| Operator bridge (daemon + CLI) | `cmd_chat/operator/__main__.py`, `bridge.py` |
| Agent manifest (`hh-agent/v1`) | `cmd_chat/operator/manifest.py``.hh-agent/` bundle library + CLI |
| Recursive spawn (Claude-spawns-Claude) | `cmd_chat/operator/bootstrap.py` (budgeted: `--depth`/`--fanout`) |
| Saved-VM registry / discovery | `hh/src/registry.rs``~/.hh/registry.json`; reader: `$HH registry list\|show` |
| Sandbox grammar (TUI) | `/sbx podman\|launch`, `/grant <name>`, `/sbx save`, `/sbx browse`, `/sbx publish`, `/sbx catalog @user`, `/sbx pull @user <label>` |
| Server | `cmd_chat.py serve <host> <port> --password <pw> [--no-tls]` |
| Rust client | `hh/target/debug/hack-house connect <host> <port> <name> --password <pw>` |
## Demo videos — use the `video-toolkit`
hack-house feature reels are produced with `~/coding/video-toolkit` (headless
asciinema-in-tmux capture via `bin/tmux-demo.py`, then a `video-forge` cut).
- **Flagship reel — three Claudes, one shared VM:** `scenarios/hh-vmhub.json`
(capture) + `scenarios/hh-vmhub-cut.json` (forge cut). A planner, builder, and
tester (each a real nested `claude -p`) collaborate to build **VMHub** — a
GitHub-for-VMs web app — live inside one shared sandbox, then save it to the VM
library. Role task-scripts: `scenarios/vmhub-{builder,tester}-task.sh`; staged
app under `scenarios/assets/vmhub/`.
- A reel must **show the product**: the Rust TUI room (roster, encrypted chat, the
shared sandbox F2 view) with Claude visibly operating as a member and
Claude-spawning-Claude — not just a console of CLI calls.
> Provisioning note: the demo summons a pre-baked `localhost/hh-dev:slim` (python
> 3.11) podman sandbox so VMHub (stdlib, zero pip deps) runs with no installs.
+29 -11
View File
@@ -32,11 +32,12 @@ Encrypted chat that runs in your terminal. You host the server, you control the
- **SRP authentication** — the password is never sent over the network (zero-knowledge proof)
- **Zero-knowledge server** — relays only ciphertext; cannot read messages, files, or terminal output
- **RAM only** — nothing persisted on the server; close it and history is gone
- **Shared sandbox** — summon a disposable `local` / `docker` / `multipass` box the whole room can watch and drive
- **Shared sandbox** — summon a disposable `local` / `docker` / `podman` / `multipass` box the whole room can watch and drive. Docker defaults to **Parrot OS Security** (`parrotsec/core`) and Podman to **Kali** (`kalilinux/kali-rolling`) — pentest distros out of the box; Podman is rootless & daemonless, so **no sudo** to launch
- **Snapshot save/load** — freeze a sandbox to a named snapshot and restore it later (`/sbx save` · `/sbx load` · `/sbx snaps`)
- **Local VirtualBox VMs** — `/sbx vms` detects VirtualBox and lists your VMs; `/sbx gui <vm>` opens a desktop VM locally for the room to gather around — per-user consent gate, with automatic resolution of VT-x conflicts (Docker Desktop / multipass)
- **Real permissions** — the host grants/revokes *drive* (keyboard) and *sudo* (VM superuser) per user; **stacking roster badges** show exactly who holds what, both in the clergy panel and inline on every chat message
- **Local-first AI agent** — `/ai start` summons an in-room AI that runs against *your own* [Ollama](https://ollama.com) (no API key, nothing leaves your machine); replies **stream token-by-token** with **in-RAM semantic recall** of the conversation for context; model-agnostic, addressed-only, end-to-end encrypted like every other client
- **AI that acts in the sandbox** — grant an agent *drive* and address it with `/ai <name> !<task>`; it works the shared box through a bounded, host-side **tool-calling loop** (run shell, write/read files, inspecting each result before the next step) and you watch its commands land live in the shared terminal. Ungranted, it stays **advisory-only** (tells you the commands, runs nothing); destructive commands are gated behind an explicit `/ai <name> confirm`
- **Encrypted file transfer** — `/send``/accept` with SHA-256 verification
- **TLS** — self-signed by default, or bring your own cert; `--no-tls` for local/Tailscale use
- **Themes** — seven switchable "vestments" (`crypt` default · `church` · `neon` · `blush` · `matrix` · `wraith` · `goldcrypt`), plus a live randomizer
@@ -153,16 +154,18 @@ Type to chat. Slash commands and keys:
| `/sendroom <path>` | Offer a file (or directory) to the whole room |
| `/accept` · `/reject` | Respond to a pending file offer |
| `/clear` | Wipe your chat scrollback (local only) |
| `/ai start [model\|profile]` | Summon a local AI agent (default `ollama/qwen2.5:3b`; a bare name is a `models.toml` profile) |
| `/ai start [model\|profile] [allow]` | Summon a local AI agent (default `ollama/qwen2.5:3b`; a bare name is a `models.toml` profile). `allow` auto-grants it sandbox drive at spawn |
| `/ai stop` | Dismiss the agent you summoned |
| `/ai <question>` | Ask the agent (`/ai <name> <question>` if several present) |
| `/ai <name> !<task>` | Have a *granted* agent act in the shared sandbox (advisory-only if it has no drive) |
| `/ai <name> confirm` | Approve a gated (destructive) command the agent proposed |
| `/ai list` | List the agents present (or hint to `/ai start` if none) |
| `/ai models` | Models the active agent can serve — or, with no agent, your local Ollama tags |
| `/sbx launch [local\|docker\|multipass] [image] [install]` | Summon the shared sandbox (`install` fetches a missing backend; `--start` boots a stopped Docker daemon) |
| `/sbx <local\|docker\|podman\|multipass> [image] [install]` | Summon the shared sandbox — the backend leads (`/sbx launch …` still works). Docker → **Parrot OS** (`parrotsec/core`), Podman → **Kali** (`kalilinux/kali-rolling`, rootless, no sudo). `install` fetches a missing backend; `docker --start` boots a stopped daemon |
| `/sbx stop` | Tear down the sandbox you host |
| `/sbx save [label]` · `/sbx load <label>` · `/sbx snaps` | Snapshot the sandbox, restore one, or list snapshots |
| `/sbx vms` | Detect VirtualBox and list local VMs |
| `/sbx launch vbox [new [name]]` | Open the local VirtualBox VM picker, or build a fresh VM via cloud-init |
| `/sbx vbox [new [name]]` | Open the local VirtualBox VM picker, or build a fresh VM via cloud-init |
| `/sbx gui <vm> [--install]` | Open a local VirtualBox desktop VM for the room (consent-gated) |
| `/drive` · `F2` | Take the shared shell (`Esc` releases) |
| `/grant <user>` · `/revoke <user>` | Owner: delegate/withdraw drive |
@@ -176,7 +179,7 @@ Type to chat. Slash commands and keys:
### The shared sandbox
Anyone in the room can summon a disposable Linux box with `/sbx launch`. The
Anyone in the room can summon a disposable Linux box with `/sbx <backend>`. The
person who summons it is the **owner/host**: their client runs the real PTY
locally and relays its output to everyone else as encrypted frames, so the
server only ever sees ciphertext (same trust model as chat).
@@ -184,10 +187,15 @@ server only ever sees ciphertext (same trust model as chat).
| Backend | Isolation | Notes |
|---|---|---|
| `local` | none | a `bash` shell on the host — fast, for dev/testing only |
| `docker` | container | `ubuntu:24.04` by default; `/sbx launch docker --start` boots the daemon (or run `hh/scripts/ensure-docker.sh`) |
| `docker` | container | **Parrot OS Security** (`parrotsec/core`) by default — swap `parrotsec/security` per-launch for the full pentest set; `/sbx docker --start` boots the daemon (or run `hh/scripts/ensure-docker.sh`) |
| `podman` | container | **Kali rolling** (`kalilinux/kali-rolling`) by default — **rootless & daemonless, no sudo to launch** (add `kali-linux-headless` for the toolset); `hh/scripts/ensure-podman.sh` installs it |
| `multipass` | full VM | `24.04` by default; strongest isolation, ~30 s to boot, the choice for real use |
Tear it down with `/sbx stop` (purges the VM/container).
The backend leads the command — `/sbx podman`, `/sbx docker`, `/sbx multipass`,
`/sbx local` (the older `/sbx launch <backend>` still works). Override the image
positionally, e.g. `/sbx docker parrotsec/security` or `/sbx podman ubuntu:24.04`.
Both container engines are Debian/apt-based, so the dev-toolchain bootstrap runs
unchanged. Tear it down with `/sbx stop` (purges the VM/container).
**Snapshots.** Freeze the current sandbox to a named checkpoint with `/sbx save
[label]`, list what you've stored with `/sbx snaps`, and restore one later with
@@ -256,6 +264,15 @@ when you quit). Pick a model at summon time with `/ai start <model>`.
- **Addressed-only.** The agent reads room traffic like any client but forwards
to the model *only* the messages that trigger it (`/ai …`) — no passive
surveillance, no cost or noise when idle.
- **Can drive the sandbox.** Grant an agent *drive* (`/grant <name>`, or summon it
pre-granted with `/ai start <name> allow`) and ask it to act with
`/ai <name> !<task>`. It works the shared box through a bounded **host-side
tool-calling loop** — run shell commands, write and read files — inspecting each
result before the next step, and you watch its commands appear live in the
shared terminal. Every command runs *inside the sandbox* (the container/VM is the
blast radius), capped in count and time. Without drive it stays **advisory-only**
(it spells out the commands, runs nothing). Destructive commands are blocked
pending an explicit `/ai <name> confirm`.
- **Model-agnostic.** Swap the backend without touching the client: bundled
adapters for `ollama` (default), `anthropic`, and any OpenAI-compatible
endpoint (OpenAI, Groq, Together, local vLLM…), plus a `module:Class` hook for
@@ -360,10 +377,11 @@ supports `-h` / `--help`** for full usage.
| Script | What it does |
|---|---|
| `ensure-docker.sh` | Install Docker and/or start its daemon (idempotent; `--check`/`--plan`/`--yes`). Invoked by `/sbx launch docker`. |
| `ensure-multipass.sh` | Install Multipass. Invoked by `/sbx launch multipass`. |
| `ensure-vbox.sh` | Install VirtualBox (warns on Secure Boot). Invoked by `/sbx launch virtualbox` and `/sbx gui`. |
| `vbox-new.sh` | Create + boot a fresh Ubuntu VirtualBox VM via cloud-init. Invoked by `/sbx launch virtualbox`. |
| `ensure-docker.sh` | Install Docker and/or start its daemon (idempotent; `--check`/`--plan`/`--yes`). Invoked by `/sbx docker`. |
| `ensure-podman.sh` | Install Podman (rootless; sets up subuid/subgid). Daemonless — nothing to start. Invoked by `/sbx podman`. |
| `ensure-multipass.sh` | Install Multipass. Invoked by `/sbx multipass`. |
| `ensure-vbox.sh` | Install VirtualBox (warns on Secure Boot). Invoked by `/sbx vbox` and `/sbx gui`. |
| `vbox-new.sh` | Create + boot a fresh Ubuntu VirtualBox VM via cloud-init. Invoked by `/sbx vbox new`. |
| `sandbox-bootstrap.sh` | Baseline dev-tool install piped into a Docker sandbox at provision time (package list from `sandbox-tools.json`). |
| `sandbox-tools.json` | Editable package list consumed by `sandbox-bootstrap.sh`. |
Binary file not shown.

After

Width:  |  Height:  |  Size: 682 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Some files were not shown because too many files have changed in this diff Show More