Commit graph

1553 commits

Author SHA1 Message Date
Garm
5a2c484430 refactor(telemetry): centralize stack slug validation + cardinality cap
Addresses review feedback on PR #194: prior code validated X-Headroom-Stack
slugs differently at each ingress — the Prometheus counter capped length but
the env-var path and beacon didn't. Under a misconfigured or malicious client
sending arbitrary X-Headroom-Stack values, requests_by_stack could grow
unbounded in memory, in the Prometheus scrape, and in the JSONB telemetry
payload.

- New normalize_stack(raw) in headroom/telemetry/context.py: strips, lowercases,
  enforces ^[a-z][a-z0-9_]{0,63}$. Single chokepoint.
- detect_stack now routes HEADROOM_STACK and stats dominant-slug through it;
  invalid env values fall through to the agent-type path or default "proxy".
- PrometheusMetrics.record_stack routes through normalize_stack and rejects
  new slugs once the dict hits MAX_DISTINCT_STACKS (32); existing slugs still
  increment so valid callers aren't starved.
- 10 new unit tests covering normalize_stack charset/length/empty cases, the
  cardinality cap, and invalid-env fallback paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 18:42:41 +02:00
Garm
4a8d9812cf fix(ci): resolve mypy no-any-return and ruff formatting
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 18:42:41 +02:00
Garm
d4cd49af95 feat(telemetry): add headroom_stack and install_mode identity fields
Adds two orthogonal identity fields to the anonymous telemetry beacon so we
can segment usage by integration surface and deployment shape:

- headroom_stack: how Headroom is invoked (proxy, wrap_claude, wrap_codex,
  adapter_ts_openai, adapter_ts_anthropic, etc.). Resolved from HEADROOM_STACK
  env, HEADROOM_AGENT_TYPE fallback, or aggregated request-header counts.
- install_mode: how the proxy is deployed (wrapped / persistent / on_demand).
  Detected from HEADROOM_AGENT_TYPE plus DeploymentManifest lookup.

TS SDK adapters now tag every request with X-Headroom-Stack; a FastAPI
middleware buckets the counts and surfaces them via /stats so the beacon can
report requests_by_stack for mixed-integration sessions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:12:38 +02:00
JerrettDavis
799e1c3a30 fix: preserve empty-body commits in release bump parsing
Do not strip the raw git-log record before splitting on the field
separator, because commits with empty bodies lose their delimiter and get
dropped entirely. Add a deterministic unit test for empty-body parsing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 23:46:06 -05:00
JerrettDavis
8872b8be3b fix: take highest release bump across unreleased commits
Determine the release bump from all unreleased commits since the previous
release tag and apply the highest required semantic version increment.
This keeps feat commits at a minor bump unless a breaking change requires
major, even when later patch-level commits are present.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 23:38:29 -05:00
Tejas Chopra
4651f96c92
Merge pull request #190 from JerrettDavis/fix/release-semver-versioning
fix: run release versioning without package imports
2026-04-16 20:49:47 -07: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
1ef12953d4 test(paths): add adversarial backward-compat stress suite
Three scenarios lock the issue-175 additive guarantee:

1. Legacy-only user (HEADROOM_SAVINGS_PATH set, canonical unset) still
   gets the byte-for-byte legacy value.
2. Canonical-only user (HEADROOM_WORKSPACE_DIR set) sees every workspace
   bucket resource relocate with the correct filenames.
3. Both set: legacy per-resource env var wins over canonical root env
   for its own resource; other unreserved helpers still follow the new
   canonical root (orthogonality).

