feat(cli): add opencode as a first-class interactive TUI harness#253
Conversation
…mapper Factor the v1/v2/status emit machinery out of HarnessSessionTailer into a reusable SessionEnvelopeEmitter (parameterized by EmitIdentity) so a non-file session source can share envelope framing/publish/status. Add opencodeEventsFromBusEvent + createOpenCodeBusMapper mapping opencode's SSE `/event` bus frames (message.part.updated / message.updated / session.*) into the shared HarnessSemanticEvent model, with tool/text dedup across snapshots. No behavior change to the file tailer (existing v2/resume/envelope tests green). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nJsktzaVmQ1VnsYjzditQ
Add OpenCodeEventSource + startOpenCodeServer: start a headless `opencode serve` on a free port, subscribe to its `/event` SSE bus, filter to the session whose directory matches cwd, and emit the shared v1/v2 envelopes via SessionEnvelopeEmitter. runHarnessCommand launches `opencode attach <url>` in the PTY so the human's session and the bridge share one server; teardown kills serve in a finally. Non-TTY/opencode falls back to a plain spawn with a note. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nJsktzaVmQ1VnsYjzditQ
Extend the TUI (TinyVerseAgentKind, home menu, buildAgentProfile with a serverMode marker) so `tinyplace opencode` runs like codex/claude. In serverMode the TUI boots `opencode serve`, prepends `attach <url>` to the launch, bridges the live session via OpenCodeEventSource (not the file tailer), and tears the server down on exit. Add runOpencodeCommand + the opencode dispatch/help entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nJsktzaVmQ1VnsYjzditQ
…patch Unit tests for createOpenCodeBusMapper (tool/text dedup, roles, flush), OpenCodeEventSource (v1/v2 emit, session filtering, foreign-session drop), startOpenCodeServer (port/readiness/teardown/retry via injected spawn+connect), and the opencode CLI dispatch/parse. Add an opt-in live smoke (TINYPLACE_OPENCODE_SMOKE) that drives a real `opencode serve` turn end-to-end and asserts an agent_message envelope — verified locally against opencode 1.17.18 (text parts carry time.end, as the fixtures assume). Also harden the server's readiness wait to surface spawn errors (ENOENT). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nJsktzaVmQ1VnsYjzditQ
The earlier commits ran the website prettier over edited SDK files, which rewrapped unrelated pre-existing lines (the SDK is hand-wrapped and is not prettier-managed — CI only formats the website). Restore those lines to their original form so the diff contains only the opencode changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nJsktzaVmQ1VnsYjzditQ
|
@senamakel is attempting to deploy a commit to the Vezures Team on Vercel. A member of the Team first needs to authorize it. |
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 2 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6de4c8f1bd
ℹ️ 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".
| launch = { | ||
| command: config.agentBin, | ||
| args: ["attach", opencodeServer.url, ...config.agentArgs], |
There was a problem hiding this comment.
Don't pass TUI args through opencode attach
When a user supplies normal OpenCode TUI args such as a project path, --model, or --agent (also via TINYPLACE_OPENCODE_ARGS in the TUI path), this wrapper appends them to opencode attach. The current OpenCode CLI docs list --model/--agent under opencode [project], while attach [url] only documents --dir, resume/session/fork, and auth flags (https://opencode.ai/docs/cli/#attach), so common invocations like tinyplace opencode --model provider/model will fail or be ignored instead of starting the requested OpenCode session.
Useful? React with 👍 / 👎.
| export async function runOpencodeCommand( | ||
| argv: Array<string>, | ||
| options: TinyPlaceCliOptions = {}, | ||
| ): Promise<TinyPlaceCliResult> { | ||
| return runHarnessAgentCommand("opencode", argv, options); |
There was a problem hiding this comment.
Reject opencode agent mode until the plugin supports it
This enables tinyplace opencode --agent, but the unified plugin it delegates to has no OpenCode adapter: sdk/plugin-tinyplace/mcp/harness.mjs registers only claude, codex, cursor, windsurf, and mock. Because the plugin ignores an unknown --harness opencode override and falls back to auto-detection/default Claude, users choosing the new OpenCode agent mode will launch the wrong harness rather than an OpenCode session.
Useful? React with 👍 / 👎.
| const response = await fetch(url, { | ||
| signal, | ||
| headers: { accept: "text/event-stream" }, | ||
| }); |
There was a problem hiding this comment.
Authenticate the opencode SSE fetch when env enables auth
If the user has OPENCODE_SERVER_PASSWORD set, opencode serve enables HTTP Basic auth, and opencode attach can read that password from the env according to the OpenCode CLI docs (https://opencode.ai/docs/cli/#serve and https://opencode.ai/docs/cli/#attach). This direct /event fetch only sends Accept, so in that environment the server returns 401 and the bridge silently stops observing/publishing the session even though the attached TUI can still run.
Useful? React with 👍 / 👎.
What & why
opencode was a first-class
HarnessProvideronly for the headless daemon (opencode run --format json) — the interactive TUI wrapper (tinyplace codex/tinyplace claude) never supported it. The blocker: opencode keeps every session in a single SQLite DB, so the file-pollingHarnessSessionTailerfundamentally cannot observe a live interactive session (as the oldPROFILES.opencodecomment noted).This brings opencode to interactive-TUI + daemon parity with codex/claude by wrapping it over its HTTP server's SSE
/eventbus instead of files: the wrapper starts a headlessopencode serve, subscribes to/event, and spawnsopencode attach <url>in the PTY so the human's session and the observer share one server. Bus frames fold into the existing v1/v2 envelope pipeline.How
SessionEnvelopeEmitter(harness-wrapper.ts) — extracted the v1-message + v2-typed-event + status emit machinery out ofHarnessSessionTailer(composition, parameterized by anEmitIdentity) so a non-file source reuses identical envelope framing/publish/status. Behavior-preserving — the existing tailer delegates to it.opencodeEventsFromBusEvent+createOpenCodeBusMapper(harness-events.ts) — map opencode SSE bus frames (message.part.updated/message.updated/session.*) into the sharedHarnessSemanticEventmodel, with tool + text dedup across the repeated part snapshots. The daemon's flat-shapeopencodeEventsFromLineis untouched.opencode-source.ts(new) —startOpenCodeServer(free ephemeral port, resolves on theserver.connectedframe / listening line, retry-once, SIGTERM→SIGKILL teardown) +OpenCodeEventSource(Node-fetch SSE loop, filters the global stream to the session whosedirectory === cwd, emits v1/v2, status heartbeat, flush-on-stop). Both take injectablespawn/connectseams for tests.runHarnessCommandboots the server, bridges via the SSE source, launchesopencode attach <url>, and tears the server down in afinally. The interactive TUI gains anopencodeprofile (serverMode), home-menu entry, and server/bridge lifecycle.tinyplace opencodedispatches like codex/claude (bare→TUI,--raw→wrapper,--agent→plugin).Testing
tscbuild + typecheck: 0 errors.OpenCodeEventSource(v1/v2 emit, session filtering, foreign-session drop, flush),startOpenCodeServer(port/readiness/teardown/retry via injected spawn+connect), andtinyplace opencodedispatch/parse.harness-wrapper-v2/ resume-tail / envelope tests stay green.TINYPLACE_OPENCODE_SMOKE=1) drives a realopencode serveturn end-to-end and asserts anagent_messageenvelope — run locally against opencode 1.17.18 (confirmed text parts carrytime.end, as the fixtures assume).Scope / follow-ups
🤖 Generated with Claude Code
https://claude.ai/code/session_011nJsktzaVmQ1VnsYjzditQ