Commit Graph
81 Commits
Author SHA1 Message Date
taodengandClaude Fable 5 64150e0408 fix(tui): kill a cancelled boot's pane when it settles + make async tests actually count
Folds in the independent review's remaining nit — and, in proving the nit's fix, uncovers
two defects in the test suite itself.

## The nit (latent M1b, second costume)

`_cancelBooting` kills BY NAME, but the tmux session only EXISTS once `bootPane` has run —
and `bootPane` is queued on a microtask. So a caller doing `refill()` then `drain()` in the
SAME synchronous block leaves `_cancelBooting` with nothing to kill (a no-op); it bumps the
generation, and the boot microtask then CREATES the session, succeeds, and — under the old
bare `return` on a stale generation — walked away from a LIVE authenticated `claude` that
nothing owns. Reproduced:

  reverted: drain() kills nothing (no session yet) -> boot creates it -> ORPHAN: ['p1']
  fixed   : drain() kills nothing (no session yet) -> boot creates it -> boot kills it -> []

Not reachable from any current call site, so this is defense-in-depth — but ADR 0008 and the
reap-tick comment in server.mjs BOTH explicitly contemplate a boot-time pre-warm, which is
exactly the shape that reaches it. Killing an already-dead session is a harmless no-op, so
the fix is idempotent whichever way the race lands.

## Defect 1 in the suite: async tests were never awaited (44 of them)

Writing the regression guard exposed this. `test()` called `fn()`, got a promise back, and
IMMEDIATELY printed ✓ and incremented `passed` — without awaiting it. For all 44 tests written
as `test("...", async () => {...})`:
  - ✓ meant "did not throw SYNCHRONOUSLY", not "passed";
  - a failed assertion escaped as an unhandled rejection, crashing the process (CI stays red on
    the non-zero exit) but never being COUNTED — so the summary could print "0 failed" and be wrong.
The suite's headline number was therefore not evidence for ANY async test, including this PR's own
M1a/M1b guards. `test()` now settles an async body before counting it, and the summary awaits them.

## Defect 2, exposed the instant defect 1 was fixed: a false guard

`"a boot that resolves AFTER a drain kills its own pane ... no orphan process left behind"` asserted
`killed.length === 1` — i.e. that kill was CALLED once. But `_cancelBooting`'s kill-by-name on a
not-yet-existent session is a NO-OP that still increments that counter. So "kill was called once" and
"a live session is orphaned" were both true at the same time: a test named for the absence of an
orphan was passing while the orphan was present. Now asserts LIVENESS (`live.size === 0`) — the only
honest question.

## Evidence

  fix present : 295 passed, 0 failed, exit 0
  fix reverted: 293 passed, 2 failed  <- BOTH liveness guards fire (the old kill-count guard did not)

Also: `dropped`'s doc comment now lists `cancelled` (a cancelled in-flight boot lands there via _drop).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VqgWJcjxrjjL9L9SkpZyXR
2026-07-13 16:50:35 +10:00
taodengandClaude Fable 5 ed9abb19fe feat(tui): warm pane pool — single-use pre-booted panes, opt-in via OCP_TUI_POOL_SIZE
Backlog item #3 of docs/plans/2026-07-13-tui-latency/README.md. Every TUI request
currently cold-boots a tmux+claude pane. This adds an OPT-IN pool of pre-booted panes.
Recorded as ADR 0008 (docs/adr/0008-tui-warm-pane-pool.md), which extends ADR 0007.

MEASURED (this host, Sonnet 4.6, --effort low, through a real OCP instance; a sample
counts only if HTTP 200 AND the body carries the demanded marker):

  pool off (main code)      n= 6  p50 10.17s  [9164 9499 9760 10572 10774 11281]
  pool on, warm hits        n=12  p50  6.00s  [5286 5289 5520 5584 5621 5969
                                               6040 6098 6280 7846 8036 11053]
  pool on, warm hits (post- n= 6  p50  5.62s  [4729 4753 5236 6004 7548 9548]
    review-fix re-run)

  -> -4.17s / -41%.  12 hits / 1 miss / 0 bootFailures over 13 requests (and 6/1/0 on
  the post-fix re-run). Robust to counting the miss: n=13 p50 -> -40.6%.

The plan doc predicted only -1.0s (the boot). It is ~4.2s because the cold path also
pays ~2.9s INSIDE the first turn beyond claude's own reported turn_duration — post-
input-bar init that an idle pane has already finished. Phase decomposition of the cold
path (n=6 medians): prep 2ms | tmux spawn 27ms | boot->input-ready 1232ms | paste 8ms |
paste-verify 426ms | submit->terminal 8458ms | teardown 8ms = 10162ms total, vs native
turn_duration 5539ms => 4490ms of OCP-side overhead, of which the pool recovers ~1.26s
of boot and ~2.9s of in-claude cold start. (The 426ms paste-verify is one 400ms poll
tick; a real paste lands in ~80ms. Not addressed here — separate item.)

