Commit Graph
14 Commits
Author SHA1 Message Date
dtzp555-maxGitHubtaodengClaude <claude-opus-4-8> <noreply@anthropic.com>
eeec2bf83d fix(setup): never carry test-only key-store redirection vars into a server OCP launches (A4) (#165)
* fix(setup): never carry test-only key-store redirection vars into a server OCP launches (A4)

Defense-in-depth for the key-store isolation shipped in #163, plus a correction to the
overstated claim that fix's comments made. Surfaced by an independent (Codex) re-review.

Background: keys.mjs honors OCP_DIR_OVERRIDE only when NODE_ENV === "test", so the key store
can be pointed at a scratch dir for the test suite. If BOTH vars reached a production daemon's
environment, it would open a scratch/empty key store instead of ~/.ocp/ocp.db — in AUTH_MODE=multi
a silent total auth outage. #163's comments claimed a production server "runs without NODE_ENV, so
it CANNOT honor the override no matter how the variable got in." That is not something keys.mjs can
enforce — it is only true while the daemon's env happens to lack NODE_ENV=test. This PR makes it
true for every server OCP itself launches, and softens the docs to stop overclaiming.

Three parts (all in OCP's own launch/installer paths — no server.mjs change, no cli.js analogue):

1. scripts/lib/plist-merge.mjs — new exported NEVER_PRESERVE = {NODE_ENV, OCP_DIR_OVERRIDE},
   stripped from the preserved set in BOTH mergePlistEnv and mergeSystemdEnv. The preservation
   rule ("keys only in the EXISTING unit are kept verbatim") was the vector: a unit that once
   carried these test-only vars would otherwise survive every setup re-run. setup.mjs's template
   never injects them, so preservation was the only entry path, and this closes it.

2. ocp (cmd_restart manual fallback) — the one direct `node server.mjs` launch OCP controls now
   runs under `env -u NODE_ENV -u OCP_DIR_OVERRIDE`, so a maintainer who exported both while
   debugging and then restarted can't silently boot the daemon onto a scratch store.

3. keys.mjs + test-env.mjs — softened the overstated comments to state what is actually enforced
   (the two-key gate makes neither var alone do anything; OCP's launchers strip both) and to name
   the one residual path honestly: a hand-rolled `node server.mjs` with both vars explicitly
   exported, bypassing every launcher — for which the loud getDb() "NOT the default" log is the
   backstop. No library-level gate can catch an operator who both sets a test flag and bypasses
   the launchers; the honest fix is a non-silent wrong-store, which #163 already provides.

Severity: LOW (defense-in-depth; the default/shipped path was already safe). No behavior change on
any correctly-configured install.

ALIGNMENT.md: this PR does not touch server.mjs, so the cli.js-citation hard requirement does not
apply; and no cli.js operation is involved — key-store isolation and installer env hygiene are
entirely OCP-owned (no Class A / cli.js-mirror surface).

Tests: +4 mutation-proven (3 behavioral: drop the `!NEVER_PRESERVE.has(k)` guard in either merge
fn and they fail — verified 326 passed / 3 failed under mutation; restored). The `ocp` bash
`env -u` line is verified by `bash -n` + inspection (the suite does not exec the installer/daemon).
Full suite: 329 passed / 0 failed (was 325).

Version bump + CHANGELOG deferred to the later chore(release) PR, per the repo's #148/#149/#150 ->
#151 convention (matching PR #164).

Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>

* test(setup): assert NEVER_PRESERVE.size === 2 so the "exactly two" test matches its name

Reviewer nit (LOW): the membership assertion let a future spurious third entry slip past a
test whose name promises "exactly the two". Behavior stays guarded by the 3 mutation-proof
tests; this just makes the contract test honest.

Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude <claude-opus-4-8> <noreply@anthropic.com>
2026-07-15 22:28:49 +10:00
5aaab5ea28 fix: -p spawn-home isolation (③ latency 3x) + concurrency queue/429 (⑥) + ocp restart env + /ocp plugin compat (#144)
* fix(server): isolate default -p spawn in credential-free scratch HOME (latency ③)

The default (-p/stream-json) spawn inherited the operator's real HOME (global
~/.claude plugins/skills/hooks) and ran with cwd=~/ocp (project CLAUDE.md/skills),
loading heavy host context on EVERY request. Measured: pure API floor for haiku
"hi" ≈ 1–2s; same CLI in the operator's real HOME/cwd ≈ 10–28s; a clean minimal
HOME + CLAUDE_CODE_OAUTH_TOKEN ≈ 3–7s with auth intact.

When an OAuth token is resolvable (and OCP_SPAWN_REAL_HOME!=1), spawnClaudeProcess
now runs claude under a credential-free minimal scratch HOME (<HOME>/.ocp/spawn-home,
no .credentials.json / settings.json / plugins) with cwd = that neutral dir and the
resolved token in CLAUDE_CODE_OAUTH_TOKEN (env token is authoritative for -p). Mirrors
the TUI path's resolveTuiHome() env-token mode. Falls back to real HOME + inherited cwd
when no token resolves (zero regression); OCP_SPAWN_REAL_HOME=1 is a kill-switch.

The token is resolved ONCE (memoized via getSpawnHomeMode, reusing getOAuthCredentials,
the same resolver the /usage probe uses) and never logged. Adds a startup log line and
an additive /health `spawn` block so the operator can confirm isolation is on.

ALIGNMENT.md Rule 2 justification: this is an INFRA / PROCESS-MANAGEMENT change
(HOME/cwd/env isolation of the spawned process). It does NOT mirror a cli.js wire
operation, introduces no new endpoint/header, and adds no API token to the wire path —
so there is no cli.js function to cite. CLAUDE_CODE_OAUTH_TOKEN / CLAUDE_CODE_DISABLE_*
are existing claude-CLI env contracts already used by the TUI path and getOAuthCredentials.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(server): bounded wait-queue + HTTP 429 for -p concurrency overflow (⑥)

spawnClaudeProcess used `if (activeRequests >= MAX_CONCURRENT) throw` → the client
got an opaque 500 AND the rejection was uncounted (a 15-concurrent stress run returned
7×500 while /health stats.errors stayed 0). The TUI path already had a bounded-queue
semaphore (TuiSemaphore); the -p path did not.

Now the -p path reuses TuiSemaphore as `claudeSemaphore = new TuiSemaphore(MAX_CONCURRENT,
{ maxQueue: CLAUDE_MAX_QUEUE })`. Requests beyond MAX_CONCURRENT WAIT (up to CLAUDE_MAX_QUEUE,
default 16) instead of being rejected; only when the queue is ALSO full does the request get
HTTP 429 + Retry-After (rate_limit_error, NOT 500), a distinct `concurrency_queue_full` log,
and a stats.queueRejections counter surfaced on /health. callClaude / callClaudeStreaming now
acquire a slot (acquireClaudeSlot) before spawning; the release fn is wired into the existing
idempotent cleanup() so the slot is freed on EVERY exit path (close/error/timeout/abort) — the
#37/#40 slot-leak guard. MAX_CONCURRENT semantics (max concurrent claude procs) are unchanged;
only overflow handling changed from throw-500 to queue-then-429. claudeSemaphore.limit is kept
in sync with runtime /settings maxConcurrent changes.

Live-verified (fake claude, MAX_CONCURRENT=1 MAX_QUEUE=1): 3 concurrent → exactly one 429
(Retry-After: 7, rate_limit_error body) + two 200s, on BOTH the non-streaming and streaming
paths; /health stats.queueRejections=1 while stats.errors=0; after drain activeRequests=0 /
inflight=0 / queued=0 and a follow-up request returns 200 (no slot leak). 2 new unit tests
(247 passed, 0 failed).

ALIGNMENT.md Rule 2 justification: this is an INFRA / PROCESS-MANAGEMENT change (a concurrency
queue + backpressure status code in front of the existing spawn). It does NOT mirror a cli.js
wire operation, adds no new endpoint or wire header, and introduces no API token — so there is
no cli.js function to cite. (Retry-After is a standard HTTP response header on OCP's own 429,
not a claude wire header.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ocp): macOS restart uses bootout+bootstrap so plist env is re-read

The macOS restart path used `launchctl kickstart -k gui/$uid/dev.ocp.proxy`, which
only re-execs the process and reuses launchd's CACHED environment — so a plist
EnvironmentVariables edit (CLAUDE_BIND, CLAUDE_CODE_OAUTH_TOKEN, etc.) was silently
ignored until a full unload/reload. This is the documented pit-index footgun.

`ocp restart` (macOS) now does a full `launchctl bootout` + `bootstrap` of the agent
via a new `_launchd_reload` helper, which re-reads the plist EnvironmentVariables so env
changes take effect. Success is keyed on the bootstrap (the env-reloading load), not the
bootout (which may legitimately fail if the agent is not currently loaded). A missing
plist returns failure so the `elif` chain falls through to the legacy label and then to
the Linux `systemctl --user restart` path unchanged (systemctl already re-reads its
EnvironmentFile). Updates `ocp restart` help text and adds a README Troubleshooting
subsection ("Env var change doesn't take effect after restart") with the manual
bootout+bootstrap commands and a ps-based verification one-liner.

Verified `_launchd_reload` with a stubbed launchctl: missing plist → rc=1 (falls through,
no launchctl call); bootout-fail + bootstrap-ok → rc=0; bootstrap-fail → rc=1; call order
is bootout then bootstrap. `bash -n ocp` clean; npm test 247/0 (server.mjs untouched).

ALIGNMENT.md Rule 2 justification: this changes ONLY the `ocp` CLI wrapper's local
service-restart mechanism (launchctl invocation). It does not touch server.mjs, the wire
path, any endpoint/header, or any API token — so there is no cli.js function to cite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(readme): document new env vars CLAUDE_MAX_QUEUE / CLAUDE_QUEUE_RETRY_AFTER / OCP_SPAWN_REAL_HOME

Release_kit contract (CLAUDE.md Iron Rule 5.5: "new env var → README § Environment
Variables table") requires the three env vars added by the perf/concurrency fixes to be
in the README table. Adds rows for CLAUDE_MAX_QUEUE (16) + CLAUDE_QUEUE_RETRY_AFTER (5)
(FIX ⑥ -p wait-queue + 429) and OCP_SPAWN_REAL_HOME (FIX ③ spawn-home isolation
kill-switch), each cross-referencing the additive /health.concurrency and /health.spawn
fields. Addresses the independent reviewer's MEDIUM finding. Docs only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ocp-plugin): add openclaw.extensions for OpenClaw 2026.5.27 compat + sync version (②/ocp)

ocp-plugin/package.json's openclaw object lacked the 'extensions' field that
OpenClaw 2026.5.27 requires to install/load a plugin (matches the sibling olp
plugin). Without it the daemon refused to load the local path plugin, breaking
/ocp Telegram commands. Version synced 3.12.0 -> 3.16.2 to match the manifest.
Plugin-layer change; no server.mjs / ALIGNMENT cli.js surface touched.

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:26:57 +10:00
7a69d72886 feat(snapshot): gcSnapshots + ocp update --rollback --gc + auto-GC; v3.16.0 (#94)
Adds snapshot garbage collection with retention policy: keep last 5,
keep snapshots within 30 days, always keep the most recent. Configurable
via keepCount / keepDays opts.

Wire-up:
- ocp update --rollback --gc (manual trigger; --dry-run supported)
- runFullUpgrade auto-GC after successful upgrade (best-effort,
  swallows errors)

4 unit tests: keepCount enforcement, keepDays override, never-delete-
most-recent safety, dry-run mode.

Bumps to v3.16.0 (bundles PR #93 --check oauth + this GC feature).

No cli.js citation needed: this is OCP-internal snapshot lifecycle with
no corresponding cli.js operation.

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 07:33:13 +10:00
ab03c13332 feat(upgrade): ocp doctor + cross-version ocp update + rollback + AI prompts (#91)
* feat(doctor): add ocp doctor with --json + next_action contract

Implements scripts/doctor.mjs with semver-aware path selection
(noop/update/upgrade/fresh_install/fix_oauth/fix_service) and the JSON
contract documented in the design spec.

Service health + OAuth checks integrated; mockable via opts.mockHealth
for unit tests. 8 unit tests cover the kind dispatch tree and the
next_action shape for each kind.

No cli.js citation needed: this is OCP-internal tooling with no
corresponding cli.js operation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ocp): wire cmd_doctor into bash CLI; dispatch to scripts/doctor.mjs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(doctor): handle unparseable version + empty health body

Three issues raised by code-quality reviewer on b65201b:

1. semverCompare returned 0 for unparseable input, causing fromSupported=true
   and kind=noop for an install with unreadable package.json. Now treats
   unparseable currentVersion as fresh_install candidate.
2. mockHealth: { status: 200, body: null } routed to fix_oauth (because
   health.body?.auth?.ok was undefined → falsy). 200 with empty body is
   server-broken, not OAuth-broken; now routes to fix_service.
3. Removed unused KIND_ENUM declaration (dead code).

Two regression tests added.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(upgrade): add scripts/upgrade.mjs + scripts/lib/snapshot.mjs

Implements the upgrade dispatcher (noop / dry-run / light delegation /
full path) and the snapshot writer/reader/list module. Full path snapshots
plist + db + admin-key + openclaw.json before mutating, runs the 6 phases
(pre-flight, snapshot, fetch+install, reconfigure, restart, post-flight),
and emits a heads-up before launchctl bootout per
notify_before_prod_service_restart.md policy.

mockExec/mockDoctor injection points let tests verify the phase ordering
without touching the real shell. fresh_install + rollback paths are
deferred to Bundle 3.

No cli.js citation needed: this is OCP-internal upgrade tooling with no
corresponding cli.js operation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(upgrade): error path completeness + observability

5 issues raised by code-quality reviewer on c12013a:

A. exec() wrapper now captures stderr from execSync failures and
   re-throws with `phase X failed: <stderr>` instead of the terse
   "Command failed: ..." default. Operators see the actual git/npm
   error.
B. runFullUpgrade body wrapped in try/catch; any error after phase 2
   (snapshot written) carries snapshotPath + phases + hint pointing
   at `ocp update --rollback`. Aligns with the post-flight failure
   pattern.
C. CLI entrypoint now prints snapshotPath + hint on error.

Plus minor:
- snapshot.mjs tryCopy logs a [snapshot] warn line instead of silently
  swallowing copy errors (e.g. permission-denied admin-key)
- heads-up window 1s → 3s, more operable per the policy intent
- opts.yes intent comment added (Bundle 3 will use)

One regression test added.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(upgrade): fresh-install + rollback paths

Implements the two missing branches of runUpgrade dispatcher:

- runFreshInstall: gated by --yes, runs doctor.next_action.ai_executable
  steps in order, fails fast on first error, attaches steps[] to thrown
  errors. Accepts mockExec for unit tests.
- runRollback: locates latest or named snapshot in ~/.ocp/, reads
  from-commit.txt, restores plist + db + admin-key + service file (with
  per-file warn lines on copy failure), git-checkouts the from-commit,
  npm installs at that revision, restarts the service. --list shows all
  snapshots; --dry-run prints the plan without mutation.

Both paths use the same exec() error-wrap pattern as runFullUpgrade
(stderr capture, phases attached to thrown errors, restart heads-up).

CLI entrypoint extended to parse --rollback / --list / --target / and
optional positional snapshot path after --rollback.

6 unit tests cover: --yes gate, fresh_install ai_executable run,
--rollback --list, no-snapshots error, --rollback --dry-run, mock-exec
restore.

No cli.js citation needed: this is OCP-internal upgrade tooling with
no corresponding cli.js operation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(upgrade): nit fixes from Bundle 3 code-quality review

3 micro-fixes on 48e9408:
1. Remove unused mkdirSync import
2. snapshot-not-found error message hints "must be inside ~/.ocp/upgrade-snapshot-*"
3. runFreshInstall failure now includes e.stderr (or e.message fallback) in the
   thrown error and steps[].error so non-interactive callers see the actual reason

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(ocp): cmd_update dispatches via doctor; --rollback added; light path preserved

cmd_update now calls scripts/doctor.mjs to determine which path to take:
  noop          → "already at latest" exit 0
  update        → existing light path (git pull + npm install + restart),
                  extracted into _cmd_update_light helper to keep the daily
                  case fast and shell-only
  upgrade       → exec node scripts/upgrade.mjs (full path with snapshot
                  + post-flight)
  fresh_install → exec node scripts/upgrade.mjs (gated by --yes)
  fix_oauth/fix_service → print error referring user to `ocp doctor`

cmd_update --rollback path: exec node scripts/upgrade.mjs --rollback "$@"
forwards remaining args (--list, --dry-run, optional snapshot path).

cmd_update_help expanded to document new flags.

cmd_update --check fast path is preserved exactly (no doctor call there).

No cli.js citation needed: this is OCP-internal CLI dispatch with no
corresponding cli.js operation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ocp): forward all args to cmd_update so multi-flag invocations work

Bug found via runtime smoke test:
  ./ocp update --rollback --list → "no snapshots" (wrong; should list)

Root cause: dispatch was `cmd_update "\${1:-}"` (only first arg). When
user typed `--rollback --list`, cmd_update only received `--rollback`,
the shift left $@ empty, and exec node ... --rollback got no flags.
Other commands using "\${1:-}" don't need multi-arg, but cmd_update now
does (--rollback --list, --rollback --dry-run, --target X --yes, etc.).

Change: dispatch is now `cmd_update "\$@"`. cmd_update internals already
handle multi-arg correctly (\$1 == --check fast path; \$1 == --rollback
shift+forward; otherwise doctor-driven).

Verified:
  ./ocp update --check         → existing behaviour preserved
  ./ocp update --rollback --list → "Found 0 snapshots:" exit 0
  ./ocp update --rollback --dry-run → no-snapshot error exit 1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(release): v3.15.0 — README AI prompt blocks + Upgrading rewrite + CHANGELOG

§Installation, §Upgrading, §Troubleshooting each start with a copy-paste
AI prompt block for Claude Code / Cursor / Copilot. The Upgrading section
explains the three paths (light / full / fresh-install) and rollback usage.

All Commands table gains an `ocp doctor` row.

package.json bumped to 3.15.0.

CHANGELOG.md gains the v3.15.0 entry covering doctor, the cross-version
update path, --rollback, fresh-install routing, and AI prompt blocks.
Notes the dependency on PR #90 (plist env merge bug fix, already merged).

No cli.js citation needed: docs + version bump only, no server.mjs change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(readme): show --yes in rollback usage examples

Per Iron Rule 10 reviewer nit on PR #91: live rollback requires --yes
even for interactive humans. Update §Upgrading examples to show the
canonical human form. (AI agents pass --yes by convention; humans were
hitting a confusing "requires --yes" error following the prior README.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(scripts): CLI entrypoint guard resilient to symlinked install paths

Bug found via integration test on MacBook Pro (macOS /tmp → /private/tmp):
`import.meta.url === \`file://\${process.argv[1]}\`` evaluates false when
the install path traverses a symlink, because import.meta.url is canonicalised
but process.argv[1] is not. Result: ./ocp doctor (and ./ocp update via
upgrade.mjs) exit silently with code 0 and no output, instead of running.

Fix: use fileURLToPath + realpathSync on both sides of the comparison.
Affects any install at a symlinked path (/tmp, NFS mounts, /var/ paths,
docker bind mounts, etc.). Normal ~/ocp installs were unaffected.

No cli.js citation needed: this is OCP-internal CLI dispatch with no
corresponding cli.js operation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 06:56:17 +10:00
fb2d1d3feb fix(ocp-cli): replace eval-curl with bash array to preserve JSON body quoting (#74)
`eval curl "$_AUTH_HEADER" "$@"` re-tokenizes its argument list according
to bash word-splitting rules. When OCP_ADMIN_KEY is set, the JSON body
`'{"name": "laptop"}'` (which contains a space) gets split into
`'{name:'` and `'laptop}'` — two separate args — so curl receives a
malformed body and the server rejects the request.

Fix: replace the `_AUTH_HEADER` string + `eval` pattern with a bash array
`_AUTH_ARGS`. Array expansion via `"${_AUTH_ARGS[@]}"` preserves word
boundaries across substitution with no eval required. Both code paths
(OCP_ADMIN_KEY env var and ~/.ocp/admin-key file fallback) and the empty
case (no admin key) are preserved unchanged.

Verified via `bash -x` trace:
  Before: `curl … -d '{name:' 'laptop}'` (body split, malformed)
  After:  `curl … -d '{"name": "testkey-pr-c"}'` (body intact, single arg)

Identified during fresh-state Round 2 testing on MacBook.

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 00:50:01 +10:00
780462c763 fix(cli): set DISABLE_AUTOUPDATER=1 in manual restart fallback (#52)
When ocp restart falls back to nohup (no launchctl/systemd
service registered), prepend DISABLE_AUTOUPDATER=1 so the
spawned server.mjs and any claude subprocess it spawns skip
the in-binary auto-updater check.

Why: claude-code's native binary contains an auto-updater
that fires after each successful invocation. Partial
install.cjs failures during update leave bin/claude.exe as
an ASCII shim → spawnSync ENOEXEC → OCP slot lockup
(symptoms in #37, #40 forensics). Setting this env at the
launch site stops the trigger.

Note: only covers the manual nohup path. Users with
launchctl plist or systemd unit must add the env there too
(plist EnvironmentVariables or systemd Environment=).
Authoritative settings location is ~/.claude/settings.json
env key — see learnings/claude_code_disable_autoupdate.md
in cc-rules for the full 4-layer guidance.

Verified: 24h+ stable on Mac mini after applying full
multi-layer fix on 2026-04-30 04:20 (claude.exe mtime
unchanged, OCP uptime 22h54m, 0 errors over 3 real calls).

No server.mjs change — ALIGNMENT.md unaffected.

Co-authored-by: dtzp555 <dtzp555@gmail.com>
2026-05-01 17:36:47 +10:00
5ef163aa95 feat(sync): idempotent OpenClaw registry sync in ocp update (#31)
Adds scripts/sync-openclaw.mjs which reconciles
config.models.providers["claude-local"].models and
config.agents.defaults.models["claude-local/*"] with models.json.

Hooked into `ocp update` between git pull and proxy restart, non-fatal
(sync failure does not abort the update; the gateway still restarts and
/v1/models still works).

Scope boundaries (honoring the no-OpenClaw-source-edit constraint):
  - Only touches claude-local provider block and claude-local/*
    alias keys. baseUrl / api / authHeader preserved on existing
    installs (user may have customized port). All other providers
    and top-level config keys untouched.
  - Creates a timestamped backup (openclaw.json.bak.<ms>) before every
    write. No-op path (already in sync) skips backup.
  - Exits 0 with a skip message when ~/.openclaw/openclaw.json does not
    exist (non-OpenClaw users).

server.mjs change: a 15-line passive self-check added inside the
existing server.listen() callback. It only reads the OpenClaw config
and emits console.warn on drift. No network/endpoint surface added, no
new headers, no new routes, no new Claude-CLI-call path. Per
ALIGNMENT.md Rule 2 this is not an operation cli.js performs, so no
cli.js:NNNN citation is required. The added `existsSync` import from
node:fs is already part of the node:fs module surface.

Independent reviewer: Tao pre-approved the 3-PR plan; Iron Rule 10
waived for this task-scoped execution.

Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-20 17:35:41 +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 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 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
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 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