Plus a regression guard asserting all three legacy vars (savings, toin,
subscription) simultaneously beat the canonical workspace override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:59:47 -05:00
JerrettDavis
6410a1bdac test(paths): close coverage gaps for derived-only helpers
Every public helper in headroom/paths.py now has both a default-derived
test and a HEADROOM_WORKSPACE_DIR/CONFIG_DIR precedence test. Adds the
derived_workspace_helpers parametrized matrix plus explicit cases for
proxy_log_path, rtk_path, beacon_lock_path, plugin_*_dir, and both
ensure_* functions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:59:30 -05: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
edfe22a0c3 fix: run release versioning without package imports
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 19:17:53 -05:00
Tejas Chopra
01fa4aa3a1
Merge pull request #155 from vichu/fix/strip-accept-encoding-forwarded-headers
fix: strip accept-encoding from forwarded proxy headers
2026-04-16 17:17:22 -07:00
JerrettDavis
ebb9149fe9 fix: serialize release versioning
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 19:10:36 -05:00
JerrettDavis
4fb1935405 feat(paths): add canonical HEADROOM_CONFIG_DIR / HEADROOM_WORKSPACE_DIR module
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 18:50:50 -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
0e3e03f319 fix: restore semantic release versioning
Replace the inline release version math with a tested helper that normalizes legacy four-part tags and computes a single semantic version for packages and GitHub releases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 17:44:23 -05:00
chopratejas
d66fcd4ba9 Update number array tests for schema-preserving compression
Tests expected the old behavior where _crush_number_array prepended a
summary string into the array. The fix in 14415db moved stats to the
strategy string instead, keeping arrays homogeneously numeric. Update
3 tests to check strategy string instead of array[0].
2026-04-15 19:00:43 -07:00
Vish Ranganathan
9685e4c476 fix: strip accept-encoding from forwarded proxy headers
Edge proxies such as Cloudflare Workers inject accept-encoding values
(e.g. gzip, br, zstd) into every outbound request. When Headroom
forwarded these headers unchanged to OpenAI or Anthropic, the upstream
could respond with Brotli-encoded content. Because httpx does not
decompress brotli without the optional brotli package, the raw
compressed bytes reached the JSON parser, causing a UnicodeDecodeError
and a 502 response to the client.

Fix: pop accept-encoding before forwarding so httpx negotiates its own
encoding independently. Applied consistently across all four request
header construction sites:

- openai.py: chat completions handler
- openai.py: Responses API handler
- openai.py: generic passthrough handler
- anthropic.py: main messages handler (CCR continuation already had the strip)

Closes #135
2026-04-15 11:40:11 -07:00
chopratejas
14415dbbb5 Fix SmartCrusher bugs: schema violation, race condition, thread safety, recursion
- Number array compression no longer mixes types (string summary was
  prepended to numeric array, violating schema-preserving guarantee).
  Statistics now go in the strategy string instead.
- Replace instance-level _current_field_semantics with threading.local()
  to prevent cross-thread contamination in concurrent crushes.
- Add lock to module-level _within_compressor lazy init (was unprotected).
- Add _MAX_PROCESS_DEPTH=50 guard to _process_value to prevent
  RecursionError on deeply nested JSON.
- Remove dead expression (unused stats.max_val - stats.min_val).
- Fix all UP038 isinstance(x, (A, B)) -> isinstance(x, A | B) across file.
- Add 11 regression tests covering all fixes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 21:16:13 -07:00
chopratejas
89061fd430 Fix CI lint errors and test failures
- test_memory_sync.py: remove unused imports (asyncio, MagicMock,
  AgentMemory, AgentMemoryAdapter, SyncResult), fix import sorting
- test_ws_memory_relay.py: remove unused pytest import and unused
  output_index variable, fix import sorting
- test_wrap_copilot.py: provide dummy API keys in test env — the
  BYOK validation added in 7a7b8b6 requires ANTHROPIC_API_KEY or
  OPENAI_API_KEY to be set
