mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-21 21:15:09 +00:00
v3.11.1
25
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 (
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
47434a8ba7 |
fix: security hardening + real streaming (#4)
* Add graceful shutdown handling for SIGTERM and SIGINT On receiving SIGTERM or SIGINT, the server now: 1. Stops accepting new connections (server.close) 2. Clears session cleanup and auth check intervals 3. Sends SIGTERM to all active child processes 4. Waits up to 5s for processes to exit, then SIGKILL + exit(1) 5. Exits cleanly with exit(0) once all processes are gone Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: security hardening — bind localhost, validate models, limit body size - Bind to 127.0.0.1 instead of 0.0.0.0 to prevent network exposure - Restrict CORS Access-Control-Allow-Origin to http://127.0.0.1 - Add 10MB request body size limit (413 on exceed) - Validate model parameter against known MODEL_MAP keys (400 on unknown) - Remove catch-all POST route; only /v1/chat/completions accepted - Use crypto.timingSafeEqual for API key comparison - Sanitize error messages to strip internal file paths Bumps version to 2.4.1. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace fake streaming with real stdout-piped SSE streaming Previously, streaming requests waited for the entire claude CLI process to complete, then split the output into artificial 500-char chunks sent as SSE events. This defeated the purpose of streaming and added latency. Now, stdout from the claude child process is piped directly to SSE chunks as data arrives. Each `data` event from the process stdout becomes an immediate SSE delta event, giving clients real incremental output. The shared process setup logic is extracted into spawnClaudeProcess() to avoid duplication between streaming and non-streaming paths. Client disconnect detection kills the child process to free resources. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: tighten CORS to dynamic localhost-only and reduce body limit to 5MB - CORS now dynamically matches localhost/127.0.0.1 origins instead of static http://127.0.0.1, preventing cross-origin abuse while supporting any localhost port - Body size limit reduced from 10MB to 5MB to limit OOM risk Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Oracle Public Cloud User <opc@instance-20230820-1333.subnet07301351.vcn07301351.oraclevcn.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |