feat: opencode sdk#1
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
|
@cursor review |
|
Skipping Bugbot: Bugbot is disabled for this repository |
|
@cursor review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| try: | ||
| await asyncio.wait_for(event_task, timeout=600.0) | ||
| except asyncio.TimeoutError: | ||
| self.opencode_ui.error("Session timed out (10 min)") |
There was a problem hiding this comment.
Timeout error returns success result instead of None
When asyncio.TimeoutError is caught at line 97, an error message is displayed but _last_error is never set. The code then continues to line 103 where the check if self._last_error passes (since it's None), causing execution to fall through to line 108-118 which returns a success result. This means a timed-out session incorrectly returns a result with a script_path that may not exist instead of returning None to indicate failure.
Additional Locations (1)
| event_sid = properties.get("sessionID") | ||
| if event_sid != self._session_id: | ||
| debug_log(f"session.error for other session {event_sid}, ignoring") | ||
| continue |
There was a problem hiding this comment.
Session errors silently ignored when sessionID is missing
The comparison if event_sid != self._session_id at line 261 will evaluate to True when sessionID is absent from the error event (making event_sid equal to None). This causes the error to be silently ignored with a debug log saying it's for "other session". If the OpenCode API ever sends error events without a sessionID field, actual errors affecting this session could be missed entirely.
|
|
||
| except httpx.ReadError as e: | ||
| debug_log(f"ReadError (normal on session end): {e}") | ||
| pass |
There was a problem hiding this comment.
Unexpected disconnection treated as success instead of error
When an httpx.ReadError occurs (e.g., the connection drops unexpectedly before session.idle is received), the exception is caught and silently ignored with pass, but _last_error is never set. This differs from the generic Exception handler at line 295-296 which does set _last_error. Since _last_error remains None, the check at line 103 passes and execution continues to the success path, returning a result with a script_path that may not exist. The comment suggests ReadError is "normal on session end," but normal session completion returns via the session.idle event handler, not via exception.
Implements the first two items of the agent-friendliness backlog (kalil0321#62): kalil0321#1 TTY-detect at REPL entry Without a TTY and no subcommand, the prompt_toolkit REPL would block on stdin forever. Now detects `not sys.stdin.isatty()`, prints `--help` to stderr, and exits 2 (misuse). Subcommands are unaffected. kalil0321#2 engineer --json / --no-interactive Mirrors the agent-mode contract (PR kalil0321#61): --json redirects Rich output to stderr and emits a stable JSON payload on stdout. New helper `_build_engineer_payload` produces the schema {schema_version, status, run_id, prompt, fresh, script_path, usage, error}. KeyboardInterrupt surfaces as `error: "interrupted"`. Exit codes: 0 ok, 1 runtime error. --no-interactive is reserved for symmetry (engineer mode has no questionary prompts internally). Also surfaces `--json` / `--no-interactive` in the root --help (item kalil0321#6 partial). Adds tests/test_cli_followups.py (14 tests) covering TTY-detect end-to- end via subprocess, _build_engineer_payload edge cases, and the click wiring for engineer --json (success, not-found, KeyboardInterrupt, exception, prompt-vs-fresh threading). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "Safari can't connect to the server" failure mode after an abrupt rae exit (force-quit, kill, crash, kernel panic) was a clean data loss: the snapshot of the user's pre-rae proxy settings lived only in `AppState.proxySnapshot` (in-memory). When the process died without reaching `restoreProxyBeforeExit`, the system proxy stayed pointing at 127.0.0.1:<our port> with nothing listening — Safari / Chrome / Atlas all hard-fail on every request — and the user's real proxy config (Wi-Fi defaults, corporate proxy, VPN settings) was gone with no way for the next launch to bring it back. Two complementary fixes: 1. **Persist the snapshot to disk.** Inside applySystemProxy, write the snapshot to <Application Support>/ReverseAPI/proxy-snapshot.json *before* flipping networksetup, so even a crash between `enable()` and the in-memory assignment leaves enough state for the next launch to recover. On `restoreSystemProxy` / `disableCurrentRaeProxy` / `restoreProxyBeforeExit` we delete the sentinel file alongside the in-memory clear. AppState.init now also reads the sentinel back into proxySnapshot at boot, so `recoverStaleSystemProxyOnLaunch` can do a real restore (preserving the user's previous corporate / Wi-Fi proxy settings) instead of a blanket disable that dropped everything. The recovery message has two variants now: "Restored previous proxy settings from a stale session." when the snapshot survived, "Recovered stale device proxy from a previous session." when only a disable was possible. ProxyServiceSnapshot grows a Codable conformance + explicit public init (the synthesized memberwise was internal so the ReverseAPI target couldn't construct one). The shape stays identical. 2. **Catch SIGTERM / SIGINT / SIGHUP.** AppDelegate.installSignalHandlers wires DispatchSource.makeSignalSource on the main queue for each of the three signals — Activity Monitor's "Quit"/"Force Quit", `kill <pid>`, terminal Ctrl-C from `swift run`, and shutdown all hit one of these. The handler flips the existing isTerminating guard, calls AppLifecycle.shared.restoreProxyBeforeExit() synchronously (we have no async budget once the OS decided to kill us), then re-raises the signal with SIG_DFL so the process exits with the correct termination status. SIGKILL (kill -9) and a true crash still bypass everything — the kernel doesn't let userspace catch those — but the disk sentinel from fix #1 covers that path too: next launch restores the snapshot, no manual `networksetup -setwebproxystate Wi-Fi off` gymnastics needed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implement OpenCode AI SDK.
We didn't use the Python SDK as it is outdated.
(We = antigravity + claude opus + me)
Used the API.
Might need to add a warning to tell user to start opencode with a subprocess.
Note
Introduces an extensible engineering layer and adds an alternative OpenCode backend alongside the existing Claude flow.
BaseEngineercentralizes shared logic (prompt build, paths, UI, message store)OpenCodeEngineerwithhttpxAPI calls, SSE event streaming, auto‑permission handling, usage tracking, andOpenCodeUIfor live updatesrun_reverse_engineeringnow selectsOpenCodeEngineerorClaudeEngineerbased onsdk; keepsAPIReverseEngineeralias/settingsoption to choosesdk, and passessdkinto engineer run; improved help/outputssdksetting (defaults toclaude)Written by Cursor Bugbot for commit 319f9d2. This will update automatically on new commits. Configure here.