Repository navigation
v1.18.0
Minor Changes
-
2a68c41: Qualify against OpenCode 2.2.0.
OpenCode 2.2.0 reorganizes the
@opencode/aiexport surface.Messageis no
longer reachable from the package root there, and a root named import throws
SyntaxError: The requested module '@opencode/ai' does not provide an export named 'Message'. A V2 plugin that cannot be evaluated registers no hooks, so
every policy would be inert while the host looks healthy. This release moves the
import and the pins to the 2.2.0 package set.- Import path.
Messagemoves from the@opencode/airoot to
@opencode/ai/effect. On 2.2.0 the root no longer re-exports the schema
surface, so a root named import throws, as quoted above; verified against the
installed 2.2.0 dist.@opencode/plugin@2.2.0itself importsMessagefrom
/effect, so this is the host's own sanctioned path. No call site changes:
Message.useris still the constructor used atsrc/lib/v2-plugin.ts:542. - Pins.
@opencode/pluginand@opencode/aimove to2.2.0, and
@opentui/solidto0.7.0, which@opencode/plugin@2.2.0declares as an
exact peer requirement.@opentui/coreis deliberately not added as a direct
dependency:@opentui/solid@0.7.0already declares it as an exact dependency,
so an extra pin would duplicate a resolution the tree already settles. The
@opencode-ai/plugin1.19.3V1 pin is unchanged. Of the existingoverrides
entries,solid-js1.9.17still applies because@opentui/solid@0.7.0still
peer-pins exactly1.9.12;toml5.0.1is unaffected. - Todo capability. The registry probe already covers this host, so 2.2.0
needs no version check. Verified on the live 2.2.0 binary:availablewith
experimental.todo: true,unavailablewithout it. - Docs.
docs/installation.mdcarries a 1.18.x / 2.2.0 qualification row
listing only the seats actually exercised, and the pin-set paragraph now
explains the@opencode/airoot-versus-/effectboundary and why
@opentui/coreneeds no direct pin. - Test coverage.
test/test-e2e.mtsgains two canaries that need no model
provider: the permission seat denies a real finding and abstains on an
undecidable push. They run inside the real host setup with the workspace taken
from the host's ownlocation.directory; the push case passes no client,
because the point is that simulation skips the remote PR probe. The deny
canary asserts policy identity (secrets/secret_read), not message prose.
Verified on OpenCode v2.2.0:
npm run typecheckclean,npm test1370 passed /
0 failed,npm run test:install32 passed / 0 failed. The opt-in model-driven
probes remained unavailable because the provider returned
Model unavailablebefore guard behavior could run, which that harness reports
aslive unavailablerather than as a guard failure.The
zodadvisory is also cleared here.GHSA-mrhx-c7xc-m3w5covers
zod <4.6.6, and the vulnerable4.1.8copy arrived through the exact
@opencode-ai/plugin@1.19.3V1 pin while the V2 entrypoint importszod
without declaring it. A newzod: 4.6.6override dedupes both onto one patched
copy, andnpm auditnow reports zero vulnerabilities. That override must be
applied with a clean reinstall: an incrementalnpm installleaves the old
resolution in place and fails 14test:installchecks, which reads as an
incompatibility but is a stale tree.One pre-existing issue is deliberately not fixed here, because it needs a real
binary probe and changes behavior beyond a host qualification:
src/lib/policy-posture.tsdescribes thecode-mode-surfacerule as inert on the
qualified hosts because they "register noexecutetool". That rationale is not
accurate: the tool is registered, and this repository's own session calls it on
2.1.3. - Import path.
Patch Changes
-
2a68c41: Let the V2 permission seat abstain when remote PR state is unverified.
Every non-protected
git pushwas denied on OpenCode 2, with a message that contradicted itself. The seat always evaluates the policy core withsimulate: true, which deliberately skips the remote GitHub/Azure PR probe. The core then reportsneeds_approval(remote_state_unchecked), and the seat collapsed every non-allowedstatus intoeffect = "deny". The denial therefore demanded the result of a probe the same code path had just skipped.needs_approvalis a third state meaning "cannot decide here", not a finding.PermissionSeatResult.effectnow carriesabstainalongsideallowanddeny. Only ablockeddecision denies. An abstention leaves the host's configured effect and message untouched, so a configuredaskstaysask, andexecute.beforeruns the real check with the probe. The seat still cannot upgrade an effect.The abstention is journaled with
decision: "abstain",reason: "deferred_to_execute_before", and the preservedneeds_approvaldecision, so a skipped probe stays readable in the audit trail instead of appearing as a plain allow. The seat's fail-closedcatchblock is unchanged.guard_whystill reportsneeds_approval, because that is the honest answer for a simulation; this changes the seat, not the policy core.