One idea per sentence, short sentences, active voice, one word one meaning,
imperative instructions, no ambiguous pronouns. Caveman cuts filler; the
controlled-English rules keep what makes meaning unambiguous.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2f7Lf7G9erA7YNn5SXPEj
Live-tested Codex 0.149 through the proxy and found four defects:
- proxy: custom_tool_call_output.output is an ARRAY of input_text parts on
Codex 0.149+. The Responses extractor accepted only the string form, so
every real Codex tool turn had an empty live zone and the subscription
route compressed nothing (75-116 KB requests, zero savings). The captured
real request now compresses; regression test pins the array shape.
- proxy: the /chatgpt route never recorded compression eligibility, so the
CLI told every Codex subscription user "your agent never reached the
compression layer" even when routing worked.
- cli: codexNativeConfig ran the legacy table strippers before removing its
own marker blocks; the stripper swallowed the tables end marker, so the
block enable had just written failed its own re-parse as "corrupted" on
the next wrap and doctor reported drift forever. Markers come off first.
- cli: nativeHooksDocument matched caveman hook entries by exact command
string, so a binary path change (upgrade, moved install) appended a new
hook set per event instead of replacing it. Match on managed identity.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2f7Lf7G9erA7YNn5SXPEj
The compile test hardcoded kilo@7.5.6 and the drift test hunted the live
registry for a prerelease pin, so the nightly-drift bump in 18a8c09 turned
main red. Read the pin from agents/profiles at test time and synthesize the
prerelease registry the drift test needs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2f7Lf7G9erA7YNn5SXPEj
- cache original base URLs per model, not per provider, so a same-provider
model with its own endpoint stays direct (#973)
- proxy publishes its named compat mounts in the run-state file; the pi
extension routes a custom provider through /w/pi/compat/<name> only when a
mount with that name and a matching host is published (#952, #970)
- hostOf compares host:port so loopback relays on different ports differ
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2f7Lf7G9erA7YNn5SXPEj
Follow-ups on the #936 merge: npm run package now emits the Firefox zip
too; the Firefox e2e (which fetches web-ext through npx) is no longer part
of test:all; strict_min_version drops to 140.0, the desktop floor for
data_collection_permissions (no gecko_android target is declared).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2f7Lf7G9erA7YNn5SXPEj
shrink-hook (PreToolUse), the memory recall hook (UserPromptSubmit) and
native-hook are invoked directly by hosts and read stdin to EOF. Under the
Windows pipe implementation the host's close can lag arbitrarily (#729/#833,
#949), so a hook with its work done sat until the host's timeout. Same fix
shape as native-hook-fast and the Go bridge: resolve on the first complete
JSON object, keep the 2 MiB cap, unref stdin so the process can exit.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2f7Lf7G9erA7YNn5SXPEj
Merges PR #951: the UserPromptSubmit reminder now restates the concrete
style rules for the active level instead of a bare status banner, so the
model holds caveman style after competing mid-session instructions.
Checksum manifest regenerated after the #954 merge. Fixes#303.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2f7Lf7G9erA7YNn5SXPEj
The JS adapter already returns on the first complete JSON object because the Windows pipe close can lag arbitrarily (#729/#833), but the Go bridge in front of it still blocked on EOF, so under 'codex exec' on Windows the SessionStart hook never reached the daemon spawn. Refs #949.
* fix(stats): pin number formatting to en-US
toLocaleString() renders per host locale, so the suite's "1,250"
expectations fail on non-en-US hosts (observed de-DE: 1.234 vs 1,234).
fmt() pins the locale for all formatted display; 52/52 incl. a de-DE
regression test.
* chore(hooks): refresh integrity manifest
* fix(stats): clean merge-conflict debris from checksums.sha256
Lines 6-10 held an unresolved conflict block (stray HEAD/edd4205 markers and a
duplicated caveman-stats.js line) flagged in review; the integrity regex
silently skipped it, but a future manual edit could trip over the duplicate.
Rewrote the manifest to exactly one canonical line per file; all hashes
re-verified against the working tree (caveman-stats.js 8650f63... is the
intended refresh on this branch).
---------
Co-authored-by: Alot1z <alot1z@users.noreply.github.com>
* fix(pi): route only default provider endpoints
The extension routed a provider by name. A provider with the name openai
that points at a local relay or at Azure got the gateway route, and the
gateway sent the request to api.openai.com. The local relay got no
request, and the request failed with a 401 error.
The extension now routes a provider only when the host of its original
base URL is the upstream host of the proxy route. The router caches the
original base URL before the first override, because the registry
reports the gateway route after an override. A mismatch keeps direct
mode and names the endpoint in the pass-through notice. The same rule
keeps a moved opencode-go endpoint away from the stale built-in mount.
The stub provider fixture now declares three providers: openai on the
real host, opencode-go on the real host, and anthropic on a dead
loopback port. New integration tests drive an opencode-go model through
the compat mount path and keep a custom-endpoint provider direct.
Fixes#970Fixes#971
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
* docs(pi): state that pi keeps provider overrides in memory
The review of #972 asked about a stale override from an earlier pi
process. Pi keeps extension provider overrides in memory only, so a new
process starts from the original base URL. The comment on the originals
map now records this fact.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
---------
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
Co-authored-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* fix(proxy): send x-api-key on compat /v1/messages
A named compat mount mapped every credential to Authorization: Bearer.
The Anthropic client in Pi sends x-api-key to /v1/messages. OpenCode Go
rejects a Bearer header on that path with 401 "Missing API key.". Thus
the opencode-go anthropic-messages models failed through the proxy.
The named adapter now maps the credential by the wire protocol of the
path. A request to /compat/<name>/v1/messages gets x-api-key and a
default anthropic-version. Every other path keeps Bearer. The gateway
env fallback puts the key in the header that the adapter emitted, so
the upstream never gets two credentials.
New tests cover the header mapping at the adapter, the gateway fallback,
and the full standalone handler with the /w/pi prefix. A test also
shows that a user compat.opencode-go entry replaces the built-in
upstream on the adapter path. The docs describe the header rule.
Fixes#967Fixes#968
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
* docs(proxy): add the compat header rule to AGENTS.md
proxy/AGENTS.md carries the same auth bullet as proxy/CLAUDE.md. This
commit adds the same header rule to both files.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
* fix(proxy): keep a real bearer on compat /v1/messages
Review of #969 found two risks. The Anthropic-path remap relabeled a
real inbound Bearer token as x-api-key on every named compat mount.
This broke the rule that an inbound credential keeps its auth scheme.
The remap now skips a real Bearer token. The gateway placeholder is not
a real credential, so the remap still moves it to x-api-key, and the
env fallback replaces it in that header.
The fallback test for x-api-key used a direct map lookup that depends
on canonical header keys. It now uses Header.Get. The openai and
openai_compatible fallback cases are separate, so each guard list
names only the headers that its adapter can emit.
New tests show that a real Bearer stays Bearer on /v1/messages at the
adapter, in the gateway fallback, and through the standalone handler.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
---------
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
Co-authored-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
A relative url() in a content-script stylesheet resolves against the page
origin (the D1 @font-face 404/403 defect, KB #6441) and no amount of
allowlisting catches it - the reference looks valid, it just can never
load. Fail the pack for both targets whenever the css the manifest
registers as content_scripts references a non-absolute url(), driven off
the manifest so new stylesheets are covered automatically.
All three skip/exit outcomes now resolve through main()'s return value and a
single top-level exit, so no process.exit can preempt the cleanup finally
(web-ext tree kill, BiDi close, temp-dir removal). Mutation pass 2/2 on the
guard's key assertions (KB 5489): wrong site host -> indicator FAIL; altered
primer text -> primer FAIL; both reverted, final run 5/5.
The Firefox proof (injection + full-primer send, FF 155) existed only as a
one-off drive and a KB recipe; any edit could silently decay it back into a
claim. scripts/firefox-e2e.mjs rebuilds the Firefox stage, installs it in real
Firefox via web-ext (release-safe temporary add-on) and asserts over a raw
WebDriver BiDi session on the same instance (--args=--remote-debugging-port,
KB #6443) that the indicator injects, a trusted Enter sends the full primer,
and no extension-sourced console errors appear. Skips with exit 0 and a clear
message when Firefox (FIREFOX_BIN override) or web-ext is unavailable, so
test:all never breaks on machines without Firefox. Wired as test:firefox and
added to test:all, mirroring the existing test:browser split (npm test stays
the fast hermetic unit/contract suite).
Content-script CSS is injected into chat pages, so its relative url()s resolve
against the page origin: the indicator's @font-face requested geist-mono.woff2
from the page host (measured 404 on a local harness, 403 on chatgpt.com) and
never loaded, while every chat page ate a failed cross-origin request. Remove
the dead font-face and its now-consumerless web_accessible_resources entry;
fonts stay shipped for the popup, which loads them from the extension origin.
Add the mandatory data_collection_permissions disclosure (none) and raise
strict_min_version to 142.0, the floor the key requires on desktop (140+)
and Android (142+). web-ext lint on the staged build now reports
0 errors / 0 warnings / 0 notices. README load instructions point at the
staged build, since the template intentionally carries no version field.
Three items from the #936 maintainer review:
- gecko id is now a UUID-style id ({2bcb73e7-...}) that claims no domain
(AMO ids are permanent); removed the Co-Authored-By footer from the
Firefox README while touching it.
- the Firefox manifest template no longer carries a version: the firefox
pack target injects the shared version (package.json == Chrome manifest,
validated) at pack time, so the two manifests cannot drift.
- background uses the Firefox MV3 event-page form (background.scripts);
service_worker dropped, strict_min_version 121.0 kept as a conservative
floor with the corrected rationale. The stage verifier now also validates
background.scripts references.
Verification: extension unit suite 11/11 (new firefox-template contract
test), firefox pack + stage verification green, chrome pack unaffected.
The shipped gecko.id pointed at an unowned domain and the background
block declared both service_worker and scripts, which WebExtensions
reject together. Use a stable add-on id, keep a single MV3 service
worker, and derive the packed Firefox version from the shared source so
the two manifests cannot drift at pack time.
`node build-extension-zip.mjs firefox` stages the shared runtime flat
(WebExtensions forbid ../ escapes), swaps in the firefox manifest, and verifies
against the package allowlist -> dist/caveman-browser-firefox-<ver>.zip. The
chrome target is unchanged.
Co-Authored-By: Alot1z <Alot1z@users.noreply.github.com>
Signed-off-by: Alot1z <Alot1z@users.noreply.github.com>
Same runtime files as the Chrome MV3 extension (directive injection on
chatgpt/claude/gemini), Firefox-tuned manifest: browser_specific_settings.gecko
id + strict_min_version 121, service-worker fallback via background.scripts.
Flat, root-relative paths so it stages standalone for AMO.
Closes#810
Co-Authored-By: Alot1z <Alot1z@users.noreply.github.com>
Signed-off-by: Alot1z <Alot1z@users.noreply.github.com>
A proxy caveman just started binds its port before its run state is
readable, so the immediate probe could report a foreign owner on slower
runners. Poll briefly after start and restart. Test fixes for CI: pin
XDG_CONFIG_HOME for in-process Kilo assertions, import the profile scope
checker through a file URL so Windows can load it, and skip the sh-stub
gh reporter tests on Windows.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review follow-ups to #945: a listener caveman did not start this run and
cannot prove it owns (foreign process, or a proxy binary too old to
answer) now launches direct, and the direct-launch strip removes the
agent-attributed /w/<agent> route as well as the bare gateway URL, so a
leaked outer route never reaches the child. Qwen's ACP and extension
listing guards accept yargs camelCase spellings. Proxy-aware fetch no
longer sets an IP literal as TLS servername (rejected on Node 26). The
conformance-pin test fixture follows the pinned-upstream-binary job.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A foreign listener on 127.0.0.1:8787 used to get the banner but still
receive the routed launch, which would send the operator's provider
keys to a process caveman does not own (#945). Treat it like a gate
mismatch and fall back to a direct launch.
Also: Claude Code refuses Remote Control behind any non-first-party
ANTHROPIC_BASE_URL, so `caveman claude remote-control` launches direct
and the limitation is documented (#947); caveman-compress reads the
first text block instead of content[0] so a leading tool_use block no
longer crashes after a paid call (closes#932's report); hook checksum
manifest regenerated after #794.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two-size install story, a What-you-save section with static SVG charts
rendered from the committed benchmark table, and plain-language copy
for non-programmers. Charts regenerate with benchmarks/render_charts.py.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Bare start never stamps global recovery; the banner now says which
sessions compress (those whose agent carries caveman_retrieve) and the
status mcp-missing row keys off any installed MCP marker.
Qwen: refuse extension, ACP, session-restore, control-command, and
enforced-auth surfaces that could reactivate a provider route outside
the locked session; replace modelProviders/providerProtocol wholesale
in the routed profile; keep web search and workflows off via env.
Kilo: daemon and console launch direct since they outlive the wrap.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A bare proxy serves whichever client connects, so a machine-level MCP
install marker can never bind to one request. Read the namespaced
caveman retrieve tool out of the request's own tool list instead, so
subscription and OAuth sessions on a standalone proxy reach the live
zone when their agent carries the tool (#908).
Cache-breakpoint planning and the provider-native cache optimizers now
default on in optimization modes; explicit off and unknown values still
fail closed, and record mode never runs them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The standalone cache planner is no longer in scope here. Drop the
directory and its BSL listing, license scope note, contributor note,
and the verify_repo license-boundary entry.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
compress_file() wrote each un-validated candidate straight to the live
source file and validated it in place. An interruption (Ctrl-C,
SIGTERM, OOM, or a harness killing the process) between that write and
a passing validation left the live file holding un-validated or
garbage output, with the out-of-tree backup as the only recovery path.
Each candidate is now staged next to the source and validated there;
the live file is written only once a candidate passes, and left
untouched if every attempt fails.
Fixes#544
Signed-off-by: Amir Fathi <amirfathi.me@gmail.com>
The proxy now registers /compat/opencode-go/ with a default upstream
and the OPENCODE_API_KEY credential variable. Add the mount and the
user override to the provider docs. Add the variable to the
credential list.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>
The pi extension routed models by wire protocol only. OpenCode Go
models use the OpenAI protocol, so the gateway sent them to
api.openai.com. The OpenCode Go key then caused a 401 error.
The extension now routes by provider and API. OpenCode Go models go
to the /w/pi/compat/opencode-go mount. The extension does not route
unknown providers and shows a pass-through warning.
The standalone proxy registers a built-in compat mount with the
upstream https://opencode.ai/zen/go. The config package holds the
built-in table, so the mount and its key policy come from one place.
A user compat entry with the same name wins. Keyless requests to this
mount use OPENCODE_API_KEY, not OPENAI_COMPAT_API_KEY.
Fixes#946
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com>