Skip to content

Add resilient OpenCode tool activity#3851

Merged
BunsDev merged 116 commits into
mainfrom
agent/issue-3846-opencode-tool-activity
Jul 26, 2026
Merged

Add resilient OpenCode tool activity#3851
BunsDev merged 116 commits into
mainfrom
agent/issue-3846-opencode-tool-activity

Conversation

@CompleteDotTech

@CompleteDotTech CompleteDotTech commented Jul 24, 2026

Copy link
Copy Markdown
Member

Fixes #3846

Beads: cave-trt.6 · Milestone: #3 Runtime Tool-Activity Compatibility

Compatibility architecture

  • Probe opencode --version and opencode run --help with the same safe direct-launch path used for chats; version is retained for support diagnostics, while observed capabilities select behavior.
  • Resolve a data-only parser schema from a built-in offline baseline or an optional Ed25519-signed registry bundle. Remote bundles have bounded input, expiry, immutable monotonic sequence, atomic cache writes, and last-known-good rollback protection.
  • Select launch arguments (--format json, --session, --model) and decoding from that compatibility result, not a semantic-version threshold.
  • Decode current text, terminal tool_use, and error JSONL events plus a retained legacy fixture; preserve native stable IDs, tool input/output/error state, chronological placement, persistence, resume, duplicate suppression, and bounded reordering reconciliation.
  • Unknown/malformed or unsupported schemas preserve ordinary chat and surface an actionable, value-free compatibility notice. Diagnostics use only a redacted event-shape fingerprint.

Support matrix

Client capability Behavior
JSON + session opencode-run-json-v1; structured tool activity and native-session resume
JSON without session legacy compatible parser; text/tool activity remains, resume replays context into a new session
No JSON or no compatible schema text-only chat plus visible compatibility diagnostic

Evidence and sources

  • OpenCode CLI reference documents run, --session, --model, and --format json raw events.
  • Current run implementation confirms current JSON output emits root sessionID with text, terminal tool_use, and error envelopes.
  • OpenCode permissions documents that permissions are configured by tool/action and that run --auto changes only prompt behavior, so Cave continues to refuse unsupported Read-only enforcement.

The development host has no opencode executable, so its real-client version/help capture is recorded as unavailable rather than fabricated. A signed canonical registry URL and public key are external deployment prerequisites; until configured, the reviewable built-in profile is the offline baseline.

Validation

  • Focused compatibility cache/signature, current + legacy schema, malformed/unknown, reordered/duplicate, persisted reload, tool-failure, privacy-redaction, and routing-contract tests: pass
  • pnpm check:tests-wired: pass
  • pnpm lint: pass
  • pnpm typecheck: pass
  • node scripts/run-tests.mjs api: blocked before OpenCode tests by the unrelated host WSL error execvpe(/bin/bash) failed: No such file or directory in scripts/git-hooks-pre-commit.test.mjs
  • pnpm build: an existing build process owns this worktree's .next/lock; it was left undisturbed rather than terminated

Codex and others added 28 commits July 25, 2026 01:01
Keep indentation and blank lines intact when compatibility falls back to plain OpenCode output, while continuing to filter the known permission control notice. Add route-level coverage for an unsupported future JSON format.

Co-authored-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com>
Reject overfull local trust-anchor journals before enumeration can consume unbounded memory, preserving a fail-closed compatibility fallback.

Co-authored-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com>
Avoid reusing help-derived JSON and session capability evidence across familiars, whose scoped launch environments can select different OpenCode contracts even when the launcher path and version match.

Co-authored-by: Timothy Wayne Gregg <5861166+CompleteDotTech@users.noreply.github.com>
@CompleteDotTech
CompleteDotTech force-pushed the agent/issue-3846-opencode-tool-activity branch from 6ec0853 to a9afef6 Compare July 26, 2026 03:41
@BunsDev
BunsDev marked this pull request as ready for review July 26, 2026 07:35
Copilot AI review requested due to automatic review settings July 26, 2026 07:35
@BunsDev
BunsDev merged commit 52468bb into main Jul 26, 2026
15 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a capability-driven OpenCode integration that preserves tool activity/resume across evolving JSONL protocols, backed by a signed compatibility-schema registry with offline fallback/quarantine behavior, plus new safety bounds for tool payload capture and persistence.