- test_package_init_lazy.py: stop hardcoding version string that
  breaks on every bump; assert it's a non-empty string instead

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:23:06 -07:00
chopratejas
7c91fe1e4b Fix headroom learn failing on project paths with underscores (#159)
_component_tokenizations only split on `-` and `.`, so directory names
like `my_project` could never be reconstructed from the dash-encoded
slug. Add `_` as a separator so the greedy decoder matches snake_case
directory names correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:59:49 -07:00
chopratejas
b50136904c Make KompressCompressor model-configurable: model_id, chunk_words, score_threshold
KompressConfig now accepts model_id, chunk_words, and score_threshold so
domain-specific models (e.g. kompress-finance with 50-word chunks) can be
used without forking the compressor. Model cache is keyed by model_id,
allowing multiple models to coexist. All defaults match prior behavior.

Also fix mypy errors in memory/sync.py from recent merge.
2026-04-14 13:19:35 -07:00
Tejas Chopra
82301bbf76 Add cross-agent memory interoperability via MCP, sync engine, and atomic facts
Memory saved in one agent (Codex, Claude Code, Aider) is now accessible
from any other agent through a shared headroom DB. Three integration layers:

MCP Server (headroom.memory.mcp_server):
- stdio MCP server that Codex/Claude discover natively via config.toml
- memory_search with supersession filtering (only active memories returned)
- memory_save accepts atomic facts array — each fact stored/indexed individually
- Auto-supersession: new facts that match existing ones (≥0.70 similarity)
  retire the old entry via the supersedes/superseded_by lineage chain
- ONNX embedder pre-loaded at startup (no cold-start on first query)
- HuggingFace offline mode eliminates network latency on startup

Sync Engine (headroom.memory.sync):
- Bidirectional sync: DB ↔ agent-native memory files
- Pluggable adapters: ClaudeCodeAdapter (frontmatter .md files + MEMORY.md index),
  CodexAdapter (AGENTS.md sections)
- Fast no-op: fingerprint comparison skips sync when nothing changed (<5ms)
- Content-hash dedup prevents duplicate memories across agents
- Lineage metadata: source_agent, source_file, content_hash, synced_at
- Anti-echo: memories imported from an agent are not re-exported to that agent
- CLI entry point: python -m headroom.memory.sync --agent claude|codex

Wrap CLI integration:
- `wrap codex --memory`: registers MCP server + AGENTS.md guidance + syncs
  Claude memories into DB for MCP search
- `wrap claude --memory`: bidirectional sync at startup (DB ↔ Claude files)
- MCP config re-injected after provider config to survive file rewrite
- Cross-platform: Windows path handling in TOML configs and path sanitization

Proxy improvements:
- Responses API: tool format conversion (Chat Completions → Responses API)
- Responses API: memory tool calls handled with proper continuation
- WebSocket: buffer-then-decide relay suppresses memory tool events from
  Codex, executes them transparently, relays only the final answer
- memory_handler: supports Responses API function_call format (call_id,
  top-level arguments, output[] extraction)
- HNSW vector index now persists to disk via auto_save + save_path

Tests: 37 new tests covering WS relay event suppression, sync import/export,
bidirectional sync, idempotency, fast no-op, lineage, cross-agent interop
2026-04-13 23:38:44 -07:00
Tejas Chopra
c2451296df Add memory support for Codex via OpenAI Responses API handler
Inject memory context into instructions and memory tools into Responses
API requests, and handle memory tool calls in responses.
2026-04-13 16:18:30 -07:00
Tejas Chopra
1b5cc9bbac Fix --memory flag silently dropped when proxy already running
_ensure_proxy() now queries the running proxy's /health config to
detect feature mismatches. When --memory, --learn, or --code-graph
is requested but not enabled on the running proxy, it merges flags
and restarts the proxy with the upgraded configuration.

- Expose memory, learn, code_graph, pid in /health config response
- Add _query_proxy_config() and _kill_proxy_by_pid() helpers
- Refactor _detect_running_proxy_backend() to use _query_proxy_config()
2026-04-13 14:36:30 -07:00
SwiftWing21
951d021f97 feat(kompress): add compress_batch with device-aware routing
Implements compress_batch() for issue #151. Compresses N texts with
batched forward passes on GPU and falls back to sequential compress()
on CPU where batching doesn't help.

Measured performance (RTX 3080 Ti, 1000-word / ~6K-char inputs):

  GPU (PyTorch + CUDA):
    N=1:  2.68x speedup (multi-chunk text batches within single call)
    N=5:  2.75x speedup
    N=12: 2.49x speedup

  CPU (ONNX): fallback to sequential — parity with compress() in loop

ONNX Runtime's CPU execution provider does not parallelize across the
batch dimension for this model architecture; verified across default,
physical-cores-only, and single-thread configurations. The fallback
keeps the API useful while that limitation exists.

Features:
- Per-item target_ratio: scalar applies to all, list allows per-text
- Input order preserved in output
- Passthrough parity with compress() on short texts / errors
- Configurable batch_size (default 32)

Tests: 8 new (TestKompressCompressorBatch), 21 total pass.

Closes #151
2026-04-12 21:51:30 -07:00
Tejas Chopra
54ae7b9928
Merge pull request #147 from JerrettDavis/feat/anthropic-usage-insights
feat: AI quota & rate-limit tracking — Anthropic, OpenAI Codex, and GitHub Copilot
2026-04-12 10:54:22 -07:00
JerrettDavis
c03b9cca05 feat: pluggable QuotaTracker ABC + QuotaTrackerRegistry
Introduce headroom/subscription/base.py with:
- QuotaTracker ABC: key, label, is_available(), start(), stop(), get_stats()
- QuotaTrackerRegistry: register(), start_all(), stop_all(), get_all_stats()
- get_quota_registry() / reset_quota_registry() process-global singletons

Adapt all three existing trackers to implement QuotaTracker:
- SubscriptionTracker (Anthropic) — adds enabled param, key/label, get_stats(), is_available()
- CodexRateLimitState (Codex) — passive tracker, no-op start/stop already inherited
- _CopilotQuotaTracker (Copilot) — is_available() gates on discover_github_token()

Refactor server.py to use the registry:
- Single registry.start_all() / stop_all() replaces ad-hoc per-tracker calls
- /stats endpoint uses registry.get_all_stats() (dict-spread, keys unchanged)
- New /quota endpoint: unified view of all providers
- Removes _get_codex_rate_limit_stats() and _get_copilot_quota_stats() helpers

Add 19 tests in tests/test_quota_registry.py covering:
- registration, duplicate rejection, get/trackers snapshot
- start_all skips unavailable, stop_all continues on error
- get_all_stats excludes unavailable and None-returning trackers
- global singleton + reset isolation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-12 00:58:01 -05:00
JerrettDavis
a9f53cc104 style: apply ruff format to copilot_quota and test_copilot_quota
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-12 00:08:56 -05:00
JerrettDavis
d11feea511 feat: add GitHub Copilot monthly quota tracking
Adds passive tracking of GitHub Copilot per-category monthly quotas
(chat, completions, premium_interactions) via GET /copilot_internal/user
on api.github.com.

Token discovery checks environment variables in priority order:
  GITHUB_COPILOT_GITHUB_TOKEN > GITHUB_TOKEN >
  COPILOT_GITHUB_TOKEN > GITHUB_COPILOT_API_TOKEN

- headroom/subscription/copilot_quota.py: CopilotQuotaCategory,
  CopilotQuotaSnapshot, CopilotQuotaState, parse_copilot_quota(),
  discover_github_token(), _CopilotQuotaTracker singleton (60s poll)
- headroom/subscription/__init__.py: export new symbols
- headroom/proxy/server.py: start/stop tracker in lifecycle;
  _get_copilot_quota_stats(); copilot_quota key in /stats
- dashboard.html: GitHub Copilot Quota panel with per-category
  progress bars, remaining counts, overage alerts, reset date
- tests/test_copilot_quota.py: 25 unit tests (all pass)
- docs/screenshots/subscription_window_active.png: updated to show
  all three panels (Anthropic + Codex + GitHub Copilot)

Env vars sourced from @github/copilot v1.0.24 app.js (k6e array).
API schema sourced from copilot_internal/user via eBo/QRt zod schemas.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 23:58:44 -05:00
JerrettDavis
a3abbec4a3 feat: add OpenAI Codex rate-limit window tracking
Passively capture x-codex-* response headers from proxied Codex API
calls and surface them in /stats and the dashboard.

Unlike Anthropic subscription window tracking (which polls a dedicated
OAuth endpoint), Codex embeds rate-limit data directly in every API
response header — no polling, no new credentials needed.

Changes:
- headroom/subscription/codex_rate_limits.py: new module with
  CodexRateLimitWindow, CodexCreditsSnapshot, CodexRateLimitSnapshot
  data models and a thread-safe CodexRateLimitState singleton;
  parse_codex_rate_limits() parses x-codex-primary/secondary-used-percent,
  window-minutes, reset-at, credits, limit-name, and promo-message headers
- headroom/subscription/__init__.py: re-export new public symbols
- headroom/proxy/handlers/openai.py: call
  get_codex_rate_limit_state().update_from_headers() after each
  proxied /v1/chat/completions and /v1/responses response
- headroom/proxy/server.py: add codex_rate_limits key to /stats
  via _get_codex_rate_limit_stats() helper
- headroom/dashboard/templates/dashboard.html: new OpenAI Codex
  Rate-Limit Window panel (primary + secondary progress bars, credits
  balance, limit name, reset countdown); hidden when no data
- tests/test_codex_rate_limits.py: 25 unit tests covering parsing,
  window labels, reset time, credits, state updates
- docs/screenshots/subscription_window_active.png: updated screenshot
  showing both Anthropic and Codex panels in the real dashboard

Header schema (from codex-rs/codex-api/src/rate_limits.rs):
  x-codex-primary-used-percent / x-codex-primary-window-minutes /
  x-codex-primary-reset-at  (and secondary- variants)
  x-codex-credits-has-credits / -unlimited / -balance
  x-codex-limit-name / x-codex-promo-message

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 23:34:19 -05:00
JerrettDavis
357fe131c9 fix: ruff lint in test_subscription_tracker (unused imports, ordering)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 22:06:20 -05:00
JerrettDavis
4329213e57 feat: add Anthropic Claude Code subscription window tracking
- New headroom/subscription/ package:
  - models.py: RateLimitWindow, ExtraUsage (cents->USD), SubscriptionSnapshot
    with per-model 7d windows (opus/sonnet), HeadroomContribution,
    WindowDiscrepancy, SubscriptionState
  - client.py: async httpx client for GET /api/oauth/usage with OAuth token
    resolution (env var -> credentials file -> live proxy header)
  - tracker.py: background polling singleton (asyncio Task), notify_active(),
    update_contribution(), anomaly detection (surge pricing, cache miss),
    atomic persistence, OTEL callback
  - session_tracking.py: JSONL transcript reader, Sonnet-normalised model
    weights, compute_window_tokens() for 5h/7d window boundaries
  - __init__.py: clean public re-exports

- Integration:
  - proxy/models.py: subscription_tracking_enabled, poll_interval_s,
    active_window_s config fields
  - proxy/server.py: tracker startup/shutdown, /subscription-window endpoint,
    subscription_window key in /stats
  - proxy/handlers/anthropic.py: OAuth Bearer detection, notify_active() +
    update_contribution() per request
  - observability/metrics.py: 5 observable gauges (5h/7d utilisation %,
    5h/7d seconds-to-reset, overage USD) using Observation type
  - dashboard/templates/dashboard.html: subscription window panel with 5h/7d
    progress bars, countdown, overage, Headroom contribution bar + anomaly
    alerts

- Tests: 33 new unit tests in tests/test_subscription_tracker.py (all pass)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 21:53:44 -05:00
JerrettDavis
5884f7fb41 fix: restore Windows mypy compatibility
Make Unix-only file locking imports type-safe on Windows, tighten beacon lock cleanup, and fix the remaining exposed typing issues so the repository's mypy check passes cleanly on Windows again.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 18:40:53 -05:00
JerrettDavis
bd242fc62d test: expand persistent install coverage
Add focused regression coverage for install, runtime, provider, state, health, supervisor, and persistent wrap flows so the new persistent deployment surfaces are exercised more thoroughly in CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 18:24: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
21896a095c feat: add persistent install lifecycle management
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 13:47:05 -05:00
chopratejas
b0ed0de37c Fix tests: update default assertions for disabled CodeCompressor 2026-04-11 10:40:44 -07:00
Tejas Chopra
a245234f5a
Merge pull request #130 from Gyeonghun-Park/feat/cli-llm-backend
feat(learn): add CLI-based LLM backends for keyless headroom learn
2026-04-11 09:05:26 -07:00
Tejas Chopra
12adb8ebd2
Merge pull request #136 from JerrettDavis/jd/openclaw-launch-fix
fix: lazy-load Headroom proxy startup for OpenClaw
2026-04-11 09:05:13 -07:00
Tejas Chopra
9f124c99ff
Merge pull request #139 from JerrettDavis/feat/docker-native-cli
feat(cli): add Docker-native install flow and parity docs
2026-04-11 09:05:00 -07:00
JerrettDavis
9fa1763087 feat: add copilot CLI wrap support
Add headroom wrap copilot with backend-aware provider routing, health metadata for running proxy detection, focused Copilot tests, and docs updates across the main integration surfaces.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-11 01:20:17 -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
JerrettDavis
f63cfe7a1e fix: lazy-load proxy startup dependencies
Keep the default Headroom proxy startup path lightweight so OpenClaw can launch it reliably on Windows. This defers heavyweight provider, cache, transform, pricing, and detector imports until they are actually needed, adds a lightweight version module, and keeps the OpenClaw launcher on the configured Python path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 20:33:46 -05:00
Gyeonghun Park
da4b971128 Merge remote-tracking branch 'origin/main' into feat/cli-llm-backend
# Conflicts:
#	docs/learn.md
2026-04-11 10:05:45 +09:00
JerrettDavis
a1beb08d53 feat: add reproducible devcontainers
Add a default devcontainer and a compose-backed memory-stack profile, validate them in CI, and document the contributor workflow.

Also lock the memory-stack dependencies, pin related container tooling, and sync the latest healthcheck shutdown fix for stubbed memory handlers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 12:58:35 -05:00
JerrettDavis
7a18b71c0d feat: add proxy healthcheck endpoints
Add /livez and /readyz, keep /health backward-compatible, and wire readiness into Docker artifacts and docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 12:23:23 -05:00
Gyeonghun Park
c3cf022886 fix(learn): handle FileNotFoundError when CLI tool is not installed
When --model codex-cli is used but codex is not in PATH,
subprocess.run raises FileNotFoundError. Catch it and raise
a clear RuntimeError with guidance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 22:11:19 +09:00