diff --git a/.changeset/await-plugin-activation.md b/.changeset/await-plugin-activation.md new file mode 100644 index 0000000..41409ab --- /dev/null +++ b/.changeset/await-plugin-activation.md @@ -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. diff --git a/bun.lock b/bun.lock index 7e6f9c9..e9e0b2e 100644 --- a/bun.lock +++ b/bun.lock @@ -32,7 +32,7 @@ }, "packages/drive": { "name": "opencode-drive", - "version": "2.0.0", + "version": "2.0.1", "bin": { "opencode-drive": "bin/opencode-drive", }, @@ -40,7 +40,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", @@ -295,11 +295,11 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@opencode-ai/client": ["@opencode-ai/client@0.0.0-dev-18862", "", { "dependencies": { "@opencode-ai/protocol": "0.0.0-dev-18862", "@opencode-ai/schema": "0.0.0-dev-18862" }, "peerDependencies": { "effect": "4.0.0-rc.112", "solid-js": ">=1.9.0" }, "optionalPeers": ["effect", "solid-js"] }, "sha512-smimfBrxt/or8nEIdkE3cVycokDQKWfKWbuO4NYKDlnBMZ28igkxMLSKWJ3sve8SrvjLtpW66ALPhWg+oDTKyA=="], + "@opencode-ai/client": ["@opencode-ai/client@0.0.0-dev-18911", "", { "dependencies": { "@opencode-ai/protocol": "0.0.0-dev-18911", "@opencode-ai/schema": "0.0.0-dev-18911" }, "peerDependencies": { "effect": "4.0.0-rc.112", "solid-js": ">=1.9.0" }, "optionalPeers": ["effect", "solid-js"] }, "sha512-Gq951CE1ue4MF11+4qJfdxoWD5TxdBHe47RIPmlaAvYXPtAXZbdRY9XHQzBFSQEnYWW22QQEG39GuIS0ITpHHA=="], - "@opencode-ai/protocol": ["@opencode-ai/protocol@0.0.0-dev-18862", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-dev-18862", "effect": "4.0.0-rc.112" } }, "sha512-LtNCvigOyGmYL53Vt/KxaUyD/xGDMoNsjMjTXl85ZsxMoQ6GvOyiDelealEHEn9z8O5mhkXkUmrRP9byzhJ0vA=="], + "@opencode-ai/protocol": ["@opencode-ai/protocol@0.0.0-dev-18911", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-dev-18911", "effect": "4.0.0-rc.112" } }, "sha512-fWup6XvEEK9WuA87+Q46OnAtwyFKti/nYstZN3A6HxQdOtAre/mvLb/SVnnrBNaDIjt9yD+3HsY6wbhtn717KA=="], - "@opencode-ai/schema": ["@opencode-ai/schema@0.0.0-dev-18862", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112" } }, "sha512-oCl4sNE3cxEBpdqx1IslImcIU9w7CJwf+L0M9uY3E9CVgRnCRIDhvXy2yTGb5MwTbVp80NdvSZCzmgWe31uxNQ=="], + "@opencode-ai/schema": ["@opencode-ai/schema@0.0.0-dev-18911", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112" } }, "sha512-8xJlzsJizyt/e0NJFZxqYk4ugyX9ziVZ03WJBJriPsZxhW+EbQHdyPRGhxwswGdV6mvW3e8JBnHP4plUlGd5rw=="], "@opencode-drive/catalog": ["@opencode-drive/catalog@workspace:apps/catalog"], diff --git a/packages/drive/package.json b/packages/drive/package.json index 2af3f52..d9925d6 100644 --- a/packages/drive/package.json +++ b/packages/drive/package.json @@ -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", diff --git a/packages/drive/test/manual/tui-regressions/README.md b/packages/drive/test/manual/tui-regressions/README.md index fcee470..537aa81 100644 --- a/packages/drive/test/manual/tui-regressions/README.md +++ b/packages/drive/test/manual/tui-regressions/README.md @@ -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 @@ -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 @@ -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`. diff --git a/packages/drive/test/manual/tui-regressions/compact-admission.ts b/packages/drive/test/manual/tui-regressions/compact-admission.ts index 429df87..50d2b8d 100644 --- a/packages/drive/test/manual/tui-regressions/compact-admission.ts +++ b/packages/drive/test/manual/tui-regressions/compact-admission.ts @@ -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, @@ -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, diff --git a/packages/drive/test/manual/tui-regressions/open-picker.ts b/packages/drive/test/manual/tui-regressions/open-picker.ts index 8f61190..2efa294 100644 --- a/packages/drive/test/manual/tui-regressions/open-picker.ts +++ b/packages/drive/test/manual/tui-regressions/open-picker.ts @@ -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