Changes:

  • Added OpenCode compatibility resolution (capability probing, signed schema bundle selection/rollback/quarantine) and expanded JSONL parsing/dispatch for text/tool/error frames.
  • Hardened tool activity tracking (bounded payload caps, reordered progress/result reconciliation, settled-id dedupe) and persisted user-visible compatibility diagnostics (progress) through conversation APIs.
  • Added release-time guardrails (registry config validation) and expanded test coverage + suite wiring for OpenCode routing/probing/registry behavior.

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/lib/server/stuck-created-sweep.test.ts Adjusts route-shape assertion for turn spawn timing change.
src/lib/server/conversation-write-guards.ts Treats progress as harness-owned assistant field (client-forgery stripped).
src/lib/server/conversation-write-guards.test.ts Adds coverage ensuring forged progress is removed.
src/lib/server/cave-home-reconciliation.ts Adds injectable clock (lockNow) for deterministic lock timing in tests.
src/lib/server/cave-home-reconciliation-types.ts Extends reconciliation options with lockNow test override.
src/lib/opencode-stream.ts Major expansion of OpenCode event parsing + schema-driven envelopes + JSONL handler.
src/lib/opencode-stream.test.ts Adds extensive fixture coverage for schema validation/quarantine behaviors and JSONL dispatch.
src/lib/opencode-compatibility.test.ts Adds comprehensive tests for signed registry bundle validation, caching, monotonicity, quarantine, redaction.
src/lib/opencode-bin.ts Prefers user launch PATH while retaining scoped fallback; updates spawn env.
src/lib/opencode-bin.test.ts Tests PATH preference merge behavior across platforms.
src/lib/chat-turn-state.ts Includes progress in persisted turn mapping/cloning.
src/lib/chat-tool-events.ts Adds payload caps, reordered tool result/progress buffering, bounded dedupe windows.
src/lib/chat-tool-events.test.ts New tests for reordered frames, caps, dedupe windows, and bounded persistence.
src/lib/cave-conversations.ts Extends persisted turn schema to include progress.
src/lib/cave-conversations.test.ts Ensures progress round-trips through persistence/mapping.
src/app/api/chat/send/route.ts Integrates OpenCode compatibility probing/launch selection, structured JSONL handling, diagnostics persistence, privacy bounds.
src/app/api/chat/send/route-opencode.integration.test.ts New end-to-end route test with a shim opencode executable (argv/probe/SSE/persistence).
src/app/api/chat/send/harness-routing-opencode.test.ts Updates routing assertions to match new capability/schema-driven behavior.
src/app/api/chat/send/harness-routing-host-session.test.ts Updates host-session identity assertions for OpenCode unrecorded native resume behavior.
src/app/api/chat/send/harness-routing-copilot-jsonl.test.ts Updates harness session id routing assertions to include OpenCode path.
src/app/api/chat/send/chat-send-capabilities.ts Adds robust OpenCode run --help / --version probing + parsing for capability selection.
src/app/api/chat/send/chat-send-capabilities.test.ts New tests for probing behavior, option parsing, and bounded help-derived capability evidence.
src/app/api/chat/conversation/[id]/route.ts Normalizes and returns persisted progress entries via conversation API.
src/app/api/chat/conversation/[id]/route.test.ts Adds tests for stripping forged progress on PUT and preserving stored diagnostics on GET.
scripts/run-tests.mjs Wires new tests into suites and alias-loader set.
scripts/git-hooks-pre-commit.test.mjs Prefers Git Bash on Windows to avoid WSL bash launcher failures.
scripts/check-opencode-registry-release.test.mjs Adds tests ensuring release workflow + guard script + docs stay in sync and don’t leak credentials.
scripts/check-opencode-registry-release.mjs New release-only guard validating registry URL/keyring/checkpoint inputs.
scripts/cave-home-migration-windows.test.ts Uses injected monotonic clock for deterministic retry timing; tweaks timeout.
next.config.ts Excludes src/**/* from output tracing to avoid unintended NFT checkout capture.
docs/opencode-compatibility-registry.md Documents registry config, publishing, canonicalization, rotation.
.github/workflows/release.yml Enforces registry configuration validation during release packaging.
Comments suppressed due to low confidence (1)

src/app/api/chat/send/route.ts:1042

  • This comment line is misindented (starts at column 0) and should be aligned with the surrounding block to avoid formatting/lint issues.
  await ensureAdapterManifestScaffold(binding.harness);
// The Responses API does not expose a documented, enforceable equivalent of
  // Cave's read-only sandbox. Do not downgrade that security promise to a

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// harness uses coven run's capability probe.
const hermesDirect = !sshRuntime && binding.harness === "hermes";
const openCodeDirect = !sshRuntime && binding.harness === "opencode";
// Cave's Read-only control is a security promise, not a prompt hint.
resolveOpenCodeCompatibility,
} from "@/lib/opencode-compatibility";
import { handleOpenCodeJsonLine } from "@/lib/opencode-stream";
import { parseOpenCodeRunEvent } from "@/lib/opencode-stream";
Comment on lines +239 to +244
function hasRunOption(help: string, flag: string): boolean {
// Only option-definition lines count. Mentions in examples, migration notes,
// or another command's help text are not evidence that `opencode run` takes
// this flag.
return new RegExp(`^\\s*(?:-[A-Za-z],?\\s+)?${flag}\\b(?:\\s|=|,|$)`, "m").test(help);
}
- `OPENCODE_SCHEMA_REGISTRY_URL` — canonical HTTPS URL for the signed OpenCode bundle.
- `OPENCODE_SCHEMA_REGISTRY_PUBLIC_KEY` — PEM-encoded Ed25519 public key that verifies that bundle.

- `OPENCODE_SCHEMA_REGISTRY_CHECKPOINT` — compact JSON with the current bundle's `sequence` and lowercase SHA-256 `payloadHash` of its canonical unsigned payload.
Comment on lines +50 to +63
function utf8Bytes(value: string | undefined): number {
return value === undefined ? 0 : new TextEncoder().encode(value).byteLength;
}

export function capLiveToolPayload(value: string | undefined, cap: number): string | undefined {
if (value === undefined || utf8Bytes(value) <= cap) return value;
const suffix = "\n[tool payload truncated]";
const budget = Math.max(0, cap - utf8Bytes(suffix));
let end = Math.min(value.length, budget);
// UTF-16 code-unit offsets are not byte offsets. Trim to a UTF-8 boundary
// so a hostile unicode payload cannot exceed the advertised byte cap.
while (end > 0 && utf8Bytes(value.slice(0, end)) > budget) end -= 1;
return `${value.slice(0, end)}${suffix}`;
}
@BunsDev
BunsDev deleted the agent/issue-3846-opencode-tool-activity branch July 26, 2026 09:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add full OpenCode tool-activity compatibility

4 participants