feat(app): the harness switcher in the chat box (#1549) - #1550
Open
aarontrowbridge wants to merge 1 commit into
Open
aarontrowbridge wants to merge 1 commit into
aarontrowbridge wants to merge 1 commit into
Conversation
The composer gets a fourth select-slot — the harness — riding the
solver-mode trio end to end:
- engine: GET/POST /amicode/harness (server/amicode/harness.ts). The id
allowlist IS the extension-published registry view
(harness-options.json) — the engine never hardcodes harness identity;
POST validates against it, no-ops when already current, and writes
{harness, status:"switching"} to the ops dir (same loopback guard +
fixed value-free refusals as the solver-mode flip).
- extension: harness_switch.ts (the handshake reader/writer + the
watcher, the solver watcher's poll+busy-latch discipline) and the
registry layer (harness.ts): harnessMenu — the ONE serialization both
fronts render — and decideHarnessSwitch — the ONE gate both consult
(unknown/needs-setup/unentitled blocked with their reason; the
harness.telaio read-side gate lives here, never as a spawn-time
failure). The palette command now renders from the same menu and gates
through the same decision; the watcher persists the setting, republishes
the menu, restarts, and settles ready — a refused switch settles ready
at the STILL-CURRENT harness, never a lie.
- composer: the harness select (disabled-with-reason entries carrying the
disclosure "switching harnesses switches session history" at the point
of switching), harnessDegraded dimming of agent/model/variant under a
non-opencode harness (Harness Contract v1 accepts none), and the
harness switch banner narrating switching → restarting → ready.
Under opencode, byte-for-byte unchanged when untouched: no published
registry → the control renders nothing.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This was referenced Sep 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1549.
What landed, per layer
Engine route (
packages/app-bundle/overlay/packages/opencode/src/server/amicode/harness.ts+ registration in the instance httpapi server):GET /amicode/harnessserves{ok, harness, status, options?}— the current harness plus the extension-published registry view (harness-options.jsonin the ops dir). The engine never computes or hardcodes the registry; a response withoutoptionsmeans "no registry published" and the composer control stays hidden (stock-opencode behavior, unchanged).POST /amicode/harness({harness}) validates the id against that published view (it IS the allowlist), no-ops when already current (the watcher is never poked for nothing), and atomically writes{harness, status:"switching"}— the same loopback guard, sibling ok/false shape, and fixed value-free refusals as the solver-mode flip.Extension (
packages/extension/src/harness_switch.ts,harness.ts,extension.ts):harnessMenu— the ONE serialization of the registry both fronts render;decideHarnessSwitch— the ONE gate both consult. Unknown ids, needs-setup, and unentitled targets are blocked with their reason; theharness.telaioentitlement read-side gate lives here (disabled-with-reason), not as a hidden spawn-time failure.watchHarnessSwitch— the solver watcher's poll + busy-latch discipline overharness.json; the watcher performs the real switch (gate →amicode.harnesssetting persist → menu republish →amicode.restartServer) and settlesstatus:"ready". A refused or failed switch settles ready at the still-current harness — never a ready file claiming a switch that didn't happen.amicode.selectHarnesspalette command now renders from the same menu and re-gates through the same decision at confirm time; boot publishes the options file (best-effort, never blocks boot).Composer (app + session-ui overlay):
harnessControl) beside agent/model/variant. Entries carry the honest state; disabled entries show their reason; the disclosure copy — "Sessions are harness-local — switching harnesses switches session history" — rides every selectable entry at the point of switching.harnessDegraded: under a non-opencode harness the agent/model/variant slots dim inertly with a "Not accepted by this harness" affordance (Harness Contract v1 accepts none);display: contentsotherwise — layout byte-identical.Verification (commands + counts verbatim)
bunx vitest run test/harness.test.ts(extension) — 32 passed (incl. the new menu/gate/two-fronts pin tests)bunx vitest run test/harness_switch.test.ts(extension) — 12 passedbunx vitest run test/scores(extension, untouched) — 20 files / 323 passed (expected count matches)bun run typecheck(packages/extension) — cleanbun test test/server/amicode-harness.test.ts(materialized engine tree) — 12 passed, 48 expect() callsbun test test/server/amicode-connections.test.ts(solver-mode neighbors, materialized) — 103 passedbun test --conditions=solid src/components/harness-switch.test.ts src/components/prompt-input/harness.test.ts(materialized app) — 11 passedbash packages/app-bundle/scripts/engine_typecheck_gate.sh— OK (only the Base-pin drift: manifest upstream_base_sha stale vs absorbed overlay #1229-tracked base-drift allowlist errors)bash packages/app-bundle/scripts/overlay_typecheck_gate.sh— OK across 7 packages (app ui session-ui core schema sdk/js llm)node packages/app-bundle/scripts/drift_gate.mjs— PASS (manifest refreshed for the new/changed overlay files, 859 files)bun test --conditions=browser src(materialized session-ui) — 433 pass / 1 fail: theeditable-diff-viewselection-highlight test fails identically with this slice's changes reverted (pre-existing on this base, unrelated CSS topic)bunx vitest run(extension full suite) — 3689 passed / 16 failed across 8 files, all pre-existing: the same 8 files (amicode_service_* auth/bootstrap/proxy/wiring + terminal) fail identically at HEAD without this slice (environment-dependent, need a built dist)bun test --conditions=solid --preload ./happydom.ts ./src(materialized app) — 1173 pass / 19 fail + 2 errors, all pre-existing: identical failures with the slice's changed files reverted (i18n parity, sync reducers, editor-dom, offset observers)Notes
decideHarnessSwitch → persist; the same inputs land the same persisted harness from either front, and a blocked request persists nothing from either.