DESIGN
- SINGLE-USE panes. A pooled pane serves exactly ONE turn, then is killed and replaced
  in the background. Each carries its OWN fresh --session-id fixed at boot, so one
  session still holds one exchange. This is what keeps transcript.mjs's
  extractLatestAssistantText correct; its warning about a future warm pool reusing a
  session is answered in-place (comment updated) and left standing for anyone who later
  wants a second turn on a pane — that would be a cross-request TEXT LEAK and needs
  user-line scoping in the transcript reader first.
- Pool keyed by model; --model is fixed at spawn. A miss falls back to the cold path
  with zero behaviour change. The pool warms the most recently requested model, so the
  first request after start (and after a model switch) is always a cold miss.
- REAPER COEXISTENCE (the crux). An idle warm pane IS ours, and the periodic sweep runs
  precisely when we are idle. reapStaleTuiSessions() takes a `spare` set of EXACT live
  session names, and server.mjs DRAINS the pool immediately before the sweep:
    1. a live pooled pane is never reaped — INCLUDING one still BOOTING (see below);
    2. an orphaned pooled pane IS still reaped — membership is by exact name from a live
       in-memory registry, never by name shape, so a pane from a dead process generation
       has nothing claiming it. Omitting `spare` reaps MORE, never less (fail-safe);
    3. kill-server is suppressed while any pane is spared — hence the drain, so the sweep
       still flushes <defunct> claude zombies (the only mechanism that can).
- THE POOL TRACKS ITS IN-FLIGHT BOOT BY NAME, NOT AS A COUNT. bootTuiPane creates the
  tmux session SYNCHRONOUSLY and only then waits up to POOL_BOOT_MS (20s) for the input
  bar, so a pooled session can be LIVE for ~20s before its boot resolves. Tracking boots
  as a count meant the pool could not name that session, which caused two real bugs
  (found in review, reproduced, fixed, and now regression-tested):
    * the reap sweep KILLED the booting pane (it could not be spared), left the pool
      empty with nothing scheduled, and logged the exact tui_pool_boot_failed WARN
      operators are told to alert on — for a completely healthy drain;
    * graceful shutdown ORPHANED a live authenticated idle `claude`: gracefulShutdown
      calls process.exit(0) in the SAME TICK as the drain (TUI panes are tmux children,
      so activeProcesses is empty and the wait-for-children path exits immediately), so
      cleanup deferred to a .then() never ran.
  Fix: the pool mints each pane's identity up front ({sessionId, name}) and holds it in
  _bootingPane. liveNames() includes it; drain() kills it SYNCHRONOUSLY. A generation
  counter distinguishes "cancelled by us" from "genuinely failed", so a drain never
  inflates bootFailures and resume() reliably starts a fresh boot. Deriving the name from
  the session-id also makes `tmux ls` correlate to the transcript file.
- SLOT ACCOUNTING. Refill boots take NO TuiSemaphore slot (those bound real turns and
  would be starved); they cannot leak one either, since they never hold one. Refills are
  SERIALIZED, one boot at a time — live at size=2, two cold boots racing an in-flight
  turn overran the readiness cap and a refill was discarded. A genuinely failed boot does
  not re-kick the chain (backoff; a broken claude must not respawn forever). Background
  boots get a more generous readiness cap (POOL_BOOT_MS = 5x BOOT_MS): BOOT_MS is tight
  because a client is blocked on it, which is not true of a pre-boot.
- BOUNDED COST. A warm pane is a LIVE idle claude process held whether or not a request
  arrives. Peak processes = pool size + OCP_TUI_MAX_CONCURRENT + 1 booting replacement.
  Size clamped to POOL_MAX_SIZE=4; garbage values disable rather than guess. Panes have
  a 10-min TTL and a health check at hand-out (dead/degraded pane => miss, never a hang).
  Missing collaborators throw at CONSTRUCTION, not on a live request (refill() is called
  synchronously from the request path).

DEFAULT OFF (OCP_TUI_POOL_SIZE=0). This is a stable production path and the pool holds
standing processes, so the operator opts in. With the pool off, runTuiTurn takes the
IDENTICAL code path as before (the `pool ? pool.acquire() : null` branch yields null, and
tuiPool is null so no observer is attached and no new log line is emitted) — that is what
establishes the default path is unchanged. A pool-off control run (n=6, p50 9.40s) is
consistent with the 10.17s baseline but had 2/6 samples >12s, so it is corroboration, NOT
proof: n=6 cannot establish "unregressed" on its own. The code-path equivalence can.

BANNER: NO SPAWN ARGUMENT CHANGED. buildTuiCmd is byte-identical to main (verified by
extracting the function body from both revisions and comparing). Live banner captured
from two real POOLED panes anyway: "Sonnet 4.6 with low effort · Claude Max" — the
subscription pool, never "API Usage Billing".

/health: `tui.pool` added (null when off), incl. `cancelled` (boots WE killed — not a
fault; do not alert on it). The tui block is ADR-0007-owned and post-dates ADR 0006's
v3.16.4 grandfather snapshot; the addition is purely additive — every pre-existing key
keeps a byte-identical value. Authorization recorded in ADR 0008.

ALIGNMENT: Class B / ADR 0007 + ADR 0008 (OCP-owned TUI spawn machinery). cli.js does NOT
perform this operation — there is no cli.js citation and none is required: this is not an
Anthropic API surface, it is OCP's own process management around the claude CLI, exactly
as the existing tmux session lifecycle and reaper already are (ALIGNMENT.md Rule 2).

