mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-21 21:15:09 +00:00
3322d7bdae7389ab01ccc23b0fdc18ede02689cd
22
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0dced52215 |
chore(release): v3.19.0 — TUI large-prompt reliability (#130) + proxy-purity context (#4) (#134)
Release prep only: bump package.json 3.18.0 → 3.19.0, CHANGELOG entry, README TUI "what changes / what doesn't" note that the host CLAUDE.md/auto-memory is never injected. Bundles two already-merged, independently-reviewed TUI fixes: - #130 (PR #131): reliable large multi-line paste + version-robust turn detection. - #4 (PR #132): never inject host CLAUDE.md / auto-memory into proxied turns. Both verified live on PI231 + Oracle; adversarial multi-host battery passed (0 hangs / 0 crashes / 0 injection / 0 leaks). Follow-up: #133. No server.mjs change in this PR (package.json + CHANGELOG + README only) → cli.js citation N/A. release_kit (Iron Rule 5.5): version_source=package.json, changelog, README docs all updated; no new env var / endpoint / subcommand. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
1f577c075f |
chore(release): v3.18.0 — code-audit hardening (P0 /health + P2/P3 batch) + 3 follow-ups (#129)
Bundles the 2026-05-31 multi-agent code audit remediation and its follow-ups: - #109 (P0): /health no longer leaks the anonymous quota key to LAN (opt-in PROXY_ADVERTISE_ANON_KEY) - #110: request-validation + OpenAI-compat correctness - #111: error-output sanitization + process-lifecycle hardening - #112: OAuth-host verification + models.json SPOT - #113: CLI/installer hardening - #114: dashboard XSS escaping + key-name validation - #115: TUI non-loopback LAN gate + cc_entrypoint assertion - #123: alignment.yml wrong-host pin + ALIGNMENT.md amendment - #124: dashboard status/plan card escaping - #125: isLoopbackBind extracted to lib/net.mjs Each landed as its own PR with a fresh-context independent reviewer (Iron Rule 10) and green alignment CI. Version bumped 3.17.1 → 3.18.0; CHANGELOG finalized. 181 tests pass. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
68d58e7df4 |
fix: CLI/installer hardening — restart labels, key permissions, unit-secret escaping (#113) (#120)
Three CLI/installer findings from the 2026-05-31 audit (no server.mjs): 1. ocp-plugin `cmdRestart` hardcoded uid 501 + the legacy `ai.openclaw.proxy` label, and fell through to a dangerous `pkill -f 'node.*server.mjs' && cd ~/.openclaw/projects/*/; node server.mjs &` that could kill an unrelated node process and relaunch an env-less ghost proxy from a glob-ambiguous dir. Now uses process.getuid() + the live labels (dev.ocp.proxy on macOS, ocp-proxy on Linux systemd; the OpenClaw gateway label is unchanged) and drops the pkill fallback entirely (returns a manual `ocp restart` message on failure). 2. ocp-connect wrote the quota key unquoted into rc files and a world-readable environment.d/ocp.conf. Now single-quotes the value and chmod 600s the rc files and ocp.conf (matching the existing auth-profiles.json 0o600 convention). 3. setup.mjs interpolated the injected service-unit secrets (CLAUDE_BIN, OCP_ADMIN_KEY, PROXY_ANONYMOUS_KEY) raw into plist <string> and systemd Environment= lines. Added xmlEscape() for all plist <string> values and assertSafeInjectValue() which rejects control characters (\x00-\x1f — newline, CR, tab) before any unit is written, blocking a newline-injected rogue Environment= directive. Spaces are intentionally allowed (CLAUDE_BIN paths may contain them). XML-escaping on write also resolves plist-merge.mjs's [^<]* regex concern transitively (no raw < reaches it) — comment added, logic unchanged. ALIGNMENT.md: CLI/installer scripts only, no Anthropic operation forwarded → cli.js citation N/A. No blacklisted tokens or port literals introduced; alignment.yml passes. Independent fresh-context reviewer (opus): APPROVE (Iron Rule 10) — verified the control-char regex byte-exact via od (no space-rejection regression), the pkill fallback fully removed, restart labels match setup.mjs ground truth, OCP keys (base64url) cannot break the single-quoting, and the validator runs before any write. Closes #113. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
4a7d79c330 |
fix: record OAuth-host verification + models.json SPOT for usage-probe/default (#112) (#119)
Three alignment/SPOT findings from the 2026-05-31 audit: 1. The OAuth token-refresh host (platform.claude.com/v1/oauth/token, a Class A surface) was introduced in the 2026-04-11 drift commit and had no verification record. Verified against the compiled cli.js (claude.exe v2.1.154) via `strings`: OAUTH_TOKEN_URL and OAUTH_CLIENT_ID both appear in the binary byte-for-byte (in the same `prod` config object), and the legacy host console.anthropic.com/v1/oauth is absent (0 hits). Recorded this as an inline ALIGNMENT citation comment above the constants. No live OAuth probe was run — a refresh-token grant would rotate the operator's real credentials; the strings-on-binary evidence is decisive. (cli.js: verified against compiled claude.exe v2.1.154, 2026-05-31.) 2. fetchUsageFromApi() hardcoded the haiku model ID; now derives from modelsConfig.aliases.haiku (ADR 0003 SPOT). Prevents a silent /usage break on a future haiku ID bump. 3. [P3] The default request model hardcoded the sonnet ID; now derives from modelsConfig.aliases.sonnet (ADR 0003 SPOT). Both SPOT values are byte-identical to the literals they replace today, so zero behavior change — only drift-resistance. The alignment.yml blacklist pin for the wrong-host variant was deliberately NOT included here: extending the blacklist is a governance-layer change (alignment.yml inline policy) and belongs in its own PR. ALIGNMENT.md: finding 1 IS the verification (cli.js citation recorded inline); findings 2-3 are SPOT hygiene that forward no new operation. No blacklisted tokens or port literals introduced; alignment.yml passes. Independent fresh-context reviewer (opus) INDEPENDENTLY re-ran `strings` on the binary and confirmed the host/client_id present and the legacy host absent — APPROVE (Iron Rule 10; alignment hard-requirement #3 satisfied). Closes #112. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
36be723198 |
fix(security): gate /health anonymousKey behind opt-in PROXY_ADVERTISE_ANON_KEY (#109) (#116)
/health is an unauthenticated, LAN-reachable endpoint that returned the live anonymous bearer key (anonymousKey: PROXY_ANONYMOUS_KEY). Any device that could reach the port harvested a working, quota-spending credential (P0). The anonymousKey field is now included only when the caller is localhost (already fully trusted by the auth path, via the unspoofable req.socket.remoteAddress) OR the admin explicitly opts in with the new PROXY_ADVERTISE_ANON_KEY=1 env var (default off). ocp-connect's absent-field fallback (interactive --key / anonymous access) is unchanged — comment-only update there. ALIGNMENT.md: this gates an OCP proxy-policy field in /health; it does NOT forward, add, or alter any Anthropic API operation, so a cli.js citation is N/A under Rule 2. No blacklisted tokens introduced; alignment.yml passes. Independent fresh-context reviewer (opus): APPROVE WITH MINOR (Iron Rule 10). Closes #109. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
1b5a742711 |
chore(release): v3.17.1 — code-audit P1/P2 hardening (crash fixes + multi-tenant gates) (#107)
Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
a30b20978c |
chore(release): v3.17.0 — Phase 6c stream-json default + opus 4.8 + opt-in TUI-mode (#105)
- Phase 6c: default claude spawn → stream-json + --system-prompt (~64% cost cut) - opus 4.8 model - opt-in CLAUDE_TUI_MODE interactive subscription-pool bridge (single-user) Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
74260d7f6f |
feat(tui): opt-in CLAUDE_TUI_MODE — interactive subscription-pool bridge (PR-1..PR-4 squashed) (#104)
Reader + driver + home modes + server wiring + entrypoint hardening. 5 independent reviews folded; e2e green; default stream-json path byte-identical when flag off. See PR description + ADR 0007 for the full layer/authority/security detail. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
885f62addf |
feat: Phase 6c — claude stream-json port + --system-prompt + opus 4.8 (#103)
* wip(rescue): OCP Phase 6c stream-json port + opus 4.8 (uncommitted /tmp work) Rescue commit — preserves the subagent-produced Phase 6c port (claude -p → stream-json + --system-prompt + NDJSON parser) and opus 4.8 model addition done 2026-05-30 in a /tmp clone (volatile). NOT reviewed-for-merge; this is preservation only on a WIP branch so a /tmp reboot does not lose the work. Strategy context: OCP is being made the first-mover for TUI-mode (users are on OCP). Phase 6c here is the DEFAULT-path foundation (stream-json = sdk-cli = the "Max-user stable mode" fallback). TUI-mode (cc_entrypoint=cli subscription bridge) lands on top as an opt-in. Re-review before merging to main. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(phase6c): fold review findings — scrub hostname, README prose, parser tests, version citation P3-1 (binding): scrub private hostname "PI231" from server.mjs comment; replace with role-based term "the test server". Repo is public — no machine-specific hostnames may appear. P3-2: update README.md "How It Works" diagram and prose from stale "claude -p" to "claude --output-format stream-json", matching the Phase 6c spawn change already in server.mjs and CHANGELOG. P2-2: update all five authority citations in server.mjs from bare "claude CLI v2.1.104" to "claude CLI § <flag> (ported from OLP, verified v2.1.104; behavior stable through v2.1.158)" — honest about OLP provenance and version range, without inventing new facts. P2-1: add "Stream-JSON parsers" suite to test-features.mjs (17 new tests, 84 → 101 total). Copies parseStreamJsonLines and parseStreamJsonEvent verbatim from server.mjs with a MIRRORS header comment; logEvent stubbed to avoid live-server side-effects. Covers: content_block_delta deltas, assistant-aggregate fallback, no-double-count guard, aggregate-only short responses, partial-line buffering across two chunks, is_error result variants, malformed/non-JSON lines, system/user/unknown event types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9e25160527 |
refactor: hoist port literal to lib/constants.mjs + CI gate (v3.16.4) (#98)
Closes the structural side of the port-drift cascade addressed by
v3.16.2/v3.16.3. Those releases reverted the literal line-by-line; this
one removes the invitation to drift.
Changes:
* NEW lib/constants.mjs — exports DEFAULT_PORT=3456, LOCAL_HOST,
OPENAI_API_BASE, LOCAL_PROXY_URL.
* server.mjs / setup.mjs / scripts/upgrade.mjs / scripts/doctor.mjs
(x2) / scripts/sync-openclaw.mjs all import DEFAULT_PORT from
lib/constants.mjs instead of hardcoding "3456".
* .github/workflows/alignment.yml:
- path filter extended to setup.mjs, scripts/**, lib/**,
ocp, ocp-connect.
- NEW job port-spot hard-fails any PR that introduces a hardcoded
"3478" or "3456" literal outside EXEMPT_REGEX (lib/constants.mjs,
test-features.mjs, ocp/ocp-connect bash CLIs, docs, the workflow
itself).
* Doc-comment rewording so CI grep finds zero hits.
No behavior change for any user. CLAUDE_PROXY_PORT env var still wins
at runtime; only the unset-env fallback now flows through one constant.
ALIGNMENT.md note: server.mjs change is one import + one literal swap,
mechanical. No cli.js operation changed; the citation requirement does
not apply.
cli.js: not applicable — mechanical refactor, no behavior change.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
|
||
|
|
49c6d32e3b |
fix(scripts): default CLAUDE_PROXY_PORT to 3456 (completes v3.16.2 revert) (#97)
Three places in scripts/ still defaulted to 3478 after v3.16.2's plugin / manifest / README / plist revert: scripts/upgrade.mjs:137 scripts/doctor.mjs:84 scripts/doctor.mjs:205 These were the residual cascade source for the port-drift bug originally caused by the PR #71 dogfood accident on 2026-05-08 (see ~/.cc-rules/memory/learnings/subagent_setup_mjs_prod_host_collision.md and v3.16.2 CHANGELOG entry). Every `ocp doctor` / `ocp upgrade` invocation without an explicit `CLAUDE_PROXY_PORT` in env probed port 3478 — got "OCP not responding" against a healthy 3456 instance — and on the maintainer's host this cascaded into wrong baseUrl writes for the OpenClaw `claude-local` provider, taking out the OpenClaw Telegram agent ("大内总管") on 2026-05-13. This change is the smallest possible: three string literals `"3478"` → `"3456"`, aligning unset-env defaults with `server.mjs:126`. Env-set users are unaffected (env precedence is unchanged). cli.js: not applicable — this is a scripts/ change, not server.mjs. ALIGNMENT.md hard-requirements (cli.js citation, blacklist CI, independent reviewer) target server.mjs; this PR honors the SPOT spirit by ending the literal-port drift across the codebase. Bumps to v3.16.3. CHANGELOG entry added. Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
7766fa0868 |
fix(plugin): revert default to 3456 + correct v3.16.1 narrative; v3.16.2 (#96)
v3.16.1's narrative ("OCP server moved to 3478 default in v3.14+") was
incorrect. OCP source default has been 3456 since
|
||
|
|
70faeff067 |
fix(plugin): default OCP plugin port to 3478 + env-overridable (v3.16.1) (#95)
* fix(plugin): default OCP plugin port to 3478 + env-overridable; v3.16.1 ocp-plugin/index.js hard-coded http://127.0.0.1:3456 since the plugin was created. OCP server moved to 3478 default in v3.14+ as part of the same wave that renamed the launchd label (dev.ocp.proxy). The plugin never got the memo. Result: `/ocp usage` from OpenClaw bots (e.g. the home Telegram bot 大内总管) hit the dead port 3456 and returned "OCP error: fetch failed". Fix: - Default PROXY → http://127.0.0.1:3478 - Read OCP_PROXY_URL env (full URL) first - Else read CLAUDE_PROXY_PORT env (port only, localhost assumed) - Else fall back to the 3478 default openclaw.plugin.json bumped (3.12.0 → 3.16.1) and configSchema default updated. Plugin version now matches OCP version. Top-level package.json bumped 3.16.0 → 3.16.1. CHANGELOG entry added. Diagnostic trail: caught 2026-05-12 when home Telegram bot reported "OCP error: fetch failed" against `/ocp usage`. Mac mini OCP service was healthy on port 3478; lsof -iTCP:3456 had no listener; plugin index.js had hardcoded 3456. No cli.js citation needed: this is OCP-internal plugin code with no corresponding cli.js operation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(readme): document OCP_PROXY_URL + CLAUDE_PROXY_PORT plugin reuse (per release_kit 5.5) --------- Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
7a69d72886 |
feat(snapshot): gcSnapshots + ocp update --rollback --gc + auto-GC; v3.16.0 (#94)
Adds snapshot garbage collection with retention policy: keep last 5, keep snapshots within 30 days, always keep the most recent. Configurable via keepCount / keepDays opts. Wire-up: - ocp update --rollback --gc (manual trigger; --dry-run supported) - runFullUpgrade auto-GC after successful upgrade (best-effort, swallows errors) 4 unit tests: keepCount enforcement, keepDays override, never-delete- most-recent safety, dry-run mode. Bumps to v3.16.0 (bundles PR #93 --check oauth + this GC feature). No cli.js citation needed: this is OCP-internal snapshot lifecycle with no corresponding cli.js operation. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
cd6ec2a212 |
fix(doctor): dynamic latest_version from origin/main; release v3.15.1 (#92)
v3.15.0 doctor used a hard-coded `latest = "v3.14.0"` fallback, causing any v3.15.0+ install to report kind=upgrade against a stale value. `ocp update` would then attempt `git checkout v3.14.0` — a downgrade. Doctor now fetches `git -C ~/ocp show origin/main:package.json` to determine the actual latest. On failure (offline, fresh clone, no remote), falls back to currentVersion so kind=noop instead of recommending a downgrade. Regression test added: doctor with unreachable ocpDir falls back to currentVersion as latest (not the old hardcoded v3.14.0). Caught during v3.15.0 post-deploy verification on home-mac: ./ocp doctor reported `kind=upgrade` immediately after v3.15.0 install, which would have been a critical user-facing bug. No cli.js citation needed: this is OCP-internal doctor logic with no corresponding cli.js operation. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
ab03c13332 |
feat(upgrade): ocp doctor + cross-version ocp update + rollback + AI prompts (#91)
* feat(doctor): add ocp doctor with --json + next_action contract Implements scripts/doctor.mjs with semver-aware path selection (noop/update/upgrade/fresh_install/fix_oauth/fix_service) and the JSON contract documented in the design spec. Service health + OAuth checks integrated; mockable via opts.mockHealth for unit tests. 8 unit tests cover the kind dispatch tree and the next_action shape for each kind. No cli.js citation needed: this is OCP-internal tooling with no corresponding cli.js operation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(ocp): wire cmd_doctor into bash CLI; dispatch to scripts/doctor.mjs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(doctor): handle unparseable version + empty health body Three issues raised by code-quality reviewer on |
||
|
|
750b25ba77 |
chore(release): v3.14.0 — security hardening (sessions namespacing + file modes + /api/usage scope) (#89)
Bump version 3.13.0 → 3.14.0. No functional code change in this PR; all three security fixes are already merged in main via PRs #86, #87, #88. This PR covers only metadata + docs: - package.json: version bump to 3.14.0 - CHANGELOG.md: v3.14.0 entry with Features / Behavior changes / Verification / Governance sections - README.md: /api/usage self-scope note in Auth Modes §, API Endpoints table row update, file-mode bullet in Important Notes § cli.js citation N/A: this release PR modifies no server.mjs / setup.mjs / keys.mjs. The three underlying security PRs (#86, #87, #88) each carry their own cli.js-citation-not-applicable disclaimer per PR #75 pattern, as they are OCP-internal access-control, session-state, and file-permission changes with no corresponding cli.js operation. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
9494fd6c69 |
chore(release): v3.13.0 — cache layer hardening (per-key isolation + bypass + chunked replay + singleflight) (#67)
Per release_kit overlay (CLAUDE.md § Iron Rule 5.5): - package.json bumped 3.12.0 → 3.13.0 - CHANGELOG.md updated with v3.13.0 entry - README.md § Response Cache updated to document the four hardening features This is a release preparation commit. Tag push to v3.13.0 will trigger .github/workflows/release.yml which auto-creates the GitHub Release. cli.js does not perform proxy-layer response caching, stampede protection, or replay; this release only ships internal cache-layer correctness and concurrency improvements that do not change the OpenAI-compatible wire surface visible to clients. No client-observable wire shape change. 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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |