Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/await-plugin-activation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"opencode-drive": patch
---

Bump the pinned `@opencode-ai/client` to `0.0.0-dev-18911` so the generated SDK exposes `plugin.awaitActivation`, which scripts need before reading agents or models from a cold location on current V2 servers.
10 changes: 5 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/drive/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"@effect/platform-node": "4.0.0-rc.112",
"@effect/platform-node-shared": "4.0.0-rc.112",
"@napi-rs/canvas": "1.0.2",
"@opencode-ai/client": "0.0.0-dev-18862",
"@opencode-ai/client": "0.0.0-dev-18911",
"@opentui/core": "0.4.5",
"@types/bun": "1.3.13",
"@typescript/native-preview": "7.0.0-dev.20251207.1",
Expand Down
25 changes: 24 additions & 1 deletion packages/drive/test/manual/tui-regressions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,13 @@ and terminal frame in `state-machine-failure.json`.
| `cancelled` | Cancellation removes the known pending item while setup is blocked. The client still proposes a fresh compaction ID and completes it after healing; cancellation itself does not permanently reserve the old ID. |
| `rollback` | Killing connections before admission removes the unacknowledged row and displays the transport error. After healing, retry compaction and submit a recovery prompt successfully. |

The simulated compaction reply is a structured summary with the `##` section
headings that OpenCode requires since PR #46751; a plain sentence now settles as
`compaction.failed` ("did not match the required template") instead of
`completed`. The fake model routes compaction requests on the stable prompt
frame ("Summarize only the history shown"), not on template wording, which has
already changed once since.

The simulated model paces a busy-step checkpoint before holding the response
open; this isolates admission behavior from stream-publication behavior. The
bounded-flush tests separately verify complete single bursts without a later
Expand Down Expand Up @@ -364,7 +371,10 @@ Drive writes current V2 `cli.json` under its isolated `OPENCODE_CONFIG_DIR`.
The probe expresses `session.new_location: "inherit"` and a `Ctrl-N` binding
through `tuiConfig`, making the movement check an explicit fixture contract
without a manual config-file workaround or installed-client changes. Fixture
sessions also select the simulation model and build agent explicitly.
sessions also select the simulation model and build agent explicitly. Since
OpenCode PR #46639 plugins load after config, so a cold location answers
`agent.list` and `model.default` with empty data until activation; the fixture
calls `plugin.awaitActivation` for both project locations before reading them.

Successful runs write `open-picker-report.json` and print `passed: true`.
Failures retain the case, viewport, marker seed, checkpoint trace, server
Expand Down Expand Up @@ -413,6 +423,19 @@ bun run --cwd packages/drive drive start --name tui-multi-tool-interleavings \
--dev "$OPENCODE_DEV"
```

This probe currently fails on V2 at or after OpenCode PR #46724
([`anomalyco/opencode#37650`](https://github.com/anomalyco/opencode/issues/37650)) with
`InvalidRequestError: Expected JSON value at ["data"][2]["metadata"]["hidden"]`
from `GET /api/session/:id/permission`. That is a server defect, not probe
drift: the glob tool copies every optional input into its permission
`metadata` (`path`, `limit`, and now `hidden`), so an omitted input leaves an
`undefined` value that the response encoder for `Record(String, Unknown)`
rejects, and the endpoint answers 400 while that permission is pending. Any
model call that omits `hidden` (or `path`, or `limit`) trips it. The probe
deliberately keeps its realistic `{ pattern, path, limit }` input rather than
adding `hidden: false` to dodge the bug; expect it to pass again once the
server drops or defaults undefined permission metadata.

## Plugin registry

`plugin-registry.ts` checks that plugin registrations reach both the frontend and the model without a server restart. A discovered local plugin registers a command and a tool; the probe asserts the command through `command.list`, the tool through the captured provider request body, and the slash menu through the TUI. It then writes a second plugin into `.opencode/plugins/` at runtime, asserts both appear, removes it, and asserts it disappears. Activation is observed through `plugin.list`.
Expand Down
14 changes: 12 additions & 2 deletions packages/drive/test/manual/tui-regressions/compact-admission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import { saveFailure } from "./state-machine.js"
// Run each scenario independently so a failed checkpoint cannot mask later cases.
const scenario = process.env.OPENCODE_DRIVE_COMPACT_CASE ?? "ordered"
const cols = Number(process.env.OPENCODE_DRIVE_COLS ?? 100)
const compactionSummary = [
"## Objective",
"- Inspect the admission fixture README.",
"",
"## Work State",
"### Completed",
"- The fixture README was inspected.",
].join("\n")

export default defineScript({
network: true,
Expand Down Expand Up @@ -55,8 +63,10 @@ export default defineScript({
yield* llm.serve((request) => {
const body = JSON.stringify(request.body)
if (body.includes("title generator")) return Stream.make(Llm.text("Admission fixture"))
if (body.includes("Create a new anchored summary") || body.includes("Update the anchored summary below"))
return Stream.make(Llm.text("The fixture README was inspected."))
// Compaction (OpenCode PR #46751) asks for a structured summary and
// rejects a reply without one of its `##` section headings as
// `compaction.failed`. Match the prompt frame, not the template text.
if (body.includes("Summarize only the history shown")) return Stream.make(Llm.text(compactionSummary))
// History includes previous markers. Only the newest prompt selects a reply.
const marker = ["CA_WARM", "CA_HOLD", "CA_NEXT"].reduce((latest, candidate) =>
body.lastIndexOf(candidate) > body.lastIndexOf(latest) ? candidate : latest,
Expand Down
8 changes: 6 additions & 2 deletions packages/drive/test/manual/tui-regressions/open-picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,14 @@ export default defineScript({
const location = yield* opencode.location.get({ location: { directory: `${artifacts}/files` } })
const archive = yield* opencode.location.get({ location: { directory: `${artifacts}/files/archive` } })
assert.notEqual(archive.project.id, location.project.id, "archive must be a distinct Git project")
// Plugins load after config (OpenCode PR #46639), so a cold location
// answers agent and model reads with empty data until activation.
yield* opencode.plugin.awaitActivation({ location })
yield* opencode.plugin.awaitActivation({ location: archive })
const model = (yield* opencode.model.default({ location })).data
const agent = (yield* opencode.agent.list({ location })).data.find((agent) => agent.id === "build")
assert(model)
assert(agent)
assert(model, "no default model after plugin activation")
assert(agent, "no build agent after plugin activation")
// Created before frontend launch: these are neither open tabs nor event-
// hydrated sessions. Distinct titles fit the 44-column picker as well.
const older = yield* opencode.session
Expand Down