Commit graph

34 commits

Author SHA1 Message Date
chopratejas
2a91cbb4b4 refactor: single-wheel maturin build backend (fixes #355)
Eliminates the dual-package architecture that was the root cause of #355.
`pip install headroom-ai` now produces ONE wheel containing both the Python
source (headroom/*.py) and the compiled Rust extension (headroom/_core.so).
No more separate `headroom-core-py` package, no more chicken-and-egg with
PyPI publication, no more wheelhouse / PIP_FIND_LINKS / composite-action
plumbing in CI.

This is the canonical pattern used by cryptography, polars, ruff,
pydantic-core, and other Rust-as-core Python packages. Honors the
"Rust as core engine" direction.

## What changed

- pyproject.toml: `[build-system]` swapped from hatchling to maturin.
  `[tool.hatch.*]` deleted; `[tool.maturin]` added pointing at
  `crates/headroom-py/Cargo.toml` for the cdylib. `python-source = "."`
  picks up the root `headroom/` package directly (dashboard HTML
  templates and other non-Python files included automatically).
- crates/headroom-py/pyproject.toml: deleted. The crate is no longer a
  separate published package; its Cargo.toml stays as the cdylib build
  target invoked via `[tool.maturin] manifest-path`.
- crates/headroom-py/python/: deleted (placeholder layout for the old
  separate package).

## CI updates

- ci.yml: `test` / `test-extras` / `test-agno` jobs simplified — Rust
  toolchain set up before `pip install -e .` (which now invokes maturin
  via build-system). Removed the "build wheel + symlink .so" dance.
  `build` job swapped from `python -m build` (hatch) to
  `maturin build` + `maturin sdist`.
- release.yml: collapsed dual-package matrix into one. New `build-wheels`
  matrix produces cross-platform wheels for cp310/11/12/13 ×
  {linux x86_64, linux aarch64, macos x86_64, macos aarch64}. New
  `collect-dist` aggregator merges artifacts. publish-pypi consumes the
  merged dist.
- init-native-e2e.yml: dropped windows-latest from the matrix —
  upstream `esaxx-rs` (/MT) and `ort-sys` (/MD) link with conflicting
  MSVC C runtime libraries, so the Rust extension cannot build for
  win_amd64 today. Tracked as a follow-up; not a blocker for Linux+macOS.
- headroom-e2e-setup: composite action now sets up Rust toolchain +
  Swatinem/rust-cache before `pip install -e .[proxy]`.
- eval.yml, publish.yml, rust.yml: same pattern — rust toolchain before
  install. rust.yml's wheels job builds from root pyproject.toml (no
  more `-m crates/headroom-py/Cargo.toml`).
- e2e/init/Dockerfile, e2e/wrap/Dockerfile: install rust + maturin in
  the build stage; copy `crates/` + workspace `Cargo.toml/lock` so the
  install can build the extension. Dropped `HEADROOM_REQUIRE_RUST_CORE=false`
  from wrap-e2e — the image now ships the full Rust core.
- Dockerfile (main): simplified — no more Layer 2/3 dance with
  `headroom-core-py` install + symlink. Single `uv pip install` builds
  + installs everything.
- .devcontainer/Dockerfile: rust toolchain + libssl-dev + maturin
  added so `uv sync` builds the extension inside the devcontainer.

## Lockfile + script

- uv.lock: regenerated. No `headroom-core-py` entries remain.
- scripts/build_rust_extension.sh: simplified from a symlink-into-tree
  workaround to a thin wrapper around `pip install -e .`. The maturin
  build-backend handles placement automatically.

## Local validation (all green on macOS aarch64)

1. Clean venv `pip install -e .` → `from headroom._core import …` works.
2. `maturin build --release` → 13.8 MB wheel, 336 files including
   `headroom/_core.cpython-311-darwin.so` (32 MB cdylib) and
   `headroom/dashboard/templates/dashboard.html`.
3. `pip install <wheel>` in fresh venv → import works.
4. Wheel contents verified via `unzip -l`.
5. `pytest tests/test_transforms/test_diff_compressor.py` — 29 passed.
6. `pytest tests/test_relevance.py` — 30 passed.
7. `cargo build --workspace` + `cargo test --workspace` — all green.
8. `make ci-precheck` — 176 Python tests + Rust + commitlint green.

## Migration notes

Users on `pip install headroom-ai` get the Rust core automatically
(linux + macos wheels). sdist installs require rust toolchain available
locally — pip will build via maturin.

Closes #355
Supersedes #357 (workarounds-based fix abandoned in favor of
architectural fix)
2026-05-03 13:16:41 -07:00
chopratejas
00ab1ea74d fix: A0 — fail-loud rust core deployment smoke test
Production incident (Finding #2 of HEADROOM_PROXY_LOG_FINDINGS_2026_05_03.md):
on this customer's deployment the Rust extension `headroom._core` was
never installed into the runtime Docker image. Diff compression failed
54 times in a single day; "Optimization failed: ModuleNotFoundError" hit
379 times. The failure rate climbed every day and reached ~223/day on
2026-05-03 — effectively 100% of requests on the Rust path. Every Rust
PR we'd merged (MessageScorer, ICM, DiffCompressor, etc.) was providing
zero customer value because the module wasn't loadable at all.

Root cause: the Dockerfile builder stage installed Python deps and the
in-tree `headroom-ai` package but never ran `maturin build` for the
`headroom-py` crate, so the runtime image shipped without `_core.so`.
The Python proxy continued to start because the extension's absence is
caught and routed through Python-only fallbacks that either silently
no-op or raise per-request.

This change makes that mode impossible by default:

* `headroom.proxy.server._check_rust_core()` runs as the first step of
  the FastAPI lifespan. If the import fails it prints a structured
  diagnostic, logs `event=rust_core_missing`, and calls `sys.exit(78)`
  (sysexits.h `EX_CONFIG`). Process supervisors (systemd / k8s /
  docker) treat this as a deliberate config error and stop restart
  loops.
* `HEADROOM_REQUIRE_RUST_CORE=false` is the explicit opt-out for
  Python-only `pip install -e .` developer flows; lifespan logs
  `event=rust_core_disabled` and continues. Any other value (including
  unset) keeps the fail-loud default.
* `/health` now surfaces `rust_core: "loaded" | "disabled" | "missing"`
  (plus `rust_core_error` when non-loaded) so operators can alert on
  the degraded state rather than discovering it via a customer ticket.
* `scripts/build_rust_extension.sh` is the single dev-time path: build
  → install → import-verify with the same `hello()` marker the lifespan
  checks. Failures are loud at every step.
* `Makefile` exposes the script as `make verify-rust-core`.
* `Dockerfile` now installs `rustup` + `maturin`, builds the wheel from
  `crates/headroom-py`, force-installs it into site-packages, and runs
  the same `hello()` import-verify in the build image so a broken build
  fails the docker-build, not the next runtime restart.

Tests:
* `tests/test_rust_core_smoke.py` pins all four contracts:
  - `_core.hello()` returns `"headroom-core"`
  - missing extension + default env → `SystemExit(78)`
  - missing extension + opt-out env → lifespan starts, `/health`
    returns `rust_core: "disabled"` with the underlying error
  - present extension + default env → `("loaded", None)`

Per-finding-#2: ~/Desktop/HEADROOM_PROXY_LOG_FINDINGS_2026_05_03.md.
2026-05-02 17:52:37 -07:00
chopratejas
fa5fbfabf4 fix(rust): wire ICM compressor into Rust proxy on /v1/messages
Adds an opt-in compression interceptor that buffers Anthropic
/v1/messages requests, runs IntelligentContextManager over the
messages array, and forwards the (possibly trimmed) body upstream.
All other paths, methods, and content-types stay on the original
streaming passthrough — so existing operators see zero change.

Behaviour gates ALL must be true to buffer + compress:
  - --compression flag (or HEADROOM_PROXY_COMPRESSION=1)
  - method == POST
  - path == /v1/messages
  - Content-Type: application/json
  - ICM constructed successfully at startup

Falls through to streaming on any failure: parse, missing fields,
unknown model, body-too-large. Compression must never break a
request — that's the safety contract.

Model context windows come from a vendored LiteLLM snapshot at
crates/headroom-proxy/data/model_prices_and_context_window.json
parsed once into an OnceLock<HashMap>. Refresh via
scripts/refresh_model_limits.sh. Rationale documented inline:
hardcoded tables silently rot; LiteLLM is the canonical source
the entire LLM-tooling ecosystem relies on.

New tests:
  - 16 unit tests across compression::{anthropic, icm, model_limits}
  - 5 integration tests: off-passthrough, on-short-passthrough,
    on-oversized-trim, on-non-json-skip, on-non-llm-path-skip

Verification:
  - cargo test --workspace -> 884 passed, 0 failed
  - cargo clippy --workspace -- -D warnings -> clean
  - cargo fmt --check -> clean
2026-05-01 16:44:44 -07:00
chopratejas
35eaf8de7f fix(proxy): remove content-keyed TTL walker that conflated content with positional cache (#327)
The Anthropic token-mode handler walked past prefix_tracker.frozen_message_count
whenever an upcoming tool_result's content-hash matched comp_cache._stable_hashes
or should_defer_compression returned True. That conflated content equality with
positional cache membership.

Anthropic's prefix cache is POSITIONAL: bytes 0..K cached, anything past K is
fresh. _stable_hashes is content-keyed and grows unbounded. In long Claude Code
sessions where tool_result content rhymes across turns (repeated system prompts,
repeated file reads, repeated tool descriptions), the walker advanced
frozen_message_count to len(messages) on every turn and the pipeline produced
transforms_applied=[] on 73% of requests in user SvenMeyer's reported session
(headroom-stats-2026-05-01.json: 74 of 101 eligible requests "prefix_frozen") —
even after the prior fix in 44944fb. The 15 requests that did compress averaged
21%, proving compression itself works when reached.

Fix: delete the walker. The freeze boundary is now

    frozen_message_count = min(
        prefix_tracker.frozen_message_count,    # positional ground truth
        comp_cache.compute_frozen_count(messages),  # local cache lower bound
    )

compute_frozen_count's use of _stable_hashes can only LOWER the freeze via the
min clamp, never raise it past prefix_tracker's value. For any position in the
gap [compute_frozen_count, prefix_tracker.frozen_count], recompressing produces
byte-stable output (compression is deterministic on input content), so
Anthropic's prefix cache stays valid.

Cross-handler verification:
* OpenAI handler (proxy/handlers/openai.py:358-382) does not have this walker
  — uses only compute_frozen_count. Codex routes through OpenAI handler. Both
  unaffected.
* Streaming and non-streaming both invoke anthropic_pipeline.apply() before the
  upstream call. One fix covers both paths.
* Cache mode (is_cache_mode) takes the _extract_cache_stable_delta path and is
  independent of the walker. Unaffected.

Tests: six new regression tests lock down the post-fix invariants — clamp to
min(prefix_tracker, compute_frozen_count); fresh tool_result whose hash matches
old _stable_hashes entry is not frozen; frozen prefix byte-stable across the
pipeline; 10-turn session produces non-empty compression suffix every turn;
streaming and non-streaming compute identical frozen_message_count; OpenAI
handler never calls the walker functions. Plus scripts/smoke_issue_327.py
(gated by RUN_LIVE_API=1) drives a 10-turn conversation against
api.anthropic.com in both shapes (string + list-of-blocks) and both modes
(streaming + non-streaming).

ci-precheck clean. 191 tests pass.

Follow-ups (separate PRs):
* Fix _cache perpetually empty (anthropic.py result.messages != working_messages
  comparison rarely fires in token mode).
* Cap _stable_hashes with bounded LRU + 1h TTL — hygiene only after the freeze
  gate is removed.
* List-shape tool_result content gates at content_router.py:1975 and
  intelligent_context.py:657 (cluster A from the audit).
2026-05-01 12:04:28 -07:00
chopratejas
d6a00ee89c ci: fix smart_crusher branch CI failures + add make ci-precheck pre-push gate
Five gates broke on the 2026-04-27 push of the smart_crusher branch.
Each is fixed below; the second half adds a `make ci-precheck` target
(plus an installable git pre-push hook) so the same dance never happens
again.

Failures fixed:

1. cargo fmt — 22 files had formatting drift introduced over the
   stage 3c.1 work. `cargo fmt --all` reformatted them; no semantic
   changes. `cargo test --workspace` still green (388 + supporting).

2. wheels job (macOS x86_64) — `fastembed -> ort -> ort-sys` does not
   publish prebuilt ONNX Runtime binaries for `x86_64-apple-darwin`.
   Removed that target from `.github/workflows/rust.yml`'s wheels
   matrix. Apple Silicon (`aarch64-apple-darwin`) covers macOS
   distribution; Intel macOS users can build from source. The matrix
   now has 2 targets: linux x86_64 + macOS aarch64.

3. test-extras (relevance.py) — `tests/test_relevance.py::TestSmartCrusherIntegration`
   constructs a `SmartCrusher`, which hard-imports `headroom._core`
   since the python implementation was retired in stage 3c.1b. The
   test-extras job didn't build the rust extension. Added the same
   `maturin build + symlink` block the main `test` job uses.

4. smoke-test (eval.yml) — same root cause:
   `compression_only.evaluate_ccr_lossless` instantiates a SmartCrusher.
   Same fix: build the rust extension before the smoke test runs.

5. commitlint — three rules tripped:
   - `subject-case` rejects PascalCase identifiers in subjects, but
     the project deliberately names classes (SmartCrusher, HfTokenizer,
     ContentRouter, DiffCompressor) in commit subjects. Disabled.
   - `footer-leading-blank` is a warning that the wagoid action turns
     into a CI failure; lines like `Module: foo.rs` in our bodies
     match the conventional footer pattern and trip it. Disabled.
   - `type-enum` doesn't include `parity`, but the project ships
     parity-test infrastructure as its own concern (separate from
     `test:`); added `parity` to the allowed types.

Pre-push verification — the prevention half:

`make ci-precheck` runs all of the above CI gates locally:
- `ci-precheck-rust`: cargo fmt --check + clippy + test --workspace.
- `ci-precheck-python`: builds the rust extension via maturin, then
  runs the smart_crusher-affected python test files (185 tests across
  test_transforms/, test_relevance*, test_ccr, test_acceptance,
  test_critical_fixes, test_quality_retention).
- `ci-precheck-commitlint`: `npx commitlint --from origin/main --to
  HEAD` against the same config CI uses. Skipped silently if npx is
  not on PATH (install Node 18+ to enable).

`make install-git-hooks` (or `scripts/install-git-hooks.sh`) installs
a git pre-push hook that runs `make ci-precheck` automatically.
Bypass with `--no-verify` only when truly needed.

When new CI gates land in `.github/workflows/`, mirror them into a
`make ci-precheck-*` target. The Makefile is the local mirror of the
CI configuration; keeping them in sync is a load-bearing invariant.

Verification: `make ci-precheck` runs green on this commit.
2026-04-27 11:13:47 -07:00
chopratejas
f5f465418b feat(rust): retire python diff_compressor, ship rust-only via pyo3
The python `DiffCompressor` is replaced by a thin pyo3-backed shim that
delegates to `headroom._core.DiffCompressor`. There is no python
implementation and no env-var fallback — the wheel is a hard import.

Why now: opt-in defaults don't drive python retirement. Byte-equal
parity was already proven across 27 fixtures (stage 3a); keeping a
shadow python impl behind a flag is a permanent maintenance cost with
no operational benefit. Stage 3b deletes ~700 lines of python parser /
scorer / formatter code; the rust crate has its own coverage.

Surface preserved:
- `headroom.transforms.diff_compressor.DiffCompressor` — same class
  name, same `__init__`, same `compress(content, context)` shape.
  Returns python `DiffCompressionResult` dataclasses so call sites that
  destructure with `asdict()` work unchanged.
- `DiffCompressorConfig` and `DiffCompressionResult` dataclasses kept.
- Sidecar `compress_with_stats(...)` exposes the rust-only
  `DiffCompressorStats` (per-file hunk drops, context lines trimmed,
  file_mode normalizations) for observability.

Removed:
- Python parser / scorer / formatter (~700 lines).
- Internal `DiffHunk` / `DiffFile` parser dataclasses (rust crate has
  parallel coverage).
- 12 tests that probed `_parse_diff` / `_score_hunks` or the deleted
  parser dataclasses. The 29 public-API tests in `test_diff_compressor.py`
  remain and now exercise the rust backend through the same import path.
- `HEADROOM_DIFF_COMPRESSOR_BACKEND` env var — no longer meaningful.

Build:
- `scripts/build_rust_extension.sh` runs `maturin develop` and symlinks
  the built `.so` into `headroom/` so `import headroom._core` resolves
  past the in-tree package shadowing the maturin overlay.
- `.gitignore` excludes the symlinks and allowlists the build script.

Tests:
- 544 transforms pass; 33 rust-vs-fixture parity tests guard the pyo3
  bridge against regressions (`tests/test_transforms/test_diff_compressor_rust_parity.py`).
- Mypy clean.
2026-04-26 09:15:37 -07:00
chopratejas
4429a11166 Merge remote-tracking branch 'origin/main' into rust-rewrite
# Conflicts:
#	headroom/proxy/server.py
2026-04-25 13:01:37 -07:00
chopratejas
0414cb70e4 feat(rust): scaffold workspace + parity harness (phase-0)
Bootstrap the Rust port of Headroom. Additive only — no existing Python
code modified. Ships the workshop, not the widgets.

Layout
  Cargo.toml (workspace) + rust-toolchain.toml
  crates/headroom-core    — transform library, stub only
  crates/headroom-proxy   — axum binary, /healthz only
  crates/headroom-py      — PyO3 cdylib, exposes headroom._core.hello()
  crates/headroom-parity  — Rust-vs-Python oracle harness + parity-run CLI

Tooling
  Makefile: test, test-parity, bench, build-proxy, build-wheel, fmt, lint
  .github/workflows/rust.yml: test, wheels (linux/mac), audit, parity-nightly
  deny.toml for cargo-deny

Parity corpus
  tests/parity/recorder.py + scripts/record_fixtures.py
  125 recorded fixtures across 5 leaf transforms (ccr, tokenizer,
  log_compressor, diff_compressor, cache_aligner)

Docs
  RUST_DEV.md — developer setup and workspace reference
  docs/spec/022-rust-migration.md — migration plan and stage breakdown

.gitignore: whitelist scripts/record_fixtures.py; ignore target/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 13:39:48 -07:00
Garm
efd2ac1ca4 chore: renormalize line endings to LF
`.gitattributes` declares `*.py text eol=lf` and `*.sh text eol=lf`, but
74 files (73 .py, 1 .sh) are stored in the index with CRLF line endings,
violating that contract. Every macOS/Linux clone reports these files as
"modified" on fresh checkout because git's diff engine sees the stored
bytes don't match the attribute contract, even though the working tree
and index match byte-for-byte.

Running `git add --renormalize .` rewrites each affected blob so the
stored form matches the attribute declaration. No semantic changes —
every affected file's diff is "N insertions, N deletions" with inserts
and deletes being the same lines modulo line endings.

Follow-up commit adds `.git-blame-ignore-revs` so `git blame` / GitHub
blame skip this mechanical commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:33:30 +02:00
JerrettDavis
297f499e37 ci: retry workflow validation dry-runs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 13:20:13 -05:00
JerrettDavis
56f6307665 fix: support py310 version sync scripts
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 20:42:56 -05:00
JerrettDavis
b852460af9 chore: normalize line endings in init diffs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 20:17:14 -05:00
JerrettDavis
a278a7b0ba test: cover init install flows end to end
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 20:15:11 -05:00
JerrettDavis
c5d795c2af build: sync agent hook manifests to repo semver
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 20:03:14 -05:00
JerrettDavis
913435e03a fix: replace asyncio.timeout with 3.10-compat shim in repro harness
asyncio.timeout was added in Python 3.11, but the project supports >=3.10.
The test_repro_codex_replay_smoke test was crashing on Python 3.10 CI with
"AttributeError: module 'asyncio' has no attribute 'timeout'".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 13:41:02 -05:00
Tejas Chopra
cd11acc3c4
Merge pull request #197 from adryanev/fix/responses-retries-keep-compression
fix(proxy): Codex reconnect-storm resilience — bounded pre-upstream + WS session tracking + stage timings
2026-04-20 09:50:37 -07:00
Adryan Eka Vandra
b71b659e1b
fix(ci): restore repro harness test in dev installs
Add websockets to the dev extra so the repro harness smoke test can import
its websocket client dependency in the CI test matrix. Also apply ruff
formatting to the files the formatter check was rejecting so the 3.12 lint
job passes.
2026-04-20 22:12:01 +07:00
Adryan Eka Vandra
c438268d8a
feat(scripts): improve repro harness CLI contract and backoff
- Route --json output cleanly: JSON goes to stdout (machine-readable),
  human-readable summary goes to stderr. Without --json, the human
  summary stays on stdout as before. Makes piping the harness into
  jq / other tools trivial without losing operator visibility.
- Add distinct exit codes so CI and shell wrappers can branch on the
  failure class: 0 success, 1 crash, 2 proxy_unreachable, 3 livez
  threshold exceeded, 4 warmup failed, 130 SIGINT (already correct).
  Smoke test updated to assert EXIT_PROXY_UNREACHABLE instead of 1.
- Replace flat 50-250ms jitter in the Anthropic-client retry loop with
  exponential backoff + 50-150% jitter
  (base=250ms, max=5000ms, attempt counter). Matches the proxy's own
  jitter_delay_ms helper; inlined to keep the script free of proxy
  package imports.
2026-04-20 22:09:23 +07:00
Adryan Eka Vandra
c482d75a73
feat(scripts): add Codex proxy reconnect-storm repro harness
Reproducibly exercises the multi-agent Codex reconnect/retry storm
(origin §"Latest Correction") against a local proxy: concurrent Codex WS
sessions + parallel Anthropic /v1/messages replays, with /livez probed
every 250ms to detect event-loop starvation. Exits non-zero if /livez
p99 exceeds the configured threshold.

- scripts/repro_codex_replay.py — CLI harness (asyncio + websockets +
  httpx, no new pip deps).
- scripts/fixtures/{anthropic_replay_body,codex_response_create_frame}.json
  — hand-crafted, fully synthetic fixtures shaped like real traffic.
- scripts/README.md — "Reproducing the reconnect storm" section.
- tests/test_scripts/test_repro_codex_replay_smoke.py — FastAPI/uvicorn
  mock server; runs the harness end-to-end in < 3s.
- .gitignore — allowlist the new checked-in scripts (existing rule
  previously ignored everything under scripts/ except install.sh/.ps1).
2026-04-20 22:02:02 +07:00
JerrettDavis
d8c2ae88cd ci: validate release workflows with act
Add a workflow-validation CI job that installs actionlint and act,
checks the release and Docker workflows against checked-in event
fixtures, and shares the same validation script developers can run
locally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-18 15:38:10 -05:00
Tejas Chopra
cb7bc8c815
Merge pull request #191 from JerrettDavis/feat/filesystem-contract
feat: canonical HEADROOM_CONFIG_DIR and HEADROOM_WORKSPACE_DIR filesystem contract
2026-04-16 20:49:24 -07:00
JerrettDavis
4a87753713 feat(docker): forward HEADROOM_WORKSPACE_DIR and HEADROOM_CONFIG_DIR into containers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:19:25 -05:00
JerrettDavis
c24b1fa46f fix: harden release notes and semver
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 18:50:36 -05:00
JerrettDavis
bc49e6da16 fix: build TypeScript packages, add OIDC permission, sync SDK dep, scope GPR publish
- publish-pypi: add permissions: id-token: write for OIDC trusted publishing
- publish-npm: add npm run build before npm publish for both packages
- publish-github-packages: add npm run build, use --registry for GPR
- version-sync: add update_openclaw_package_json to sync headroom-ai dep range

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 22:09:05 -05:00
JerrettDavis
3d891264dd feat: add version alignment verification script 2026-04-15 19:37:55 -05:00
JerrettDavis
6299644b6d feat: add changelog generator from conventional commits
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 19:34:50 -05:00
JerrettDavis
93af60ac31 feat: add version synchronization script for multi-package releases 2026-04-15 19:29:15 -05:00
JerrettDavis
865bef2216 fix: harden persistent install wrappers and review gaps
Align Docker-native wrapper help and runtime behavior with the Python install contract, including persistent deployment metadata, baked install-image defaults, and explicit unsupported wrap targets.

Harden the Python persistent-install path with profile validation, safer provider-scope handling, Windows environment restoration, runtime parity improvements, and rollback-safe apply/update behavior.

Update README, Docker install docs, CI, and focused regressions to cover the Windows BOM failure, wrapper parity, compose coverage, and Docker-native wrap behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 18:03:21 -05:00
JerrettDavis
b325a06aae feat: harden persistent install wrappers
Tighten Docker-native bash and PowerShell wrapper validation for wrap and proxy flows, pin the bash wrapper to the install-time interpreter, clean up failed persistent container starts, and extend docs, CI, e2e, and native installer coverage for persistent Docker installs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 15:56:18 -05:00
JerrettDavis
4d9c281749 fix(cli): align native wrapper help and version flags
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 00:40:16 -05:00
JerrettDavis
a1dcda6bc4 feat(cli): support OpenClaw in Docker-native installs
Add host-managed OpenClaw wrap and unwrap flows to the Docker-native wrappers so the installed headroom script can configure the OpenClaw plugin on the host while keeping Headroom itself in Docker. Reuse hidden prepare-only hooks for OpenClaw config payloads, preserve existing plugin metadata on unwrap, and update the Docker-native and integration docs to reflect the supported flow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 00:04:15 -05:00
JerrettDavis
38b1483a76 feat(cli): add Docker-native install flow and parity docs
Add system-native install scripts and host wrappers for running Headroom from Docker while keeping wrapped tools on the host. Document the Docker-native path, add a complete CLI reference with help output and parity details, and add support for root help/version aliases and proxy env-based binding behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 23:27:24 -05:00
chopratejas
859a1a49ef Remove dedupe_telemetry.py from repo, add scripts/ to .gitignore
Private scripts with credentials should not be tracked in git.
2026-03-30 20:21:37 -07:00
chopratejas
8228f0edfb Fix streaming tool calls, compressed request bodies, beacon field names; bump to 0.5.13
- Fix LiteLLM stream_message: emit tool_use blocks from delta.tool_calls,
  set stop_reason from finish_reason (fixes silent MCP tool call failures)
- Fix _convert_messages_for_litellm: convert Anthropic tool_result/tool_use
  to OpenAI role=tool/tool_calls format (fixes 500 on tool round-trips)
- Fix proxy request body parsing: decompress zstd/gzip/deflate/brotli
  Content-Encoding before JSON decode (fixes Codex UnicodeDecodeError crash)
- Fix telemetry beacon field names to match /stats endpoint
  (tokens.total_before_compression, not tokens.original)
- Add dedupe_telemetry.py script for hourly Supabase row deduplication
2026-03-30 14:48:48 -07:00