TESTS: 294 passed / 0 failed (was 267). +27 covering acquire/hit/miss, single-use (a pane
is never handed out twice), bounded + serialized refill, TTL + health-check drops, model
retarget, drain/resume, boot-failure backoff, identity linkage, all three reaper
invariants incl. post-drain kill-server restoration, and — the coverage gap that let both
bugs ship — FIVE mid-boot tests: the booting pane is nameable/spareable, the sweep's drain
kills it and resume starts a fresh boot with no bogus WARN, shutdown kills it
synchronously (asserted WITHOUT awaiting, since process.exit runs in the same tick), a
stale settle cannot clear a newer boot's slot, and a model switch cancels an in-flight
boot for the old model.

Live verification (temporary 20s reap interval, reverted): sweep drained both panes ->
reaped -> refilled with NEW panes; a foreign tmux session survived untouched; with no
foreign session kill-server fired and the pool still recovered and served the next
request. Both review bugs reproduced against a PRIVATE tmux server (-L pr3repro, so the
reaper's internal kill-server could not touch the host) before and after the fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 16:38:03 +10:00
taodengandClaude Opus 4.6 e326cee9dd docs: remove outdated troubleshooting workarounds
Removed v3.0.x migration guide, old idleTimeoutSeconds workaround
(auto-handled by setup.mjs since v3.2.1), and manual session cleanup
instructions. Replaced with concise troubleshooting for common issues.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 13:29:05 +10:00
taodengandClaude Opus 4.6 f8ca9b85b0 bump version to 3.6.0 for IDE auto-configuration feature
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 13:27:47 +10:00
taodengandClaude Opus 4.6 c22b0dd074 feat: interactive IDE configuration in ocp-connect
ocp-connect now detects installed IDEs and offers to configure them
automatically. OpenClaw gets full interactive setup (provider name,
primary/backup priority, model aliases). Other IDEs (Cline, Continue.dev,
Cursor) get manual config instructions printed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 13:27:07 +10:00
taodengandClaude Opus 4.6 7f46405152 fix: ocp-connect writes to all shell rc files + system-level env vars
- Writes to both .bashrc and .zshrc on macOS (covers both shells)
- macOS: launchctl setenv for GUI apps and daemons
- Linux: ~/.config/environment.d/ocp.conf for systemd user services
- Ensures IDEs and daemons can discover OCP models

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 12:58:27 +10:00
taodengandClaude Opus 4.6 cb6c2a8b5f fix: fallback to stale cache on usage API 429 + extend cache to 15min
When the /api/oauth/usage endpoint returns 429 (rate limit), fall back
to the most recent cached data instead of returning an error. Also
extended cache TTL from 5min to 15min to reduce API calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 09:44:18 +10:00
taodengandClaude Opus 4.6 02c6758a61 feat: CLAUDE_NO_CONTEXT mode to suppress context injection (closes #11)
Adds CLAUDE_NO_CONTEXT=true env var that passes
CLAUDE_CODE_DISABLE_CLAUDE_MDS=1 and CLAUDE_CODE_DISABLE_AUTO_MEMORY=1
to Claude CLI child processes. This suppresses CLAUDE.md and auto-memory
injection while preserving OAuth auth — needed for third-party agents
like Hermes that have their own memory systems.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 16:40:36 +10:00
taodengandClaude Opus 4.6 6d0e43ec37 fix: ocp-connect tries anonymous access before requiring key
In zero-config auth mode, the server allows anonymous access even in
multi mode. The script now probes /v1/models first — if it succeeds,
no key is needed. Also updated README examples and version to 3.5.1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 13:42:00 +10:00
taodengandClaude Opus 4.6 b87992fa3b fix: use dedicated /api/oauth/usage endpoint for reliable plan data
Replaces the old approach (sending a real messages API request just to
read rate-limit headers) with the dedicated usage endpoint that Claude
Code CLI uses. Fixes intermittent "unknown" plan usage.

- GET /api/oauth/usage with Bearer auth + anthropic-beta header
- Auto-refresh expired OAuth tokens via refresh_token grant
- Try both keychain label formats (claude-code-credentials, Claude Code-credentials)
- Zero API quota consumption for usage checks
- Parse new JSON response format (five_hour/seven_day structure)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 10:52:16 +10:00
taodengandClaude Opus 4.6 69b20815fa feat: zero-config auth — keys optional, localhost is admin
Multi mode now allows unauthenticated requests as "anonymous".
Keys are optional: provide one for per-key usage tracking, or
skip it for zero-config access. Invalid keys are still rejected.

Localhost requests are always admin-level (can manage keys,
view dashboard data, etc.) without any token.

This makes OCP much friendlier for home LAN setups — family
members can use it immediately without any key configuration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 10:11:08 +10:00
taodengandClaude Opus 4.6 3eecca35ce feat: localhost bypass auth in multi mode
Requests from 127.0.0.1/::1 skip authentication even in multi/shared
auth modes. Only remote (LAN) clients need API keys.

This simplifies local tool integration — IDEs and agents on the same
machine no longer need to configure Bearer tokens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 10:09:07 +10:00
taodengandClaude Opus 4.6 9f1a21f7ad docs: update dashboard screenshot with live usage data
Session 53%, Weekly 40% — proper colors via Playwright

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 10:04:10 +10:00
taodengandClaude Opus 4.6 a0f9268af5 fix: dashboard token via URL param + correct screenshot
- Support ?token=xxx query parameter for dashboard auth (enables
  headless browser screenshots and direct links)
- Token is saved to localStorage and URL is cleaned up
- Fix pathname matching to handle query parameters in URL
- Replace html2canvas screenshot with Playwright (accurate colors)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 09:56:21 +10:00
taodengandClaude Opus 4.6 087e26346f feat: add ocp-connect lightweight client + restructure README
- Add standalone `ocp-connect` script for zero-dependency client setup
  (only needs curl + python3, no Node/repo clone required)
- Add `ocp connect` command to main CLI
- Add `authMode` field to /health endpoint
- Restructure README with clear Server Setup / Client Setup sections
- Add dashboard screenshot to README
- Fix smoke test model name (claude-haiku-4-5-20251001)
- Fix auth mode detection for shared/multi/none
- Fix rc file cleanup idempotency (blank line handling)
- Fix key input echo (now hidden with read -rs)
- Add host validation
- Bump version to 3.5.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 09:48:33 +10:00
taodengandClaude Opus 4.6 ed53c5e0c5 docs: add Telegram/Discord /ocp slash command usage to README
Clarify that /ocp commands are for Telegram/Discord via gateway plugin,
while terminal CLI uses ocp (without slash).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 21:48:55 +10:00
taodengandClaude Opus 4.6 18cb759359 Merge feature/lan-mode: LAN sharing with multi-key auth, dashboard, CLI
Add LAN mode to share one Claude Pro/Max subscription across a household:
- 3-tier auth: none / shared / multi (per-user API keys)
- SQLite-backed key management and per-request usage tracking
- Embedded web dashboard with real-time monitoring
- CLI commands: keys, lan, usage --by-key
- Zero external dependencies (uses node:sqlite)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 21:45:17 +10:00
taodengandClaude Opus 4.6 cc745aa5d9 docs: update README with complete LAN mode reference
Add LAN CLI commands (keys, lan, usage --by-key), new API endpoints
(dashboard, keys, usage), LAN env vars (CLAUDE_BIND, CLAUDE_AUTH_MODE,
OCP_ADMIN_KEY), and expanded security section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 21:45:07 +10:00
taodengandClaude Opus 4.6 1983f9372d fix: CLI auth support for multi-key mode + fix LAN IP detection
- Add _curl wrapper that reads OCP_ADMIN_KEY env or ~/.ocp/admin-key file
- All admin API calls (keys, usage) use _curl for auth
- Fix ocp lan IP detection to try en0 and en1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 21:36:16 +10:00
taodengandClaude Opus 4.6 471bda2c40 fix: make /dashboard a public endpoint (no auth required)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 21:21:36 +10:00
taodengandClaude Opus 4.6 566a01a6bd refactor: switch from better-sqlite3 to node:sqlite (zero dependencies)
better-sqlite3 native addon fails on Node v25. Node.js built-in SQLite
(node:sqlite) has an identical synchronous API and requires no compilation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 21:20:24 +10:00
taodeng 43daf8162c docs: add LAN mode documentation and family sharing guide 2026-04-10 21:17:25 +10:00
taodeng aa0adab784 feat: add LAN mode config options to setup.mjs 2026-04-10 21:17:19 +10:00
taodengandClaude Sonnet 4.6 4f4e1edf16 feat: add ocp keys, ocp lan, and ocp usage --by-key CLI commands
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 21:16:25 +10:00
taodeng ea57db6ceb feat: add embedded web dashboard with real-time usage display 2026-04-10 21:15:44 +10:00
taodengandClaude Sonnet 4.6 5fbeaed568 security: fix key exposure, timing-safe admin auth, remove admin bypass, cap params, harden usage recording
- Fix 1 (keys.mjs): listKeys() no longer leaks full API key field in response
- Fix 2 (server.mjs): Remove BIND_ADDRESS admin bypass from isAdmin check
- Fix 3 (server.mjs): Admin key comparison now uses timingSafeEqual
- Fix 4 (server.mjs): Cap limit (max 500) and hours (max 720) in GET /api/usage
- Fix 5 (server.mjs): Streaming error path now computes promptChars from messages
- Fix 6 (server.mjs): Warn at startup if AUTH_MODE=shared but PROXY_API_KEY is empty
- Fix 7 (server.mjs): All recordUsage calls wrapped in try/catch to prevent DB errors crashing server
- Fix 8 (server.mjs): CORS fallback changed from "*" to specific origin (http://127.0.0.1:<PORT>)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 21:13:17 +10:00
taodengandClaude Sonnet 4.6 2b18884d2a feat: add LAN bind, 3-mode auth, usage recording, key/usage API endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 21:07:22 +10:00
taodeng 4f72f4844e feat: add keys.mjs — API key management and SQLite usage store 2026-04-10 21:04:00 +10:00
taodeng bc17b27f2b chore: add better-sqlite3 dependency, bump to v3.4.0 2026-04-10 21:02:40 +10:00
taodengandClaude Opus 4.6 3e8ff7a509 docs: add LAN mode implementation plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 21:00:22 +10:00
taodengandClaude Opus 4.6 b6b33d2623 chore: add MIT LICENSE file
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 20:37:11 +10:00
taodengandClaude Opus 4.6 8796a5fc19 docs: update README version to v3.3.1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 07:49:54 +10:00
taodengandClaude Opus 4.6 609ceb28b0 fix: use stable node symlink path in macOS plist, avoid Cellar breakage
setup.mjs now resolves /opt/homebrew/Cellar/node/X.Y.Z/ to
/opt/homebrew/opt/node/ so the LaunchAgent plist survives node upgrades.
Bump to v3.3.1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 10:01:54 +10:00
taodengandClaude Opus 4.6 3843ec8fe6 refactor: simplify timeout to single CLAUDE_TIMEOUT (default 600s)
Remove firstByteTimeout, adaptive tier algorithm, and MODEL_TIMEOUT_TIERS.
Claude tool-use causes 30s-5min pauses in token streams, making fine-grained
timeouts unreliable — they repeatedly killed valid requests. A single generous
timeout (10 min, matching LiteLLM/OpenAI SDK defaults) is simpler and correct.

Breaking: CLAUDE_FIRST_BYTE_TIMEOUT env var removed, default timeout changed
from 300s to 600s. Bump to v3.3.0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 09:46:12 +10:00
taodengandClaude Opus 4.6 afe0c6e1be fix: computeFirstByteTimeout now respects CLAUDE_FIRST_BYTE_TIMEOUT env var
The adaptive timeout calculation ignored BASE_FIRST_BYTE_TIMEOUT (set via
CLAUDE_FIRST_BYTE_TIMEOUT env), always using the tier base (120s for Sonnet).
Now uses whichever is larger: adaptive or env override. Bump to v3.2.2.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 09:26:01 +10:00
taodengandClaude Opus 4.6 a6007393a3 fix: auto-configure idleTimeoutSeconds=0 to prevent tool-call timeouts
setup.mjs now sets agents.defaults.llm.idleTimeoutSeconds=0 in
openclaw.json during installation. Without this, OpenClaw's default
60s idle timeout kills Claude connections during tool use (Bash, Read,
etc.), causing exit 143 errors and stuck sessions.

Also adds troubleshooting section to README. Bump to v3.2.1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:50:19 +10:00
taodengandClaude Opus 4.6 8592150f7a docs: update README for v3.2.0 — add update command, upgrade notes
- Document ocp update / ocp update --check
- Add Self-Update section
- Add Upgrading from v3.0.x migration notes
- Remove Known Issues (resolved upstream)
- Note neutral service names in OpenClaw Integration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:22:55 +10:00
taodengandClaude Opus 4.6 eb76971ffc fix: resolve symlinks in ocp update/restart for linked installs
BASH_SOURCE returns the symlink path, not the target. This broke
ocp update on systems where ocp is installed as a symlink
(e.g. ~/.local/bin/ocp → repo/ocp).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:20:26 +10:00
taodengandClaude Opus 4.6 d54e73ef89 fix: prevent git fetch failure from aborting ocp update --check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:19:41 +10:00
taodengandClaude Opus 4.6 3f10b459f5 feat: add ocp update command + fix restart for new service names
- `ocp update`: pulls latest from GitHub, syncs plugin, restarts proxy
- `ocp update --check`: shows current vs latest version without applying
- `ocp restart`: now handles both dev.ocp.proxy and legacy ai.openclaw.proxy
  service names on macOS/Linux
- Bumps to v3.2.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:18:41 +10:00
taodengandClaude Opus 4.6 b72e449337 fix: use neutral service names to avoid OpenClaw gateway detection
OpenClaw's gateway scans LaunchAgent plists and systemd units for
"openclaw" markers and flags them as conflicting gateway-like services.
Renamed service identifiers to avoid false positives:

- macOS: ai.openclaw.proxy → dev.ocp.proxy
- Linux: openclaw-proxy.service → ocp-proxy.service
- Logs: ~/.openclaw/logs/ → ~/.ocp/logs/

Setup auto-removes legacy service names on upgrade. Uninstall handles
both old and new names. Also bumps version to 3.1.0.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 08:13:55 +10:00
taodengandClaude Opus 4.6 2b05558b8b rebrand: Open Claude Proxy — multi-IDE positioning
Renamed from OpenClaw Control Plane to Open Claude Proxy. README
rewritten to position OCP as a universal proxy for any IDE that
speaks the OpenAI protocol, not just OpenClaw.

Added: supported tools table (Cline, OpenCode, Aider, Continue.dev),
simplified Quick Start, architecture diagram showing multi-IDE flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 12:06:41 +10:00
taodengandClaude Opus 4.6 d9b7c0d905 docs: add Known Issues — /ocp Unknown skill workaround
Documented the intermittent "Unknown skill: ocp" issue caused by
OpenClaw gateway session routing bug (#26895). Workaround: /new
followed by /ocp restart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 20:50:17 +10:00
taodengandClaude Opus 4.6 27cd8b1735 fix: correct server.mjs path in start.sh
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:38:18 +10:00
taodengandClaude Opus 4.6 9dd35c90ae docs: remove premature Known Issues section
The slash session workaround was incomplete — root cause still under
investigation. Removed to avoid misleading users. Will add back with
accurate info once the issue is fully resolved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 11:04:38 +10:00
taodengandClaude Opus 4.6 c005a8fd27 remove backends command — OCP is stable v3, no Phase 2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 10:57:58 +10:00
taodengandClaude Opus 4.6 77a3f48cfd feat: add restart, version, test, backends commands to /ocp plugin
New subcommands:
- /ocp restart [gateway|all] — restart proxy, gateway, or both
- /ocp version — show version, uptime, platform info
- /ocp test — end-to-end proxy test (sends haiku request)
- /ocp backends — list registered backends with health status

Also includes clean-slash-sessions.sh workaround for OpenClaw #26895.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 09:09:23 +10:00
taodengandClaude Opus 4.6 7390a19ec6 docs: add prominent warning about slash session bug near plugin install
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 17:37:10 +10:00
taodengandClaude Opus 4.6 04643217ae docs: add known issue — slash session hijacks plugin commands
Document OpenClaw gateway bug (#26895, #54485) where telegram:slash
sessions interfere with registerCommand plugin routing. Include
workaround script and warning not to add ocp to agent skills lists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 17:36:42 +10:00
taodengandClaude Opus 4.6 3de1868313 fix: read OAuth token from Linux credentials file as fallback
On Linux (no macOS keychain), read ~/.claude/.credentials.json first.
Falls through to macOS keychain if file not found. Fixes /ocp usage
showing "No OAuth token" on Linux servers and RPi.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 20:42:16 +10:00
taodengandClaude Opus 4.6 75cff38f4c docs: improve CLI install instructions for cloud/Linux users
Common issue: ocp not in PATH after clone. Added explicit symlink
instructions with absolute paths and a troubleshooting note.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:52:22 +10:00
taodengandClaude Opus 4.6 a8a0af43c9 docs: mark OCP as stable v3.0.0, update repo name and description
- README: add "Status: Stable" badge, rename title to OCP — OpenClaw Control Plane
- package.json: update description and repo URL to dtzp555-max/ocp
- No code changes. Feature-complete, bug fixes only going forward.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:49:16 +10:00
taodengandClaude Opus 4.6 77a6f8ed59 revert: rollback Phase 1 and OAuth fix — broke plan usage functionality
Reverts b5be1c0 and 1f1b387. These changes broke the OAuth token
authentication for fetching plan usage data from Anthropic API,
causing /ocp usage to show 0% with "unknown" reset times.

Root cause: code changes inadvertently altered the fetchUsageFromApi
flow, breaking the existing working OAuth x-api-key authentication.

This restores server.mjs to the last known working state (f6f4f68).
Phase 1 modular files removed — will be re-implemented in a separate
branch with proper isolation testing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 18:16:04 +10:00
taodengandClaude Opus 4.6 b5be1c04f4 fix: auto-refresh expired OAuth token for plan usage data (v3.0.1)
When the Claude Code OAuth token expires, /ocp usage showed 0% for all
plan limits. OCP now detects expiry and triggers a refresh via Claude CLI,
then re-reads the updated token from keychain. The 5-minute usage cache
prevents excessive refresh attempts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 16:18:15 +10:00
taodengandClaude Opus 4.6 1f1b3871da feat: Phase 1 — modular backend architecture (v4.0-alpha)
Internal refactor: extract BackendAdapter, ModelRegistry, AgentRouter,
SessionManager, and StatsCollector as standalone modules. Claude CLI
logic encapsulated in ClaudeCliAdapter.

External API completely unchanged — all existing endpoints behave
identically. Legacy request path (callClaude/callClaudeStreaming)
untouched. v4 modules load in parallel for validation.

New endpoints:
  GET /backends  — backend health and status
  GET /routing   — agent routing table

Architecture:
  unified-chunk.mjs    — streaming protocol (delta/done/error)
  backends/base.mjs    — BackendAdapter interface
  backends/claude-cli.mjs — Claude CLI adapter (core)
  model-registry.mjs   — model → backend mapping (Layer 1)
  agent-router.mjs     — agent policy routing (Layer 2)
  session-manager.mjs  — conversation session management
  stats-collector.mjs  — per-model/backend/agent metrics

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 08:02:53 +10:00
taodengandClaude Opus 4.6 f6f4f68ecd Add upgrade guide for v2.x skill-based /ocp users
Explains removing old skills/ocp/SKILL.md to avoid conflict
with the new plugin-based command handler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:07:29 +10:00
taodengandClaude Opus 4.6 47e39d7820 feat: v3.0.0 — OCP CLI, plan usage monitoring, runtime settings, prompt guard
Major release: complete management interface for the Claude proxy.

- /ocp CLI with 10 subcommands (usage, status, health, settings, logs, models, sessions, clear, restart)
- Gateway plugin for /ocp as native Telegram/Discord slash command
- Plan usage monitoring via Anthropic API rate-limit headers (session %, weekly %, reset times)
- Per-model request stats (count, avg/max elapsed, avg/max prompt chars)
- Runtime settings via PATCH /settings (tune timeouts, concurrency, prompt limits without restart)
- Prompt truncation guard at 150K chars to prevent conversation history blowup
- Circuit breaker removed — caused cascading failures in CLI-proxy architecture
- Timeout increases: Opus 150s, Sonnet 120s, Haiku 45s base first-byte
- New endpoints: /usage, /status, /settings, /logs
- README fully rewritten with CLI examples and changelog

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:33:17 +10:00
taodengandClaude Opus 4.6 7434f6adc0 docs: v2.5.0 release notes — sliding-window circuit breaker incident writeup
Document the 2026-03-22 multi-agent cascading timeout incident, root cause
analysis, and all new/changed defaults. Update env vars table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:48:23 +10:00
taodengandClaude Opus 4.6 8a7951134e feat: v2.5.0 — sliding-window circuit breaker, graduated backoff, increased timeouts
Emergency fix for multi-agent (ClawTeam) burst scenario that caused cascading
failures: when multiple Opus agents ran concurrently, the old consecutive-count
breaker (threshold=3) tripped within seconds, blocking ALL subsequent requests
globally — including non-ClawTeam agents and new sessions.

Changes:
- Circuit breaker now uses a sliding time window (default 5min) instead of
  consecutive failure count. 6 failures in 5min triggers open, vs old 3-in-a-row.
- Half-open state allows 2 concurrent probe requests (was 1), improving recovery
  speed when API comes back.
- Graduated backoff: cooldown doubles on each re-open (120s→240s→300s cap),
  resets fully on first success.
- Increased default timeouts: Opus first-byte 60s→90s, Sonnet 45s→60s,
  overall 120s→300s, max concurrent 5→8.
- /health endpoint now exposes per-model breaker state, window stats, and
  marks status as "degraded" when any breaker is open.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:47:23 +10:00
taodengandClaude Opus 4.6 1d95dbeebc feat: v2.4.0 — per-model circuit breaker, adaptive timeout tiers, structured logging
- Circuit breaker: consecutive timeouts (default 3) mark model as degraded for 60s,
  failing fast instead of blocking gateway with repeated timeout attempts
- Per-model timeout tiers: Opus 60s base, Sonnet 45s, Haiku 30s, plus adaptive
  scaling by prompt size (~15s/100k chars for Opus)
- Structured JSON logging for spawns, timeouts, breaker state changes
- Late close guard: prevents double-settle when timeout fires before proc.close

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 17:38:45 +10:00
taodeng 9e6bef729b fix: harden proxy timeout handling for opus 2026-03-21 17:32:21 +10:00
taodeng 256b2bfb77 docs: ship v2.3.0 positioning and coexistence guide 2026-03-21 14:02:33 +10:00
taodeng 384e66bfa6 feat: v2.2.0 — faster fallback with first-byte timeout
- Reduced default CLAUDE_TIMEOUT from 300s to 120s for faster fallback
- Added CLAUDE_FIRST_BYTE_TIMEOUT (default 30s): aborts early if Claude
  CLI produces no output, preventing silent hangs
- First-byte timing logged for every request for observability
- Health endpoint now reports firstByteTimeout in config
2026-03-21 13:57:23 +10:00
taodengandClaude Opus 4.6 76a8c56c88 feat: v2.1.0 — faster fallback with first-byte timeout and reduced defaults
- Default timeout: 300s → 120s (CLAUDE_TIMEOUT)
- New: first-byte timeout at 30s (CLAUDE_FIRST_BYTE_TIMEOUT) — aborts early
  if Claude CLI produces no stdout, triggering faster fallback
- Pool disabled by default (POOL_SIZE=0) — on-demand spawning is now the
  default; set CLAUDE_POOL_SIZE>0 to re-enable
- Health endpoint now reports timeout and firstByteTimeout values
- Startup log shows pool mode (disabled/on-demand vs active)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 13:53:27 +10:00
taodengandClaude Opus 4.6 cedd2acecc feat: v2.0.0 — on-demand spawning, session management, full tool access
Major architectural upgrade:
- Replace pool system with on-demand spawning (eliminates crash loops,
  DEGRADED states, and stdin timeout errors from v1.x)
- Add session management with --resume support (reduces token waste on
  multi-turn conversations)
- Expand default allowed tools (Bash, Read, Write, Edit, Glob, Grep,
  WebSearch, WebFetch, Agent) with configurable CLAUDE_ALLOWED_TOOLS
- Add system prompt pass-through (CLAUDE_SYSTEM_PROMPT)
- Add MCP config support (CLAUDE_MCP_CONFIG)
- Add concurrency control (CLAUDE_MAX_CONCURRENT, default 5)
- Add periodic auth health monitoring
- Add session API endpoints (GET/DELETE /sessions)
- Improve /health with full diagnostics (stats, sessions, errors, config)
- Fix timeout race condition (graceful SIGTERM → SIGKILL)
- Fix ERR_HTTP_HEADERS_SENT by checking headersSent in all response helpers
- Document coexistence with Claude Code interactive mode (Telegram, IDE)
- No conflict with CC: different ports, protocols, and process models

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:32:40 +10:00
taodengandClaude Opus 4.6 9a22372e28 feat: auto-detect claude binary to prevent ENOENT in launchd (v1.8.0)
The proxy previously defaulted to bare "claude" command which fails in
macOS LaunchAgent where PATH is minimal. Now resolves the binary at
startup via: CLAUDE_BIN env → well-known paths → which fallback.
Fails fast with clear error if binary cannot be found.

Also enhances /health to report claudeBinary and claudeBinaryOk status.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 22:35:18 +10:00
taodeng 917ff60014 fix: harden proxy recovery and sanitize anthropic env (v1.7.1) 2026-03-20 12:29:48 +10:00
taodeng 5f00d6960b docs: add Bearer token authentication section and update env vars for v1.7.0 2026-03-19 20:15:20 +10:00
taodeng 2fa7991d6e feat: add Bearer token authentication via PROXY_API_KEY (v1.7.0)
- Add optional PROXY_API_KEY env var for Bearer token auth
- Return 401 for missing/invalid token on all endpoints except /health
- Skip auth when PROXY_API_KEY is not set (backwards compatible)
- Log auth status on startup
2026-03-19 17:45:01 +10:00
taodengandClaude Sonnet 4.6 ff05657c94 fix: add crash backoff and stderr logging to pool manager
- Rework replenishPool to accept isCrash flag: initial fills spawn
  immediately with no delay; only crash-triggered respawns apply backoff
- Exponential backoff on crash: 2s, 4s, 8s, 16s, 32s, capped at 60s
- After 5 consecutive fast crashes (< 10s lived) within a 60s rolling
  window, mark model as "degraded" and stop all respawning to prevent
  CPU spin loops
- Degraded state resets automatically if a subsequent spawn lives > 10s
- stderr from crashed pool workers is captured and logged on exit
- Bump version to 1.6.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:27:40 +10:00
taodengandClaude Sonnet 4.6 17384acb4c fix: add pool respawn backoff + stderr capture + model name mapping (v1.5.1)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:24:49 +10:00
taodengandClaude Sonnet 4.6 37803edb3f fix: read version from package.json instead of hardcoded string
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:08:45 +10:00
taodengandClaude Sonnet 4.6 43d4599dac feat: add auto-start on boot and restructure README (v1.5.0)
- setup.mjs: install launchd plist (macOS) or systemd user service (Linux) after proxy starts; logs to ~/.openclaw/logs/proxy.log
- Add uninstall.mjs to stop and remove the auto-start entry
- README: Quick Start (Node.js) first, Security section, Docker moved to Server/Advanced
- Bump version to 1.5.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:17:15 +10:00
taodengandClaude Sonnet 4.6 6ff48f68c0 feat: add Docker support and improve /health endpoint
- Add Dockerfile (node:20-alpine, EXPOSE 3456, ENV for session tokens)
- Add docker-compose.yml with restart: unless-stopped
- Add .dockerignore
- Improve /health: add version, uptime, uptimeHuman, per-pool ready/error status
- Listen on 0.0.0.0 for container compatibility
- Bump version to 1.4.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 11:11:13 +10:00
taodengandClaude Opus 4.6 857c703b0a fix: restore --allowedTools flag broken in v1.3.0 pool refactor
The v1.3.0 process pool refactor changed --allowedTools to --tools ""
which disabled all CLI tools. This caused agents to output raw <tool_use>
XML as text instead of executing tools. Restored --allowedTools with
Bash, Read, Write, Edit, Glob, Grep in both spawnWarm() and callClaude()
cold-start paths. Bumped version to 1.3.1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 13:44:36 +10:00
taodengandClaude Opus 4.6 606fc255e2 bump version to 1.1.0
Changes since 1.0.0:
- SSE streaming support for OpenClaw compatibility
- --allowedTools flag for non-interactive tool approval
- Recovery and troubleshooting docs in README
- setup.mjs auto-configuration script

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 06:26:34 +10:00
taodengandClaude Opus 4.6 6d708a602f docs: translate recovery section to English
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 06:22:53 +10:00
taodengandClaude Opus 4.6 57df919873 docs: add upgrade recovery guide and troubleshooting table
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 06:05:13 +10:00
taodengandClaude Opus 4.6 15fee08b32 Improve README with compelling intro and feature highlights
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 22:34:00 +10:00
taodengandClaude Opus 4.6 92c55df021 Pre-approve common tools (Bash, Read, Write, Edit, Glob, Grep)
claude -p in non-interactive mode cannot show approval prompts,
so tools like gh/git were blocked. Add --allowedTools to enable
tool execution without manual approval.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 22:10:09 +10:00
taodengandClaude Opus 4.6 593d0dcd5f Initial release: OpenAI-compatible proxy for Claude CLI
Routes OpenClaw requests through `claude -p` CLI, letting you use
Claude Pro/Max subscriptions as a model provider without API keys.

- SSE streaming + non-streaming responses
- Auto-setup script for OpenClaw configuration
- Supports Opus 4.6, Sonnet 4.6, Haiku 4

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 21:47:12 +10:00