Commit Graph

3 Commits

Author SHA1 Message Date
Tom Boucher
a3853472de fix(#2598): declare OpenCode subagent dispatch synchronous, not background (#2682)
* fix(#2598): declare OpenCode subagent dispatch synchronous, not background

capabilities/opencode/capability.json advertised dispatch.background: true and
dispatch.backgroundDispatch: true. negotiateHostCapabilities and every
degradationFor / shouldFlattenDispatch consumer trusts these per-field values, so
declaring a capability the host lacks OVERSTATES it — the opposite of the
fail-closed posture the negotiation is built for.

The issue's own citations needed checking before acting: the host-integration
matrix (ADR-1239's designated deployment source-of-truth) documented `true` with
NEWER evidence than the issue cited, and explicitly marked the issue's
sst/opencode#5887 reference as a stale snapshot superseded by #2087. git log
confirms #2087 deliberately flipped these from false to true, citing OpenCode
v1.15.0/v1.17 as "background subagents enabled by default in all modes". Applying
the issue as filed would, on that evidence, have REGRESSED a deliberate update.

So the claim was verified against current upstream rather than either document.
`packages/opencode/src/effect/runtime-flags.ts` on `dev` today reads:

    experimentalBackgroundSubagents: enabledByExperimental("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS")

`enabledByExperimental` falls back to the `experimental` flag and `bool()`
defaults to false — the parameter is hidden from the model unless an operator
opts in by env var. Upstream #29638 is still OPEN and confirms the session loop
`tasks.pop()`s one subtask at a time. #2087's "default-on in all modes" reading
does not hold against current dev.

The issue's CONCLUSION is therefore right even though part of its evidence was
superseded: concurrent dispatch cannot be relied on, so both fields are false.

The matrix rows are corrected with the verified citation rather than reverted to
the old #5887 quote, so the record shows why the value is false TODAY rather than
re-asserting evidence that was legitimately superseded. Neighbouring sub-fields
are untouched and pinned by test: namedDispatch, subagentToolkit, and
isolation:'orchestrator-worktree' (which works via `opencode run --dir` at the OS
process level and is unaffected — #2584 does not depend on this value either way).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015TCwhbMuY37DzRMCfzTABJ

* fix(#2598): re-pin the dispatch contract tests to synchronous OpenCode dispatch

gsd-test on the descriptor change came back FAILED (5 unique, both node
versions). The failures were not incidental — they were deliberate contract-pin
tests encoding #2087's decision, one named literally "background UPGRADE":

  tests/host-integration-descriptors.test.cjs
    - EXPECTED_FLATTEN[opencode] === false (background-eligible)
    - the derived background-eligible set pin
  tests/opencode-imperative-reference.test.cjs
    - "descriptor declares background dispatch true/true (v1.15/v1.17 upgrade)"
    - "background UPGRADE changes shouldFlattenDispatch: false now"

So this is a recorded decision being reversed, not drift being corrected, and it
is reversed on evidence: current upstream `dev` gates the capability behind
OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS (default false) and upstream #29638
(OPEN) confirms the session loop still handles one subtask at a time. The issue
is filed by the maintainer and explicitly directs "update golden-parity /
validator fixtures as needed", which sanctions re-pinning.

Behavioral consequence, verified: shouldFlattenDispatch(opencode) now returns
TRUE, so GSD serializes opencode dispatch instead of trusting concurrency it
cannot get. That is the correct fail-closed direction and is safe today — no
shipped GSD flow drives OpenCode background waves (per the issue), and
isolation:'orchestrator-worktree' is unaffected because it works at the OS
process level via `opencode run --dir`, not via the native subagent.

Each re-pinned test now asserts the retracted contract in the opposite
direction — feeding the #2087 axes back in must still yield "would not flatten" —
so a silent re-flip of either field is caught rather than merely un-asserted.

lint:ci exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015TCwhbMuY37DzRMCfzTABJ

* chore(#2598): backfill changeset pr number (#2682)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 21:47:54 -04:00
Tom Boucher
ed06b6a4b9 fix(#2329): write opencode slash commands to commands/ (plural), migrate legacy command/ (#2354)
* test(#2329): fail-first tests for opencode commands/ (plural) command dir

Red phase, empirically probed: global/local install lands in command/ (singular)
with 71 gsd-*.md files and no commands/; the manifest records 71 keys under
command/ and zero under commands/; all four declaring sites report 'command'.
Migration coverage is black-box (two sequential install runs against one
configDir) so it holds regardless of how the fix implements cleanup.

The Kilo guard passes today by design — a forward-looking no-collateral check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA

* fix(#2329): write opencode commands to commands/ (plural), migrate legacy command/

OpenCode discovers slash commands from commands/ (plural); the installer wrote
them to command/ (singular), so none of the ~71 /gsd-* commands appeared in the
TUI. Five sites declared the directory and all had to agree:

- capabilities/opencode/capability.json: both artifactLayout destSubpath entries
  (global + local) and hostBehaviors.flatCommandDir
- bin/install.js: the manifest prefix was a SEPARATE hardcoded 'command/' literal,
  so the manifest would have diverged from the descriptor even after a rename. It
  now derives from _hostBehaviors(runtime).flatCommandDir.
- src/install-engine.cts installOpencodeFamilyArtifacts: the actual write target,
  which bypasses resolveRuntimeArtifactLayout via combinedFamilyInstall. This was
  a fifth site the issue did not list — without it the descriptor change alone
  would not have moved a single file.

Migration: an upgrade over a pre-fix install removes only manifest-proven
GSD-managed files from the legacy command/ dir and rmdirs it once empty.
Unmanifested user files are preserved, never deleted.

Kilo shares the opencode family install path and is explicitly unaffected —
pinned by a no-collateral test.

Note on the tests: the migration cases originally built their legacy fixture by
running the installer and relying on it to produce command/ — i.e. they depended
on the bug to set up the fixture, and became unsatisfiable the moment it was
fixed (block 1 requires command/ to be absent after a fresh install). They now
fabricate the legacy layout explicitly, including rewriting the manifest keys to
the command/ prefix — which is load-bearing, since the migration only removes
manifest-proven files and an unrewritten fixture would silently no-op and pass
even against a broken migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA

* test(#2329): regenerate opencode install golden after rebase onto next

The golden conflicted on rebase because #2322 also regenerated it. Resolved by
regenerating from the merged source rather than hand-merging a generated file;
the only delta is the 71 command/gsd-*.md -> commands/gsd-*.md key renames.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA

* test(#2329): update stale tests that pinned opencode's singular command/ dir

Seven tests encoded the old contract (opencode: command/gsd-help.md exists, the
descriptor's flatCommandDir, the install-integration contract, and the
resolveRuntimeArtifactLayout golden). They passed in the red phase precisely
because they pinned the buggy singular dir; the fix intentionally changes that
contract, so these are stale-test corrections, not regressions.

Kilo shares the opencode family install path and is deliberately NOT changing —
it stays on command/ (singular). The shared opencode/kilo test is now split via
an explicit per-runtime dir map so the two cannot be conflated, and Kilo's own
layout test is untouched. tests/opencode-command-dir-plural.test.cjs
independently pins Kilo unchanged end-to-end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA

* docs(#2329): changeset for opencode commands/ dir fix

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA

* docs(#2329): backfill PR number 2354 into changeset

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA

* docs(#2329): correct the changeset — do not assert opencode ignores command/

The changeset repeated the issue's stated mechanism ("OpenCode discovers them
from commands/ ... a clean install produced no usable commands in the TUI at
all"). OpenCode's source contradicts that: packages/core/src/v1/config/command.ts
globs {command,commands}/**/*.md, so BOTH names resolve, and its own skill doc
still calls .opencode/command/ typical. Shipping that claim as a release note
would document a mechanism that does not exist.

The change is still right, for the stronger reason: OpenCode's config docs list
plural as the convention and singular as backwards compatibility, so GSD was
shipping on the alias the vendor may withdraw. Reworded to describe it as the
alignment it is, decided on OpenCode's source and docs rather than on bug reports
in either repo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA

* fix(#2329): baseline opencode's commands/ surface — closes a data-loss path this PR opened

Not a bookkeeping gap. Moving opencode's command dir to commands/ moved the
install destination to a surface the first-time baseline scan does not cover:
000-first-time-baseline's RUNTIME_SURFACES.opencode lists ['gsd-core','command',
'skills','agents'] — no 'commands'.

installOpencodeFamilyCommands unconditionally unlinks every gsd-*.md under its
destination before writing the fresh set (install-engine.cts:870-873), with zero
manifest or migration involvement. The only thing that protects a pre-existing
file is assertInstallerMigrationsUnblocked, which runs before materialization and
halts when the baseline scan flags an unknown file at a KNOWN surface.

Probed: a pre-existing commands/gsd-plan.md is silently destroyed (install exits
0). The identical file under the legacy, already-baselined command/ surface
correctly halts the install with "installer migration blocked pending user
choice". So this PR would have traded a protected surface for an unprotected one.

Fixed with a NEW fix-forward migration rather than editing 000, per
docs/installer-migrations.md:131-134 — an applied migration never re-runs, so
editing 000 would only protect fresh installs and leave every existing machine
exposed. A new id runs for both populations and drifts no shipped checksum;
adding its entry to EXPECTED_CHECKSUMS is the case that test explicitly sanctions.
All five pre-existing shipped checksums verified byte-identical.

Kilo is excluded by the migration's runtimes filter and keeps command/.

This was previously deferred as a PR-body note claiming "low impact — nothing
else acts on baseline-scan misses". That claim was never probed and was wrong.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA

* docs(#2329): drop the parenthetical product description from the changeset

The product-name purity guard (#1777) rejects "Kilo (which still uses
command/)" — fragment prose renders verbatim into CHANGELOG.md, so a product
name must not carry a parenthetical. Reworded to a plain sentence; the meaning
is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SLufH5sDuqA1AiEGu45cuA

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 08:14:59 -04:00
Tom Boucher
396f44bd0b feat(architecture): [EoS/opencode] Migrate OpenCode onto the Embeddable Orchestration System (ADR-1239, #2087)
Route OpenCode (and its Kilo sibling) through the public Host-Integration Interface and
land two Context7-verified capability upgrades. Byte-identical install output for all 16
runtimes (golden parity asserted).

Through the interface (AC2):
- OpenCode/Kilo's bespoke commands+skills+plugin install (the inline
  `else if (isOpencode || isKilo)` block) moves into the engine
  (installOpencodeFamilyCommands/Artifacts in src/install-engine.cts), dispatched by
  installRuntimeArtifacts when the descriptor declares hostBehaviors.combinedFamilyInstall.
  opencode/kilo now flow CLI -> _runtimeAdapter -> installRuntimeArtifacts like the skills
  runtimes. _isSkillsRuntime no longer excludes them; the bespoke block + dead
  copyFlattenedCommands are removed.
- Every hardcoded `runtime === 'opencode'`/`isOpencode` branch is folded into
  descriptor-driven runtime.hostBehaviors. ZERO `runtime === 'opencode'`/`'kilo'`
  string-equality remain in bin/install.js / install-engine.cts / runtime-artifact-conversion.cts.

Upgrades (AC4):
- Background dispatch: OpenCode shipped experimental background subagents in v1.15 and
  made them default-on in v1.17 -> dispatch.background/backgroundDispatch flip to true;
  shouldFlattenDispatch(opencode) now returns false (behavioral change; type: Changed).
- Expanded event surface: the OpenCode plugin subscribes permission.asked/replied +
  session.error.

Tests: opencode-imperative-reference (adapter/profile, shouldFlattenDispatch pin,
fail-closed negotiate, hostBehaviors, AC2 source-guard) + extended plugin surface test.
Docs: capability matrix v1.15/v1.17 citations. Changeset (Changed). gitignore .memdb//.memtrace/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:17:05 -04:00