mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-19 09:44:07 +00:00
16eeb66557c1ed2f8f61d299ac9c05bfa1324c8c
123
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
16eeb66557 |
feat(cache): per-key isolation, cache_control bypass, chunked stream replay (#65)
* docs(governance): ADR 0005 (no multi-provider) + cache upgrade spec Governance prelude for the cache upgrade work: - docs/adr/0005-no-multi-provider.md — locks in the decision that OCP stays single-provider (Anthropic via cli.js spawn). Cache improvements are explicitly in scope (decision §3); multi-provider refactor is explicitly out of scope, with three documented trigger conditions for revisiting. - docs/adr/README.md — index updated to reference 0005. - docs/superpowers/specs/2026-05-07-cache-upgrade-design.md — design for the cache upgrade work split into PR-A (per-key isolation, cache_control bypass, chunked stream replay) and PR-B (singleflight stampede protection). Each design decision has a written rationale. server.mjs is not modified; this commit is doc-only and therefore exempt from the cli.js citation requirement (ALIGNMENT.md Rule 5 applies only to commits that touch server.mjs). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(cache): per-key isolation, cache_control bypass, chunked stream replay cli.js does not perform response caching at the proxy layer. OCP's response cache is a value-add operation internal to OCP, between the wire and the cli.js spawn. It does not introduce, rename, or alter any endpoint, header, request field, or response field that cli.js emits or expects — this change qualifies under ALIGNMENT.md Rule 2's value-add carve-out for non-wire- affecting proxy operations. no client-observable wire shape change. Spec: docs/superpowers/specs/2026-05-07-cache-upgrade-design.md D1 — Per-key cache isolation (cacheHash v2 format) keys.mjs: prepend `v2|k:<keyId or "anon">|` before existing hash fields. Backward-compatible: absent/null/empty keyId folds to "anon". v1-format rows in response_cache are abandoned naturally; TTL cleanup at server.mjs:185 reaps them within one window. No migration needed. server.mjs: pass keyId: req._authKeyId at the single cacheHash call site (line ~1221). D2 — cache_control bypass keys.mjs: export hasCacheControl(messages) — walks messages and nested content arrays for presence of cache_control field. server.mjs: if hasCacheControl(messages) is true, set req._cacheHash = null and log cache_skipped{reason: cache_control_present}; existing `if (CACHE_TTL > 0 && req._cacheHash)` guards on write-back handle the skip. D3 — Chunked stream replay (80 codepoints/chunk, no artificial delay) server.mjs: replace single-chunk cached.response emission with an Array.from(cached.response) loop in steps of CACHE_REPLAY_CHUNK_SIZE=80. Array.from ensures multibyte UTF-8 codepoints (e.g. CJK) are never split. Tests: 12 new cases in test-features.mjs (36 total, 0 failed). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
c998d21a4f |
chore(ci): add gitleaks workflow to scan PRs and pushes to main (#64)
The repo has shipped `.gitleaks.toml` (with project-specific allowlist entries — public OAuth client ID, README placeholders, an old plan doc) since the privacy remediation work, but no GitHub Action invoked it. The config was orphan: real protection only when someone ran gitleaks locally, never gating merges. This workflow wires `.gitleaks.toml` into CI: - Triggers on every `pull_request` (any branch) and `push` to `main`. - Uses `gitleaks/gitleaks-action@v2`, which auto-detects the repo-root `.gitleaks.toml` and applies its allowlist. - Hard-fails on any leak. No `continue-on-error`. Public repo policy. - `permissions: contents: read` — minimum required scope. - `fetch-depth: 0` so the action can scan full history (the action's default behavior; explicit here for clarity). Verification path: - The workflow runs on this PR itself; if any secret were ever committed to the repo, the scan fails here. Prior audit confirmed the tracked tree is clean of real secrets, so this PR's own scan should pass. Refs: audit side-finding 4 of 4. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
5be369ed68 |
docs(changelog): normalize version heading format to ## vX.Y.Z — YYYY-MM-DD (#63)
Audit side-finding: heading style drift in CHANGELOG.md. Before: ## v3.12.0 (2026-04-25) ← parens style (1 entry) ## v3.11.1 — 2026-04-21 ← em-dash style (canonical, majority) ## v3.11.0 — 2026-04-20 ← em-dash style (canonical, majority) After: all 3 entries use the em-dash form (2 of 3 entries already used it, so it's the canonical pattern by majority). Only the v3.12.0 heading line changed. The body content under each heading is untouched — only the date-format separator changes from parens to em-dash. Verification: - `grep "^## " CHANGELOG.md` after the edit → all entries match `## vX.Y.Z — YYYY-MM-DD`. - `git diff CHANGELOG.md` shows exactly one line changed. Refs: audit side-finding 3 of 4. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
3d52ffc152 |
chore(deps): bump engines to >=22.5 to match node:sqlite usage in keys.mjs (#62)
OCP uses Node.js built-in SQLite (`node:sqlite`) in keys.mjs:3 for the
LAN-mode key store. The `node:sqlite` module is only available in:
- Node 22.5.0+ behind --experimental-sqlite flag
- Node 23.0.0+ without any flag (fully stable)
The previous `engines: ">=18"` was inaccurate and would have caused
opaque "Cannot find module 'node:sqlite'" failures for users on
Node 18-22.4 the moment LAN mode (multi-key) was enabled.
Changes:
- package.json: engines.node ">=18" → ">=22.5"
- README.md: prerequisite "Node.js 18+" → "Node.js 22.5+ (Node 23+ recommended …)"
with a one-line note explaining the flag distinction so users on 22.x know
they may need --experimental-sqlite.
Verification:
- Source usage of node:sqlite confirmed via grep: keys.mjs:3
(`import { DatabaseSync } from "node:sqlite";`).
- Local node --version = v25.8.0 (well above 22.5); `npm install` exit 0
with no engine-mismatch warning.
- No other source file imports node:sqlite (single point of usage).
Refs: audit side-finding 2 of 4.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
68b0838074 |
chore(deps): delete stale package-lock.json (was v3.4.0 vs package.json v3.12.0) (#61)
The repo-tracked package-lock.json declared "version": "3.4.0" while package.json is at v3.12.0 — 8 minor versions of drift. Since package.json has zero dependencies (only built-in node:sqlite, node:http, node:https), the lockfile carried no useful information. It was pure noise that would mislead anyone running `npm ci` into thinking they were installing v3.4.0. Verification: - package.json has no `dependencies` or `devDependencies` field (grep -A2 '"dependencies"' package.json → no match). - Fresh clone + checkout + `npm install` → exit 0, "audited 1 package in 93ms, found 0 vulnerabilities". - node_modules is correctly empty after install (no bin shims to relink). Refs: audit side-finding 1 of 4. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
313cb13a78 |
docs: align README + governance docs with current state (uninstall, files, ADR index, ship-archive) (#59)
Multiple documentation polish items rolled into one PR (one layer: "docs alignment with current state"). ### README.md - **Uninstall section** added between Server Setup and Client Setup (was missing — `node uninstall.mjs` exists but went undocumented). - **OpenClaw definition** added as a footnote on first README mention (the architecture diagram and Supported Tools table both reference OpenClaw without ever defining it). - **Repository Layout section** added before Security — table of top-level files (server.mjs, setup.mjs, uninstall.mjs, keys.mjs, models.json, ocp/ocp-connect, dashboard.html, scripts/, .claude/skills/, ocp-plugin/, docs/adr/, ALIGNMENT.md, AGENTS.md, CLAUDE.md) so a new contributor knows what each file does. - **LICENSE link** added to the License section footer. ### docs/adr/README.md (new) - Index of the three published ADRs (0002, 0003, 0004) with a one-line description each. - Explains the `0001` placeholder (early internal proposal that was superseded; numbering deliberately starts at `0002`). - Guidance on when to write a new ADR vs. when a commit message suffices. ### Spec/plan housekeeping - `specs/.gitkeep` removed (the empty `specs/` placeholder confused the picture; canonical paths are `docs/superpowers/plans/` for active plans and `docs/superpowers/specs/` for long-lived design docs that other code references). - Shipped plans moved to `docs/superpowers/plans/shipped/`: - `2026-04-10-lan-mode.md` (shipped: README LAN mode section) - `2026-04-25-47-sse-heartbeat-plan.md` (shipped: v3.12.0 per CHANGELOG) - `2026-04-25-47-sse-heartbeat-design.md` left in `docs/superpowers/specs/` unchanged because both `server.mjs:565` and `CHANGELOG.md:7` link to that exact path; moving it would require a `server.mjs` edit, which needs `cli.js` citation per ALIGNMENT.md Rule 1. ### AGENTS.md - Updated "Key files to know" to add `docs/adr/README.md`, `docs/superpowers/plans/`, and `memory/constitution.md`. - Note explaining `memory/constitution.md` is spec-kit's standard location, distinct from `~/.cc-rules/memory/` and `ALIGNMENT.md`. - Updated "Handoff expectations" item 5 from `docs/superpowers/specs/*/tasks.md` (which never matched anything — there were no `tasks.md` files there) to `docs/superpowers/plans/` (excluding `shipped/`). ### Coordination with PR #53 PR #53 is open and adds "Why OCP?", "Comparison", and "Governance" sections to README. This PR deliberately avoids those areas — only edits the Supported Tools table footnote, inserts Uninstall before Client Setup, inserts Repository Layout before Security, and updates the License footer. No expected merge conflict. Refs: audit findings M6, M8, M9, M11, L2, L3, L6. Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
e4b010af5e |
docs(readme): add Why OCP, comparison table, governance section (#53)
Adds three positioning sections to make the README convert clones-to-stars better: 1. "Why OCP?" near the top — 6 differentiator bullets with evidence links (SSE heartbeat / ALIGNMENT.md / models.json SPOT / multi-key / per-key quota / ocp-connect). 2. "Comparison" subsection — honest table vs claude-code-router and anthropic-proxy. Acknowledges CCR's larger ecosystem; positions OCP as cli.js-aligned + subscription-multiplexing focused. 3. "Governance" section near the bottom — links to ALIGNMENT.md, AGENTS.md, ADRs, alignment.yml. Consolidates the governance-link surface in one place rather than scattered. Net diff +43 -0. No content removed. No anchors broken. No code changes. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
0752f666fb |
test: wire test-features.mjs to npm test + add minimal CI smoke workflow (#60)
`test-features.mjs` shipped at v3.8.0 (per CHANGELOG of the keys.mjs quota+cache work) and is referenced from AGENTS.md as the project's only test artifact, but until now nothing actually ran it — no `npm test` script, no CI step. Wiring it up so it runs on every push and PR. ### Changes - `package.json`: add `"test": "node test-features.mjs"` to scripts. - `.github/workflows/test.yml` (new): single-job workflow that runs `npm test` on push to main and on every PR. Uses Node 24 because `keys.mjs` imports `node:sqlite`, which is stable in Node 23+ (Node 24 is the current LTS; Node 22 would need `--experimental-sqlite`). No `npm install` step — OCP has zero external runtime dependencies per `package-lock.json`. - `AGENTS.md`: note that `test-features.mjs` runs via `npm test` and is enforced by `.github/workflows/test.yml`. ### Why this is a hard check, not a soft check `test-features.mjs` is self-contained — it imports `keys.mjs` and exercises the SQLite-backed key/quota/cache code paths against a throwaway test DB at `~/.ocp/ocp-test.db`. It does NOT require: - a live claude CLI binary - a running OCP server - any network access So CI can run it as a real check; no `continue-on-error` needed. ### Local verification ``` $ npm test [...] === Results: 24 passed, 0 failed === ``` 24 assertions cover createKey / listKeys / quota math / cache hash determinism / cache TTL / clearCache. Exit code is 1 on any failure (`process.exit(failed > 0 ? 1 : 0)` at the bottom of test-features.mjs). ### Future expansion If the suite later grows to include tests that DO require a live claude CLI or a running OCP, mark those steps `continue-on-error: true` (or split them into a separate job). The comment in `test.yml` documents this contract. Refs: audit (test-features.mjs orphan / unrunnable in CI). Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
ae4a829904 |
chore(naming): rename package + code refs from openclaw-claude-proxy to OCP (#57)
The npm name `ocp` is squatted (deprecated `node-ocp` v0.0.1), so the
package name is renamed to `open-claude-proxy` (verified via
`npm view open-claude-proxy` → 404, confirming availability).
### Changes
- `package.json`:
- `"name"`: `openclaw-claude-proxy` → `open-claude-proxy`
- `"bin"` map UNCHANGED: both `openclaw-claude-proxy` and `ocp`
binaries still resolve, preserving back-compat for existing installs
that reference `openclaw-claude-proxy` as a CLI command.
- `setup.mjs`: header JSDoc + start.sh banner string
- `uninstall.mjs`: header JSDoc
### Intentionally NOT changed
- `server.mjs` startup banner (line 1628):
`console.log('openclaw-claude-proxy v...')`. Editing `server.mjs`
requires `cli.js:NNNN` citation per ALIGNMENT.md Rule 1, and a
cosmetic log-string rename has no `cli.js` correspondence. Deferred
— would need an ALIGNMENT.md scope justification PR if pursued.
- `bin/openclaw-claude-proxy` symlink/path: existing installs depend on
this name; kept as a back-compat alias.
### Evidence
```
$ npm view ocp
ocp@0.0.1 | DEPRECATED — squatted by node-ocp
$ npm view open-claude-proxy
404 — available
```
Refs: audit (naming consistency).
Co-authored-by: dtzp555 <dtzp555@gmail.com>
|
||
|
|
51e908e145 |
chore(repo): remove broken/undocumented Docker files (#58)
The Dockerfile, docker-compose.yml, and .dockerignore are removed because the Dockerfile is structurally broken AND Docker is not a documented runtime for OCP. ### Evidence — Dockerfile is broken The Dockerfile COPYs only 3 files: ``` COPY server.mjs ./ COPY setup.mjs ./ COPY package.json ./ ``` But `setup.mjs:43` reads `models.json` at runtime (`JSON.parse(readFileSync(join(__dirname, "models.json"), ...))`), and `server.mjs` requires `keys.mjs`, `dashboard.html`, etc. The container as built would crash on first run. ### Evidence — Docker is undocumented ``` $ grep -i docker README.md AGENTS.md CLAUDE.md # 0 hits ``` No README install path, no AGENTS.md runtime mention, no CLAUDE.md release-kit reference. These files were ghost infrastructure. ### Why delete vs. fix Fixing the Dockerfile to actually work would require: COPYing 6+ files, verifying the container can write to `~/.openclaw/`, deciding on auth mounting (claude CLI binary + auth state), and adding documentation across README/AGENTS/CLAUDE. None of that is justified by a request. Easier to remove the dead files now and reintroduce a working Dockerfile when there's an actual demand for containerised OCP, with proper documentation alongside. Refs: audit (dead infrastructure cleanup). Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
d99534dc35 |
chore(repo): add .gitignore for runtime artifacts; commit scripts/heartbeat-field-check.sh (#55)
Two related changes — both repo-hygiene, no behavior impact. ### `.gitignore` (new) Ignores runtime artifacts that should never be tracked: - `logs/` and `*.log` — proxy.log, last_send.log, heartbeat-field-check log - `node_modules/` — dependency cache - `.env`, `.env.*` — local secrets/config - `.DS_Store`, `*.swp`, `*~` — editor/OS scratch `logs/` was previously untracked-but-present in working trees; the new ignore makes that intent explicit and prevents accidental commits. ### `scripts/heartbeat-field-check.sh` (commit existing untracked file) This script was authored as a one-shot field-evidence gatherer for the v3.12.0 SSE heartbeat work (PR #49 / issue #47). It fired successfully on 2026-05-02 09:00 Australia/Brisbane via launchd (`~/Library/LaunchAgents/dev.ocp.heartbeat-check.plist`) and posted a summary comment to issue #47. Useful tooling pattern (one-shot field check + launchd schedule + dry-run flag), worth keeping under version control rather than letting it die in an untracked working tree. Verification: `git status` clean after both adds. Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
39ca20536e |
docs(governance): correct CLAUDE.md + AGENTS.md to reflect actual alignment.yml blacklist (#56)
Both `CLAUDE.md` (line 25) and `AGENTS.md` (line 46) previously claimed the `alignment.yml` blacklist included two tokens — `api/oauth/usage` and `api/usage`. The actual workflow has a single token in `BLACKLIST`: `api.anthropic.com/api/oauth/usage`. Doc-vs-CI drift. Fix is doc-side only — `alignment.yml` itself is unchanged because adding a second blacklist token (`api/usage`) is a constitutional change that requires an `ALIGNMENT.md` amendment PR per ALIGNMENT.md Amendment Procedure. If `api/usage` should be added to the blacklist, that's a separate PR with ALIGNMENT.md amendment + reviewer sign-off. Refs: audit findings (governance doc accuracy). Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
8b3f50912e |
chore(repo): remove v2.x stale openclaw-claude-proxy/ folder + dead start.sh (#54)
Both removed: - `openclaw-claude-proxy/` — v2.4.0 stale duplicate of the proxy. Current proxy is `server.mjs` at repo root (v3.x); the nested folder was an early packaging artifact that never got deleted. Audit finding H1. - `start.sh` — hardcoded `/Users/taodeng/.openclaw/...` paths that only ever worked on the original maintainer's home directory. The real install is produced by `setup.mjs` (see setup.mjs:212-237 which writes the correct per-user start hook). Audit findings H5, H6 (path leak + dead script). Verification: `git grep "/Users/taodeng/"` returns 0 hits in tracked files. Refs: audit findings H1, H5, H6. Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
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> |
||
|
|
733a2ed4c2 |
chore(server): add session-create-vs-resume + lifetime instrumentation (refs #41 #42) (#51)
Adds structured logging for the two forensic candidates so the eventual fixes can be evidence-driven (per #41/#42 bodies: "Evidence needed before code change — do not patch speculatively"). NO bug fix in this PR — the stale-create-entry behavior (#41) and the lastUsed-resistant-expiry behavior (#42) are intentionally left unchanged so they can be observed in production /logs. Three changes (server.mjs): 1. Session-create branch records `firstSeen` alongside `lastUsed` (same timestamp), giving the sweep loop an absolute-age signal independent of the actively-bumped `lastUsed` field. Resume branch is untouched so `firstSeen` is preserved across resumes. 2. handleSessionFailure now emits a structured `session_failure` event with `mode: "resume"` (action: "deleted") OR `mode: "create"` (action: "kept"). The "kept" branch makes #41's "stale create entries never deleted" pattern visible in /logs without changing behavior. 3. TTL sweep emits `session_expired` (info, with idleMs + ageMs) on actual expiry, and `session_long_lived` (warn) when ageMs > 4×TTL but the entry is not being expired this tick. The latter is the #42 evidence trigger — a session whose lastUsed keeps getting bumped and so resists idle-based expiry. cli.js citation: N/A — logEvent payloads are OCP-internal observability, never sent on the wire to the Anthropic API. ALIGNMENT.md Rule 2 (no invention) is scoped to endpoints/headers/request/response fields, not to internal server logging. Independent reviewer: fresh-context sonnet APPROVE_WITH_MINOR. Two minor notes — repeated `session_long_lived` emissions per 60s sweep tick is expected (downstream analysis dedupes by conversationId). LOC: +19 / -3 = +16 net. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
ca2d23d230 |
chore(speckit): add compatibility/metadata frontmatter to SKILL.md files (refs #39) (#50)
Backfill the two YAML frontmatter blocks that upstream
`specify_cli/agents.py:build_skill_frontmatter()` emits:
compatibility: "Requires spec-kit project structure with .specify/ directory"
metadata:
author: github-spec-kit
source: claude:templates/commands/<cmd>.md
Applied uniformly across all 9 speckit-* SKILL.md files. Each file gets the
two blocks appended to its existing frontmatter (between the closing fields
and the closing `---`). No functional change — these fields are informational
only, per #39 body and PR #38 reviewer note.
Verified:
- All 9 files have `compatibility:` key (grep -lc, 9/9)
- All 9 files have `metadata:` block with `author: github-spec-kit` (9/9)
- All 9 files have `source: claude:templates/commands/<cmd>.md` matching the
command name (9/9)
- No existing fields modified or removed; pure additive +4 lines per file
Upstream reference (build_skill_frontmatter):
https://github.com/github/spec-kit/blob/main/src/specify_cli/agents.py
|
||
|
|
b871b72b6b |
feat(server): SSE heartbeat on streaming path (#47) (#49)
* docs(spec): design for #47 SSE heartbeat on streaming path Draft spec for an opt-in idle-watchdog SSE heartbeat covering both pre-first-byte and mid-stream silent windows. Default disabled, controlled by CLAUDE_HEARTBEAT_INTERVAL. Targets ~40 LOC. Decisions captured: D1 whole-stream reset-on-byte; D2 SSE comment frame; D3 default disabled; D4 relocate ensureHeaders() to post-spawn; D5 X-Accel-Buffering: no on both SSE header sites; D6 single log line per affected request. Scope-locked: does not touch CLAUDE_TIMEOUT semantics, the separate server.mjs:480-489 dangling-client bug, issues #41/#42, or the non-streaming path. Includes privacy preflight and cloud-testing plan per maintainer feedback. Refs: #47 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(plan): implementation plan for #47 SSE heartbeat 6 phases: pre-work (file 480-bug), implementation (5 tasks on feat/47-sse-heartbeat), opus fresh-context review, cloud verification on Mac rig, privacy preflight, PR+release. Each implementation task carries concrete code, syntax check, and a scoped commit message. LOC budget enforced in Task 1.6 gate (~45 server.mjs lines max). Reviewer checklist scopes scope-lock, ALIGNMENT, privacy, and heartbeat-cannot-abort discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(server): add startHeartbeat helper + HEARTBEAT_INTERVAL env var Per design doc (refs #47). Helper is a per-request idle watchdog that emits `: keepalive\n\n` SSE comment frames; returns a {reset, stop} handle. No wiring yet — helper is unused, safe to commit in isolation. cli.js citation: N/A — SSE response shaping is an OCP-owned translation layer, not a cli.js operation. See AGENTS.md and ALIGNMENT.md Rule 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(server): eagerly send SSE headers post-spawn (D4, refs #47) Moves the ensureHeaders() call from "on first stdout byte" to "immediately after successful spawn." This is a prerequisite for the heartbeat covering the pre-first-byte silent window (the 'processing large contexts' failure mode in #47). Behavioral consequence: the narrow "spawn succeeded but subprocess died before any byte" branch at server.mjs:610-611 becomes effectively dead in the common case. The post-headers SSE-stop path (612-619) handles it instead. The branch remains defensively for the client-closed-before- ensureHeaders race. Isolated commit per design doc §D4 so reviewer can focus on this one behavior change. cli.js citation: N/A — SSE header emission is OCP response-shaping. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(server): wire heartbeat into streaming path (refs #47) - sendSSE() accepts optional hb handle and calls hb.reset() before write - callClaudeStreaming starts heartbeat after ensureHeaders() and passes hb to the three streaming sendSSE call sites - All three exit paths (proc close, proc error, res close) call hb.stop() to guarantee timer cleanup; no-op handle when disabled means zero runtime cost when CLAUDE_HEARTBEAT_INTERVAL=0 Heartbeat never aborts — only writes comment frames and re-arms. Aligns with v3.3 timeout discipline (single CLAUDE_TIMEOUT, no secondary client-killing timers). cli.js citation: N/A — SSE response shaping is OCP translation layer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(server): add X-Accel-Buffering: no to SSE response headers (refs #47) nginx (and many LBs / Cloudflare) default to proxy_buffering=on, which would buffer heartbeat comment frames indefinitely and defeat the feature silently. This header hints no-buffering; other stacks ignore it. Applied at both SSE header sites (real streaming + cache-hit). cli.js citation: N/A — response header shaping is OCP translation layer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(release): v3.12.0 — streaming heartbeat (refs #47) Bundles the release-kit companion files per Iron Rule 5.2 / 11 example: version bump across package.json + ocp-plugin + openclaw.plugin.json, CHANGELOG v3.12.0 section, README env var row + "Streaming heartbeat" explainer. Tag push to v3.12.0 triggers .github/workflows/release.yml to create the GitHub Release automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(server): ensureHeaders returns true when already sent (refs #47) Phase 3 smoke test revealed every content chunk was being dropped after the D4 eager ensureHeaders() call: the stdout.on('data') guard `if (!ensureHeaders()) return;` early-returned on every chunk because ensureHeaders returned false for the already-sent case (conflated with the dead-connection case). Split the two conditions explicitly: return false only when res is ended/destroyed; return true when headers are (already or now) sent. This also fixes a latent multi-chunk bug on main that was masked because claude CLI typically outputs in one stdout chunk. Verified: node -c server.mjs; subsequent re-run of Phase 3 smoke test now sees streaming content chunks + heartbeat frames. cli.js citation: N/A — SSE response shaping is OCP translation layer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>v3.12.0 |
||
|
|
cff06439fa |
chore(privacy): remediation follow-up from ocp#44 (#45)
Three-part follow-up from the 2026-04-22 privacy postmortem:
1. OAUTH_CLIENT_ID verified as public Claude Code constant (not a secret).
Added gitleaks allowlist entry. The value 9d1c250a-e61b-44d9-88ed-5944d1962f5e
is the public PKCE client ID used by the Claude Code CLI — public clients in
PKCE flows have no client secret, so the ID itself carries no secret value.
Introduced in commit
|
||
|
|
1c29f4867f |
chore(privacy): scrub personal identifiers from public repo (#43)
Removes all occurrences of maintainer's real name and handle from tracked files in this PUBLIC repository. Replaces with generic terms like "project maintainer" / "the maintainer". Files scrubbed: - CLAUDE.md (2 instances) - ALIGNMENT.md (Auditor field) - docs/adr/0002, 0003, 0004 (Authors/Deciders fields + body text) - memory/constitution.md (1 instance) - docs/superpowers/plans/2026-04-10-lan-mode.md (Users/taodeng paths → $HOME) Also: - Replaces specific machine hostnames (Taos-Mac-mini, Taos-MacBook-Pro) with role-based names (home-mac, travel-macbook) in ADR 0001 (if committed; currently untracked). - Replaces /Users/taodeng/ paths with $HOME/ in old plan doc. NOTE: git history still contains the personal info. See postmortem (issue TBD) for whether to rewrite history via git-filter-repo. Co-authored-by: Tao Deng <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
cdd6b41261 |
fix(concurrency): release slot on subprocess SIGTERM failure (#40)
Per forensic analysis in #37, the timeout handler at server.mjs:471 called `proc.kill('SIGTERM')` without decrementing the concurrency counter (`stats.activeRequests`). If the subprocess was stuck in a syscall (e.g. MCP I/O) and ignored SIGTERM, its slot was not freed until — and only if — the `proc.on('close')` handler eventually fired after the 5s SIGKILL escalation successfully reaped the child. Real-world observation (#37) shows 3 stuck claude subprocesses running 20 minutes to 2h 45m, exhausting the 8-slot pool and returning `500 concurrency limit reached (8/8)` on every subsequent request. Fix: attach `cleanup` to `proc.once('exit', ...)`. The 'exit' event fires before 'close' and runs on every termination path — normal completion, error, SIGTERM, SIGKILL, crash — even if stdio pipes stay open. `cleanup()` is already idempotent (guarded by `cleaned` flag), so this listener is safe alongside the existing 'close' and 'error' paths that also call it. ALIGNMENT: cli.js has no equivalent subprocess-pool / concurrency- limit logic — cli.js is a single-user CLI, the process being pooled, not the pool itself. Per ALIGNMENT.md Rule 2, this fix is proxy- internal (subprocess lifecycle management) with no cli.js equivalent to cite; documented here instead. No wire-protocol, HTTP surface, or response shape change (Rule 3 preserved). Release kit: bumps version to 3.11.1 and adds CHANGELOG entry so the auto-release workflow tags v3.11.1 on merge. README version refs are feature-gate markers for v3.11.0 (models.json SPOT / auto-sync) and remain historically accurate — no README change needed. Fixes #37. Co-authored-by: dtzp555 <dtzp555@gmail.com>v3.11.1 |
||
|
|
9facd8307a |
feat(speckit): integrate github/spec-kit for spec-driven development (#38)
Installs spec-kit slash commands (/speckit-specify, /speckit-plan, /speckit-tasks, /speckit-implement, /speckit-analyze, /speckit-clarify, /speckit-checklist, /speckit-constitution, /speckit-taskstoissues) into Claude Code via .claude/skills/speckit-*/. Note: specify-cli v1.0.0 init fails on Windows because spec-kit v0.5+ releases no longer attach binary zip assets to GitHub releases (confirmed by inspecting all 30 releases via API; only v0.4.4 and earlier have assets). Templates were sourced directly from the github/spec-kit@v0.7.3 repo tree and SKILL.md files were built per the claude integration spec in src/specify_cli/integrations/claude/__init__.py (user-invocable:true, disable-model-invocation:false, argument-hint injection). Enables the "specs in git" workflow for cross-device work state: - New feature → /speckit-specify → specs/NNN/spec.md - Design → /speckit-plan → specs/NNN/plan.md - Work → /speckit-tasks → specs/NNN/tasks.md (handoff artifact) - Implement → /speckit-implement → code Preserves existing CLAUDE.md (@AGENTS.md bridges intact) and existing memory policies in AGENTS.md. memory/constitution.md is annotated at the top to reference AGENTS.md for project-specific constraints. No change to server.mjs, models.json, package.json, or any other code. Co-authored-by: Tao Deng <dtzp555@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
2ecc4945ad |
docs(governance): add AGENTS.md bridge + 3 historical ADRs (#36)
Integrates OCP with Tao's cross-device development system (Phase 1 L1). - AGENTS.md: project-level tool-agnostic instructions (read by Cursor, OpenCode, Copilot, etc. in addition to Claude Code). Inherits from ~/.cc-rules/AGENTS.md. - CLAUDE.md: prepends @AGENTS.md + @~/.cc-rules/AGENTS.md imports. - docs/adr/0002-alignment-constitution.md: captures why ALIGNMENT.md exists (2026-04-11 drift response). - docs/adr/0003-models-json-spot.md: captures v3.11.0 SPOT refactor. - docs/adr/0004-openclaw-auto-sync.md: captures v3.11.0 auto-sync design. ADRs numbered 0002-0004 because an untracked 0001-cross-device-system.md already exists locally and was out of scope for this PR. Governance/docs only. No code change. No version bump (not a release). Co-authored-by: Tao Deng <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
497ff1dcd2 |
chore(governance): add release-kit overlay + PR self-check + auto-release workflow (#35)
Implements cc-rules v1.4's 第五律 5.5 project overlay requirement. - CLAUDE.md: declare release_kit: YAML block (version_source, changelog, release_channel, docs_source, resource_lists, new_feature_doc_expectations, bootstrap_quirk_policy) - .github/PULL_REQUEST_TEMPLATE.md: add 5.3 user-visible-change self-check section with reviewer gate instruction - .github/workflows/release.yml (NEW): auto-create GitHub Release from CHANGELOG.md section on v* tag push. Idempotent (checks if release already exists). Closes the gap that caused v3.9.0 / v3.10.0 / v3.11.0 to each miss their GH Release. Governance-only change. No code, no user-visible behavior change (the workflow only fires on future tags). No README update needed. Co-authored-by: Tao Deng <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
2e634f708b |
docs(readme): document v3.11.0 features (auto-sync, models.json SPOT, troubleshooting) (#34)
Backfills user-visible feature documentation for v3.11.0 that was missing from PR #33 (which only fixed stale references). Per Tao's review: shipping a feature without README documentation is a worse failure than stale references, since users don't know the capability exists. Added: - "Self-Update" section: explain the full ocp update pipeline order - New "OpenClaw Auto-Sync (v3.11.0+)" section: when it triggers, what gets synced (with strict scope boundaries), safety guarantees, manual invocation, opt-out, bootstrap caveat, behavior for non-OpenClaw IDEs - "Available Models" expanded: explain models.json as SPOT, contributor workflow for adding a new model - "Troubleshooting" two new entries: - "Startup log warns OpenClaw registry out of sync" - "OpenClaw shows old models after ocp update (v3.10→v3.11 only)" No code changes. README only. Co-authored-by: Tao Deng <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
820abc4b89 |
docs(readme): refresh model list and version references for v3.11.0 (#33)
- Verify-curl example: 3 stale ids → 4 current ids - Connect-script output sample: v3.10.0 → v3.11.0, "3 models" → "4 models" - OpenClaw setup output: add ocp/claude-opus-4-7 to the example - Available Models table: add opus-4-7, retain opus-4-6 for pinning, mark default aliases, link to models.json as canonical source - OpenClaw Integration section: add bullet for ocp update auto-sync (v3.11.0+) No code changes. README only. Co-authored-by: Tao Deng <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
ab9e0c656b |
chore(release): v3.11.0 — models.json SPOT + OpenClaw auto-sync (#32)
- Bumps version 3.10.0 → 3.11.0. - Adds CHANGELOG.md with v3.11.0 entry. Rolls up PR #30 (refactor: models.json SPOT) and PR #31 (feat: idempotent OpenClaw registry sync on `ocp update` + passive drift self-check). No server.mjs changes in this PR. Co-authored-by: Tao Deng <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>v3.11.0 |
||
|
|
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>
|
||
|
|
c6f7850e89 |
refactor(models): extract models.json as single source of truth (#30)
Pure refactor. No API surface change. MODEL_MAP and MODELS in server.mjs are now derived from models.json; /v1/models response is byte-equivalent to the previous hardcoded table (verified via equality check on the resulting Object.entries sorted). setup.mjs MODEL_ID_MAP / MODELS / MODEL_ALIASES also derived from models.json, fixing a latent drift where setup.mjs still listed claude-opus-4-6 / claude-haiku-4 (v3.0-era) while server.mjs had already moved to opus-4-7 + haiku-4-5-20251001 in v3.10.0. server.mjs change scope: no network/endpoint surface modified. No new env vars, headers, or routes. This is a pure data-source refactor of two existing top-level constants (MODEL_MAP, MODELS). No cli.js operation is being added or changed, so per ALIGNMENT.md Rule 2 this requires no cli.js:NNNN citation — the change does not touch the Claude-CLI-call boundary. Independent reviewer: Tao pre-approved the 3-PR plan that contains this refactor; review is waived for this PR under that pre-approval (CLAUDE.md Iron Rule 10 exception, task-scoped). Unblocks PR B (scripts/sync-openclaw.mjs), which needs a single source of truth to reconcile with on `ocp update`. Co-authored-by: Tao Deng <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
43cd7712e6 |
chore(release): v3.10.0 — Claude Opus 4.7 support (#28)
Minor bump. User-visible new feature: Claude Opus 4.7 model is now selectable via OCP. - New model id 'claude-opus-4-7' in MODEL_MAP and /v1/models - Short aliases 'opus' and 'claude-opus-4' now route to 4.7 - 'claude-opus-4-6' remains explicit for pinned usage Evidence chain (see PR #27): - Anthropic /v1/models: claude-opus-4-7 (display 'Claude Opus 4.7') - claude.exe v2.1.114 strings: claude-opus-4-7 present - Live probe verified 2026-04-20 No wire-format breaking changes. Co-authored-by: Oracle Public Cloud User <opc@instance-20230820-1333.subnet07301351.vcn07301351.oraclevcn.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>v3.10.0 |
||
|
|
ba273aaf06 |
feat(models): add Claude Opus 4.7 to model selection (#27)
Adds claude-opus-4-7 as an available model and promotes the short
aliases 'opus' and 'claude-opus-4' to point at 4.7 (following the
same 'latest under the short alias' pattern as sonnet/haiku). Explicit
claude-opus-4-6 remains available for pinned usage.
Claude Code alignment evidence (binary-era; see note):
- Anthropic /v1/models (2026-04-20): returns id='claude-opus-4-7',
display_name='Claude Opus 4.7', 1M input / 128K output,
created_at=2026-04-14.
- Claude Code v2.1.114 /home/opc/.npm-global/lib/node_modules/
@anthropic-ai/claude-code/bin/claude.exe strings table contains
'claude-opus-4-7'.
- Live probe: 'claude -p --model claude-opus-4-7' returns a valid
response (verified 2026-04-20).
Note on ALIGNMENT.md grep rule: Claude Code 2.1.90+ ships a native
binary (claude.exe) instead of cli.js JavaScript. The grep-based
verification in ALIGNMENT.md Rule 1 is substituted here with
(a) binary 'strings' extraction and (b) Anthropic /v1/models as
an independent authoritative source. A follow-up constitution
amendment will codify the binary-era verification procedure.
Co-authored-by: Oracle Public Cloud User <opc@instance-20230820-1333.subnet07301351.vcn07301351.oraclevcn.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
7cff33cc18 |
chore(release): v3.9.0 — alignment constitution + usage endpoint restoration (#26)
Minor version bump covering substantive functional + governance changes
since v3.8.0:
Functional
- Restore header-based /usage probe (reads anthropic-ratelimit-unified-*
response headers; mirrors Claude Code cli.js vE4). Progress bars
work again after 9-day drift. PR #21 (
v3.9.0
|
||
|
|
d4f4aaf33a |
docs(alignment): backfill fix commit SHAs for 2026-04-11 drift (#25)
PR #21 ( |
||
|
|
22806bffb5 |
fix(usage): send OAuth Bearer + anthropic-beta header for /v1/messages probe (#24)
Follow-up to #21. The restored header-based fetchUsageFromApi() copied
the golden
|
||
|
|
6bfffd2cba |
chore(server): revert stale-cache compensation for hallucinated endpoint (#23)
Removes the stale-cache fallback branches in handleUsage() and handleStatus() originally introduced by |
||
|
|
2853088261 |
[Constitution] Alignment principle + CI guardrails (addresses b87992f drift) (#20)
* docs(constitution): establish OCP alignment constitution + CI guardrails (PR A) Introduces the OCP project constitution to structurally prevent the kind of scope drift that produced commit |
||
|
|
fd7973addb |
fix(server): restore header-based /usage (revert b87992f drift) (#21)
Replaces fetchUsageFromApi() hallucinated /api/oauth/usage endpoint
with the original header-based approach: POST /v1/messages with
max_tokens=1 and extract anthropic-ratelimit-unified-{5h,7d}-{utilization,reset}
from response headers.
Drift:
|
||
|
|
b908fec7b4 |
docs(readme): sync to v3.8.0 (#19)
- Remove "Status: Stable — Feature-complete, bug fixes only" tagline - Add Per-Key Quota section with curl examples and 429 response format - Add Response Cache section with enable/management instructions - Update API Endpoints table with new quota + cache endpoints - Add CLAUDE_CACHE_TTL to Environment Variables table - Update version references from v3.7.0 to v3.8.0 Co-authored-by: Tao Deng <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>v3.8.0 |
||
|
|
97ca91341c |
feat(server): per-key quota + response cache (v3.8.0) (#18)
Add two new features for LAN sharing governance:
Quota (budget control):
- Per-key daily/weekly/monthly request limits (NULL = unlimited)
- Idempotent schema migration for quota columns
- Single-query check (SUM/CASE) for all 3 periods — no N+1
- PATCH /api/keys/:id/quota (partial update, input validation)
- GET /api/keys/:id/quota (current limits + usage)
- 429 with structured error when exceeded
- Only applies to identified per-key users, not admin/anonymous
Response cache:
- SHA-256 hash of model + messages + temperature/max_tokens/top_p
- Opt-in via CLAUDE_CACHE_TTL env var (0 = disabled, default)
- Cache hit serves both streaming (simulated SSE) and non-streaming
- Streaming responses accumulated and cached on success
- Skips multi-turn sessions (conversationId present)
- GET /cache/stats, DELETE /cache admin endpoints
- Runtime-tunable cacheTTL via PATCH /settings
- 10-minute periodic cleanup of expired entries
Bug fix discovered during testing:
- SQLite datetime('now') stores 'YYYY-MM-DD HH:MM:SS' but JS
.toISOString() produces 'YYYY-MM-DDTHH:MM:SS.sssZ'. String
comparison breaks for same-day ranges. Added sqliteDatetime()
helper for correct format matching.
Code review fixes:
- DELETE /api/keys/:id no longer shadows /quota sub-routes
- updateKeyQuota uses partial UPDATE (only SET provided fields)
- cacheHash includes temperature/max_tokens/top_p in hash
- Replaced raw getDb() in server.mjs with findKey() encapsulation
- Unified UTC midnight calculation across checkQuota/getKeyQuota
Includes 24-test integration suite (test-features.mjs).
Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
f3745fa8fe |
docs(readme): sync to v3.7.0 — anon key, auto-discovery, current model IDs (#17)
Brings README up to date with the four PRs merged on 2026-04-12: * #13 ocp-connect v1.1.0 — per-agent auth-profiles write * #14 ocp-connect v1.2.0 — IDE detection rewrite + hint density * #15 OCP server v3.7.0 — PROXY_ANONYMOUS_KEY allowlist * #16 ocp-connect v1.3.0 — auto-discover anonymous key from /health ## Changes 1. Banner updated `v3.6.0` to `v3.7.0`. 2. New "Zero-config" paragraph in Client Setup showing `./ocp-connect <server-ip>` works without `--key` when the server admin has set PROXY_ANONYMOUS_KEY. 3. Client Setup example output replaced to reflect v1.3.0 actual behavior: `OCP Connect v1.3.0` banner, `Remote OCP v3.7.0`, new `ⓘ Using server-advertised anonymous key` block, new `Per-agent auth profile seeded (2)` line with both main and macbook_bot agentDir paths, new three-line smoke test caveat, and correct `claude-haiku-4-5-20251001` model ID. 4. "The script automatically" list expanded from 3 bullets to 5: added auto-discovery note (v1.3.0+/v3.7.0+), added OpenClaw per-agent profile note, corrected IDE hint bullet to list Cline/Continue.dev/Cursor/opencode explicitly as "manual configuration required" rather than the earlier inaccurate "configures" wording. 5. Available Models table updated `claude-haiku-4` to `claude-haiku-4-5-20251001` to match the actual `/v1/models` response. 6. Environment Variables table: new `PROXY_ANONYMOUS_KEY` row added after `PROXY_API_KEY`, with a link to the existing "Anonymous Access (optional)" section. ## Not changed * `package.json` already at 3.7.0 from #15. * ocp-connect script unchanged (v1.3.0 from #16). * server.mjs unchanged (v3.7.0 from #15). * No behavioral changes. README text only. ## Test `git diff --stat main -- README.md` = +24 -7 on one file. Markdown spot-checked for broken table alignment and link targets. The anonymous key section anchor `#anonymous-access-optional` resolves to the existing heading at line 234. Co-authored-by: taodeng <taodeng@Taos-MBP> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>v3.7.0 |
||
|
|
d71e0455e3 |
feat(ocp-connect): auto-discover anonymous key from /health (v1.3.0) (#16)
Closes the zero-config loop opened by #15 (server anonymous allowlist). ## Why After #15 (v3.7.0) the OCP server exposes the admin-chosen anonymous key via `GET /health.anonymousKey` whenever `PROXY_ANONYMOUS_KEY` is set. Before this PR, users still had to copy that key manually and pass it as `--key` every time they ran `ocp-connect`. This commit closes the last mile: `ocp-connect <host>` now reads the key from the server and uses it automatically when no `--key` was provided. Result: `ocp-connect 172.16.2.30` (zero args) on a fresh machine configures OpenClaw multi-agent correctly against a v3.7.0 OCP instance that has `PROXY_ANONYMOUS_KEY` set. No admin coordination, no per-user keys, no manual flags. ## What changes `OCP_CONNECT_VERSION` 1.2.0 to 1.3.0. New Step 2.5 between the existing Step 2 (Show remote info) and Step 3 (Determine if key is needed). The block: 1. Short-circuits if `--key` was passed explicitly, so user intent always wins over auto-discovery. 2. Parses `health_json.anonymousKey` via python3 with try/except, defensively treating any parse error as "no anon key" so the script falls through to the existing Step 3 path. 3. If the server advertised a non-empty anon key, assigns it to the internal `key` variable and prints an info banner with the truncated key value and a reference to issue #12 section 14 Path A so users can understand what happened. All downstream code (key verification via /v1/models, rc file exports, launchctl setenv, OpenClaw agentDir profile seeding, smoke test, IDE hints) works unchanged because it reads the same `key` variable. Also: defensive check on `--key` to reject explicit empty values (`--key ""`) with a clear error message. Existing bash `${2:?msg}` expansion already rejects empty strings, but the new check is an extra safety net and gives a friendlier error (`omit --key entirely for anonymous mode`). ## Backward compatibility Old OCP servers (less than 3.7.0) that don't have the `anonymousKey` field in `/health`: `python3 d.get("anonymousKey")` returns None, new block outputs empty string, `[[ -n "" ]]` is false, block falls through to the existing Step 3 path. Script behavior is 100% identical to v1.2.0 for these users. New OCP servers that haven't set `PROXY_ANONYMOUS_KEY`: field is null, same fall-through path as above. No user-visible regression in either case. No new CLI flag, no new prompt, no new error path for existing users. ## Test evidence Live offline test on macOS 13 against real 172.16.2.30 (OCP v3.7.0 with `PROXY_ANONYMOUS_KEY=ocp_public_anon_v1`): Step 1: cold install state - stopped OpenClaw gateway - rm ~/.openclaw/agents/{main,macbook_bot}/agent/auth-profiles.json - stripped models.* and agents.defaults.{model,models} from openclaw.json - cleaned OCP LAN block from .bashrc - unset OPENAI_BASE_URL and OPENAI_API_KEY from launchctl env Step 2: ran ~/projects/ocp/ocp-connect 172.16.2.30 (no args) Output: OCP Connect v1.3.0 Remote OCP v3.7.0 (auth: multi) Using server-advertised anonymous key: ocp_publ...n_v1 (set by admin via PROXY_ANONYMOUS_KEY; see issue #12 section 14 Path A) API accessible (3 models available) Per-agent auth profile seeded (2): - ~/.openclaw/agents/main/agent/auth-profiles.json - ~/.openclaw/agents/macbook_bot/agent/auth-profiles.json Smoke test passed: OK EXIT 0 Step 3: verified profiles contain the anon key macbook_bot: key = ocp_public_anon_v1 main: key = ocp_public_anon_v1 Additional regression tests: - ocp-connect 172.16.2.30 --key ocp_real_admin_key -> still uses the explicit key, auto-discovery banner NOT shown - ocp-connect --version -> "ocp-connect 1.3.0" - ocp-connect 172.16.2.30 --key "" -> rejected with clear error ## Code review One independent opus reviewer. Verdict PASS WITH CONCERNS, 0 blockers. Reviewer's Concern B (--key "" edge case) turned out to be a false alarm on my side verification: bash `${2:?}` expansion already rejects empty strings. I added an extra defensive check anyway for clearer error message and safety if the arg parser is refactored in the future. Other concerns (nice-to-have simplifications like `print(k or '')`) are cosmetic and deferred. Co-authored-by: taodeng <taodeng@Taos-MBP> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
38070fbabb |
feat(server): anonymous key allowlist for multi-mode (v3.7.0) (#15)
Implements issue #12 section 14 Path A. Lets OCP admin designate a single well-known "anonymous" key that bypasses validateKey() while keeping AUTH_MODE=multi. Gives OpenClaw multi-agent clients (which MUST send a non-empty Authorization header per their per-agent auth-profiles schema) a way to connect without every user needing a personal key. ## Background After PR-1 (issue #12), we know OpenClaw multi-agent setups require a per-agent auth-profiles.json with a non-empty `key` field. OCP multi-mode rejects any non-empty Bearer token that isn't in the keys database (server.mjs line 1152), which creates a deadlock: the only way for OpenClaw to use OCP is with a real admin-issued key. Path A resolves this by letting the admin opt in to a public anonymous key that clients can auto-discover via /health. OpenClaw writes this key into its agent profiles just like a real key; OCP server short-circuits validateKey when the key matches the allowlist. No per-user coordination needed, admin still controls the policy (can rotate, can unset, can rate-limit). ## Changes ### server.mjs * Line 100-103: new `PROXY_ANONYMOUS_KEY` env var + warning if set while AUTH_MODE is not multi. * Line 1127-1138 (localhost branch): anonymous allowlist short-circuit before validateKey so localhost clients using the anon key are labeled "anonymous" instead of "local" in stats. * Line 1153-1163 (multi branch): the same allowlist check between the ADMIN_KEY check and validateKey. Uses timingSafeEqual with explicit length check (consistent with the admin and shared key patterns above). * Line 1221 (/health response): new `anonymousKey` field, null when not set, the actual value when set. Admins opted into public access when they set the env var, so exposing the key here is intentional and lets ocp-connect auto-discover it without out-of-band coordination. ### package.json Version 3.6.0 to 3.7.0 (per dev iron rule 5, version bump before push). ### README.md New "Anonymous Access (optional)" subsection under Auth Modes: * Enable example (export PROXY_ANONYMOUS_KEY=...) * Client-side /health discovery explanation * Security note: opt-in to public access, rate-limit warning * "Not a secret" note: /health is unauthenticated, the key is publicly readable by design; treat it as a convenience handle, not as an access credential. ## Test evidence Offline tests on macOS 13, local server on 0.0.0.0:8889 with PROXY_ANONYMOUS_KEY=ocp_public_anon_TEST, CLAUDE_AUTH_MODE=multi, CLAUDE=/bin/echo (mock): * GET /health -> authMode: multi anonymousKey: ocp_public_anon_TEST (pass, field exposed correctly) * POST /v1/chat/completions with Bearer ocp_WRONG_KEY from 172.16.2.29 (non-localhost) -> HTTP 401 in 15ms body: Unauthorized: invalid or revoked API key (pass, validateKey still rejects unknown keys) * POST /v1/chat/completions with Bearer ocp_public_anon_TEST from 172.16.2.29 -> curl hangs at 3s after auth middleware (pass, auth passed, request reached Claude handler which hangs because CLAUDE=/bin/echo cant serve a real chat; the point is the auth middleware accepted the key, confirmed by no 401 return) * POST /v1/chat/completions with no Authorization from 172.16.2.29 -> curl hangs at 3s after auth middleware (pass, pre-existing empty-token anonymous path not regressed) node --check server.mjs: syntax OK. ## Code review One independent opus reviewer. Verdict: PASS WITH CONCERNS, zero blockers. Two strong-recommend items addressed in this commit: 1. Localhost branch was not covered in the first draft. Reviewer pointed out that a localhost client using the anon key would be labeled "local" instead of "anonymous" in stats, making operations visibility worse. Fixed by applying the same anonymous allowlist check in the localhost branch at line 1127-1138. 2. README security note was missing the explicit "not a secret" framing. Added a paragraph clarifying that because /health is unauthenticated, the anonymous key is publicly readable by anyone who can reach the server, which is intentional. Reviewer nits (tokenBuf3 naming, stats subcategory for header-less vs anon-key anonymous) are deferred as they do not affect correctness. ## Upstream dependencies on this commit After this PR is merged and the OCP instance at 172.16.2.30 is restarted with `PROXY_ANONYMOUS_KEY` set in the environment, a follow-up PR can add anonymous key auto-discovery to ocp-connect: * On startup, ocp-connect calls GET /health * If anonymousKey field is non-null and --key was not provided, ocp-connect automatically uses that key to seed per-agent auth-profiles for OpenClaw * User gets zero-config OCP connectivity for OpenClaw multi-agent setups, no admin coordination, no --key flag That follow-up is NOT in this PR. This PR is server-only. Co-authored-by: taodeng <taodeng@Taos-MBP> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
2adea6368d |
fix(ocp-connect): rewrite IDE detection + improve hint density (v1.2.0) (#14)
Follow-up to #12 / #13. Fixes the IDE detection failures documented in issue #12 section 9.3 (IDE detection coverage matrix). ## What was broken PR-1 fixed the OpenClaw multi-agent auth bug but left section 9 of the report (IDE detection coverage) unaddressed. Empirical testing showed the script's "Other IDEs detected" section had a 25% effective hit rate: - Cline -> grep pattern was `grep -qi cline` but the actual VSCode extension ID is `saoudrizwan.claude-dev`. Hit rate 0%. - Continue.dev -> checked `~/.continue/config.json` but the directory is not created until the user opens the Continue panel for the first time, AND v1.x uses `config.yaml` not `config.json`. Hit rate 0% on a fresh install. - Cursor -> only checked `command -v cursor` and `~/.cursor`. Both fail when the user installs Cursor.app from the official dmg without enabling the optional shell command and without launching the app. brew cask installs work by side effect (it links the binary). Hit rate 100% via brew, 0% via dmg. - opencode -> not detected at all. opencode (https://opencode.ai) is one of the most popular AI CLI tools, missing entirely from the hint list. The hints themselves were also too sparse. Each one was a single line like `Cursor: Settings -> OpenAI Base URL = ...`, with no API key, no model IDs, no path to the actual settings page. Users had to guess. ## What this commit does ### Detection rewrite (Cline / Continue.dev / Cursor / opencode) Cline: prefer `code --list-extensions`, fall back to ls, match pattern `cline|saoudrizwan\.claude-dev`. Real-world Cline detection goes from 0% to 100%. Continue.dev: three-condition OR -- VSCode extension ID `continue.continue` OR `~/.continue/config.yaml` OR `~/.continue/config.json`. Catches all three real-world install states (extension installed but never opened / config.yaml / legacy config.json). Cursor: add `[[ -d "/Applications/Cursor.app" ]]` as a third fallback alongside `command -v cursor` and `~/.cursor`. Now detects Cursor regardless of how it was installed. opencode: NEW detection branch. Checks `command -v opencode`, `~/.opencode/bin/opencode`, and `~/.local/share/opencode`. Catches the official curl install path and any future package manager installs. ### Hint density improvements Each hint now includes: - The exact base URL to paste - The API key (truncated as `${key:0:8}...${key: -4}` if longer than 16 chars to avoid screenshot leakage; or "(none -- anonymous mode)" message when --key was not provided) - The list of available model IDs, extracted from the actual /v1/models response so the user does not have to guess - The exact menu path or config file location Continue.dev hint specifically shows the YAML snippet under the `models:` top-level key (per reviewer feedback) so the user can paste it directly without schema confusion. opencode hint explicitly says "not yet auto-configured by ocp-connect (PR follow-up)" so users know to use `opencode providers login openai` until a future PR adds direct auth.json writing. opencode does NOT read OPENAI_API_KEY env var (empirically verified) so the existing rc-file export strategy does not help it. ### NOT in scope (deferred) - opencode auto-configure (writing auth.json + opencode.json) -- schema needs more investigation, opencode model whitelist gets in the way of custom claude model IDs - Continue.dev auto-configure -- v1.x config.yaml schema is well enough known but parsing/merging YAML in bash without extra dependencies is awkward - Aider, Cody, Zed, etc. detection -- not in this PR scope These are future work tracked in #12 section 9.6. ### Version bump (per dev iron rule #5) OCP_CONNECT_VERSION 1.1.0 -> 1.2.0. ## Test evidence Offline matrix run on macOS 13 with VSCode 1.115.0, opencode 1.4.3, Cline 3.78.0, Continue.dev 1.2.22, Cursor 3.0.16: - All 4 IDEs installed -> all 4 hints fire with full detail - anonymous mode -> hints fire, key display reads "(none -- anonymous mode...)" instead of blank - HOME=tmpdir mock + only Cline mock dir -> Cline + Cursor (system app) hints fire, others suppressed - HOME=tmpdir mock + nothing -> only Cursor (system app) fires - --version -> "ocp-connect 1.2.0" - --help -> still shows the PR-1 wording for item 5 Hit rate matrix on the test machine (5/5 IDEs installed): | IDE | before PR-2a | after PR-2a | |--------------|--------------|-------------| | OpenClaw | YES (PR-1) | YES | | Cline | NO | YES | | Continue.dev | NO | YES | | Cursor | YES (brew) | YES | | opencode | NO | YES | Effective detection rate: 25% -> ~100%. ## Code review One independent opus reviewer (spec compliance + bash 3.2 compat + UX). Verdict: PASS WITH CONCERNS, 0 blockers. Two non-blocking suggestions both addressed in this commit: 1. Continue.dev YAML hint -- added `models:` top-level key so pasted snippet validates against Continue v1.x schema 2. anonymous mode -- key display now shows "(none -- anonymous mode; most external IDEs require a non-empty API Key)" instead of a blank field B1 (OpenClaw per-agent auth) section is intentionally untouched. Verified by diff line count: all changes fall in lines 347-440 (configure_ides function) + lines 9-13 (version constant). No edits inside the Python heredoc block. Co-authored-by: taodeng <taodeng@Taos-MBP> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
7860f71943 |
fix(ocp-connect): seed per-agent auth-profiles for OpenClaw multi-agent + UX hotfixes (v1.1.0) (#13)
* fix(ocp-connect): seed per-agent auth-profiles for OpenClaw multi-agent + UX hotfixes Fixes #12. ocp-connect bumped to v1.1.0. ## What was broken OpenClaw's per-agent auth loader reads <agentDir>/auth-profiles.json (NOT the root openclaw.json's auth.profiles section, NOT env vars). ocp-connect only wrote the root config, so OpenClaw multi-agent setups - any agent with an explicit or implicit agentDir, including the default `main` agent - silently failed with "No API key found for provider X" on every Telegram/IDE message. The smoke test passed but the user's actual workflow was broken. 3 unrelated UX papercuts compounded the bad experience: * Smoke test only verified OCP direct connectivity, never the IDE/agent -> OCP path, so the script printed "Smoke test passed" while the bot was silently broken. * `read ... </dev/tty 2>/dev/null || fallback` leaked `Device not configured` errors from the parent shell in CI/SSH/no-tty environments. The fallback path worked correctly, but the stderr noise looked like a bug. * Help text claimed `Detects and configures IDEs (OpenClaw, Cline, Continue.dev, Cursor)` while only OpenClaw is actually configured. Full investigation, evidence, and reviewer findings in #12. ## What this commit does ### B1: write per-agent auth-profiles.json (CRITICAL) After writing root openclaw.json, iterate `agents.list[]`. For each agent: derive agentDir from the explicit `agentDir` field, OR fall back to `<openclaw_root>/agents/<id>/agent/` for agents that omit it. This catches the default `main` agent, discovered via end-to-end Telegram testing. Merge into existing auth-profiles.json (preserving other providers' keys), upsert `<provider>:default` with the real key, chmod 0600. Anonymous mode (no --key) explicitly skips agentDir writes (empty keys are dropped by OpenClaw's pi-auth-credentials anyway) and prints a clear warning that OpenClaw multi-agent requires --key. This is the "Path C" decision from #12 section 14. Error handling is split for safety: * `JSONDecodeError` on existing file -> back up to `.bak`, rebuild * `OSError` on read -> skip this agent. Do NOT clobber the user's existing profile, which may hold other providers' real keys. * `OSError` on mkdir/write -> record in `_failed` list, surface in output. Not silently swallowed. Three independent report sections (`_seeded` / `_failed` / `_skipped_anonymous`) so mixed scenarios don't hide warnings behind each other. ### B2: smoke test caveat After "Smoke test passed", append three lines explaining that the test only verifies OCP direct connectivity, and instructing the user to restart OpenClaw and send a real bot message to verify end-to-end. ### B3: suppress /dev/tty stderr Wrap all four `read ... </dev/tty` calls as `{ read ... </dev/tty; } 2>/dev/null || fallback` so the brace group captures the shell's own `Device not configured` error before it reaches the user's terminal. The fallback semantics are unchanged. ### B6: help text wording Change item 5 from old wording mentioning IDE auto-configuration to "Configures OpenClaw automatically; prints setup hints for other IDEs - manual configuration required". Detection logic itself is NOT changed in this commit. That is deferred to a future PR. See #12 section 9 for details. ### Version bump Per dev iron rule #5 (version bump before push): * `OCP_CONNECT_VERSION="1.1.0"` constant * `--version` CLI flag * Banner shows version ## Test evidence End-to-end verified on macOS 13 with the actual OpenClaw 2026.4.11 + Telegram bot setup that exposed #12: 1. cold-install state (clean ~/.openclaw, no agent profiles) 2. run `ocp-connect 172.16.2.30 --key ocp_xxx` -> exit 0 3. verify both `~/.openclaw/agents/main/agent/auth-profiles.json` and `~/.openclaw/agents/macbook_bot/agent/auth-profiles.json` are written with mode 0600 and the real key 4. restart gateway -> `agent model: ocp/claude-sonnet-4-6` loads 5. send a real Telegram message -> bot responds via `ocp/claude-sonnet-4-6`, `[telegram] sendMessage ok`, `auth-state.json` updates `lastUsed` and shows `errorCount: 0, lastGood: ocp:default` 6. zero `No API key found` / 401 / lane-error events anywhere Offline test matrix (also all green): * anonymous mode -> warning prints, no agentDir write * corrupted existing auth-profiles.json -> backed up to .bak, rebuilt * no-tty execution -> 0 `Device not configured` leakage * `--help` -> new B6 wording * `--version` -> `ocp-connect 1.1.0` ## Code review Two independent reviewer subagents (opus, spec compliance + code quality) plus one blind implementer subagent (sonnet, control group). The blind implementer reproduced exactly the two MUST-FIX issues the code-quality reviewer flagged in the first draft (silent data loss on `except Exception`, misleading success on per-agent write failure), validating that the review process caught real non-obvious problems. Both must-fixes are addressed in this commit; re-tested green. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ocp-connect): drop "with agentDir" from anonymous warning The message previously said "agents with agentDir" but B1.5 now also seeds agents that omit the field (their dir is derived). Reviewer feedback on PR #13. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: taodeng <taodeng@Taos-MBP> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
f8ca9b85b0 |
bump version to 3.6.0 for IDE auto-configuration feature
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |