Commit Graph
3 Commits
Author SHA1 Message Date
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>
2026-04-12 17:59:59 +10:00
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>
2026-04-12 17:36:02 +10:00
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>
2026-03-21 18:46:36 +10:00