* feat(od-next): open registered runtimes and CLI BYOK Use the effective runtime registry for OD Next admission and remove retired capability qualification. Keep native observation and existing resume/cold recovery, with the evidence graph owned by observability contracts. Add run-scoped BYOK connection flags with caller-selected environment key input and reuse the existing provider bridge. Accept equivalent frozen Skill JSON and Base64 representations while preserving content and ownership boundaries. Validated with focused contracts, runtime, public Run and CLI regressions; eight real native agents and a real BYOK request/production pair; guard and workspace typecheck. Refs #8701. * fix(web): describe BYOK project file tools Explain that API-provider project runs use OpenCode file tools and require OpenCode to be available. Update the existing notice in all 19 locales and its component/browser regressions. Validated with red/green notice checks, real settings and preview follow-ups, guard and workspace typecheck. Refs #8701. * fix(od-next): preserve hidden prototype form feedback Keep HTML hidden semantics within the shared layout layer without overriding authored product CSS or hidden-until-found behavior. This fixes name errors remaining visible after a successful registration. Validated with a red/green Chromium regression, three resource-boundary checks, a real Claude follow-up with desktop/mobile form interaction, guard and workspace typecheck. Refs #8701. * test(od-next): make Run fixtures select their execution path Explicitly select ordinary Runs for retry persistence, plain stdout replay and the compatibility single-run telemetry case. Keep task hierarchy and terminal fallback telemetry cases active, and assert the selected path. Restore owner write permission to the leftover-agent fixture's frozen directories only during teardown so recursive deletion works on POSIX. Skip symlinks and preserve cleanup errors and every original behavior check. Reproduced all four CI failures before the fix. Focused daemon and E2E checks passed (12 tests), along with guard, workspace typecheck and focused LSP diagnostics. Refs #8701. * fix(daemon): avoid passing Pi resource directories as prompt files Pi reads existing --append-system-prompt paths as files. Stop passing skills, design systems and task input directories there; their resource paths already travel in the composed RPC prompt. Validated with 104 focused runtime/RPC tests, a real Pi 1.1.0 offline startup red/green probe, workspace typecheck and guard. * fix(web): coalesce streamed calls in generation progress Fold in-flight snapshots and terminal inputs through the existing tool-event reducer before rendering the Design Files progress feed. Keep the latest input, stable call identity and the limit of twelve distinct calls. Reproduced the duplicate React key warning before the fix. Runtime and progress-feed regressions now pass alongside existing in-flight tool-row tests. * fix(web): preserve forms and browser storage in prototype previews Permit client-side form submission in opaque interactive previews. Route Web Storage artifacts through the existing isolated powered preview, including daemon hints for large files, and retain guards when isolation is unavailable. Verified URL and srcDoc form interaction, persisted values after reload, and host isolation with focused Chromium regressions. Focused viewer/runtime and daemon tests, workspace typecheck and guard passed. * test(web): keep srcDoc fixtures on the opaque preview path Use an existing non-storage sandbox-shim signal in transport lifecycle fixtures now that Web Storage artifacts select the isolated preview origin. Preserve every reload, activation, recovery and manual-save assertion. Validated all 338 cases in the three affected FileViewer test files, workspace guard and typecheck, and focused diagnostics. * fix(byok): accept keyless IPv6 loopback providers Recognize the bracketed IPv6 hostname returned by WHATWG URL parsing in both settings validation and the OpenCode BYOK runtime. Keep remote hosts key-required. Validated with focused daemon and Web tests, settings browser checks, workspace typecheck and guard. * fix(daemon): restore Pi sessions and preserve completed RPC turns Load prior Pi history with switch_session and capture the authoritative sessionFile through nonblocking get_state. Treat a host-requested SIGTERM after a clean completed turn as shutdown, including Pi builds that report exit 143, while preserving real errors and cancellation. Validated with focused protocol and public Run lifecycle tests, a real Pi generation and native resumed edit, workspace typecheck and guard. * fix(preview): isolate project storage and preserve hidden controls Give powered previews stable per-project loopback origins and reserve those hosts for matching powered GETs before API or static exceptions. Advertise the capability so newer Web clients retain the opaque fallback with older daemons. Reuse the active powered transport in Present. Share a low-specificity hidden-control guard across URL and srcDoc previews without overriding explicit author rules or until-found; preserve doctype mode when synthesizing a missing head. Validated with actual two-project storage, Workers, reload, Present and API isolation browser witnesses, form/hidden regressions, focused Web and daemon checks, workspace typecheck and guard. * test(e2e): align public canaries with best-effort OD Next Read the remaining recipe identity marker without the retired serialized package hash. Make the fake OpenCode help probe finite. Exercise the current public question-form action, completed question turns/new answer tasks, and no-artifact normal endings while retaining cancellation, artifact and deck assertions. Validated with six fake-runtime cases and focused active/off public browser canaries with explicit rollout flags; no production completion policy or timeout was relaxed. * fix(od-next): guide focus restoration after list updates Prefer native modal lifecycle and restore focus to a current control after list rerenders, falling back to the primary action when a filtered item disappears. Real OpenCode and Pi prototypes exposed detached-trigger focus loss. Keep this as best-effort build guidance, without output contracts or automatic repair rounds. Validated the actual artifacts through normal public edit tasks and browser interaction checks; profile resource tests passed. * fix(od-next): retain focus through inline task updates Extend the existing live-control guidance to inline list actions. Real Copilot and Qoder boards also lost keyboard focus when status updates rebuilt their cards; hidden filtered items need a visible fallback. * fix: align Discovery integration tests and presentation storage * test(daemon): tolerate transient memory origin cleanup races --------- Co-authored-by: zhouzheng <zhouzheng@refly.ai>
agent-protocol
Daemon module providing the ACP and pi RPC subprocess protocol adapters used to drive external AI agent CLIs from the OpenDesign daemon.
What changed (refactor history)
The module was originally two flat source files alongside a shared helper:
acp.ts(~1,744 lines) — the ACP (Agent Control Protocol) adapter, covering session setup, JSON-line streaming, model detection, and MCP server forwarding.pi-rpc.ts(~684 lines) — the pi--mode rpcadapter, covering prompt delivery, event mapping, session-file capture, and image forwarding.
Both files imported createJsonLineStream from the same JSON-line transport helper. The flat layout made the dependency between pi-rpc and acp invisible, and any file could reach into the other's internals.
The refactor used a strangler-fig pattern — no logic changes, only structural moves:
- Created
core/,acp/, andpi-rpc/subdirectories. - Moved
createJsonLineStreamintocore/json-line-stream.ts— the one primitive shared by both adapters. - Split
acp.tsinto eight concern files underacp/:types.ts,constants.ts,json.ts,models.ts,rpc.ts,session-params.ts,session.ts,updates.ts. - Split
pi-rpc.tsinto four concern files underpi-rpc/:internal.ts,events.ts,models.ts,session.ts. - Added per-subdirectory barrel
index.tsfiles with@moduledocblocks. - Made the root
index.tsre-export only from those subdir barrels, preserving the exact prior public surface (10 names). - Added
@moduledocblocks and per-export JSDoc to every file.
The public API surface (index.ts exports) is unchanged — external consumers see no difference.
Why this shape (architecture reasoning)
Moving createJsonLineStream into core/ dissolved the former pi-rpc → acp import edge that existed in the flat layout. The result is a pure star topology:
core/ ← acp/
core/ ← pi-rpc/
Neither acp/ nor pi-rpc/ imports the other. The declared allowedEdges for this domain is [] — no cross-concern imports are permitted at all. Any future shared primitive must land in core/ rather than creating a lateral edge.
This layout was chosen for the same reason as the design-systems reference implementation: structure without enforcement is cosmetic. The guard registration is intentionally deferred (see "Known limitations" below), but the full documented structure is in place so the registration is a single-line addition once the infra lands on main.
Import conventions
- All relative imports use
.jsextensions (Node ESM). core/is the foundation kernel. Bothacp/andpi-rpc/may import fromcore/directly ('../core/index.js'or a'../core/<file>.js'path);core/itself imports no sibling.acp/andpi-rpc/must not import each other. Cross-concern imports go only through the root barrel ('../index.js'), which is itself off-limits to the concern subdirs (it re-exports all of them and would create a cycle). If a new shared primitive is needed, add it tocore/.- The root barrel uses explicit named re-exports — never
export *— so the public surface is enumerable and free of silent name collisions. - External daemon code imports from
'./agent-protocol/index.js'(or the subpath equivalent) — never from a subdirectory path directly.
The 10 names on the public surface, in barrel order:
createJsonLineStream // core/
AcpMcpServerInput // acp/ (type)
ModelOption // acp/ (type)
buildAcpSessionNewParams // acp/
normalizeModels // acp/
detectAcpModels // acp/
attachAcpSession // acp/
mapPiRpcEvent // pi-rpc/
attachPiRpcSession // pi-rpc/
parsePiModels // pi-rpc/
Known limitations and staged migration
Guard registration is intentionally deferred. The check-barrel-imports.ts guard infra (CAPABILITY_BARREL_DOMAINS registry, pnpm guard wiring) is not yet on main. The full capability-barrel structure — core/ + concern subdirs, @module docblocks, per-export JSDoc, this README — is in place. The CAPABILITY_BARREL_DOMAINS entry lands in a follow-up once the guard infra merges.
core/ currently contains one file. json-line-stream.ts is the only member. A single-file core/ is justified here: it is the shared transport primitive that both adapters depend on, and the subdir makes the dependency direction explicit without requiring a comment to explain it.
Directory structure
agent-protocol/
├── index.ts Root barrel — named re-exports from core/, acp/, and pi-rpc/
├── core/
│ ├── index.ts core/ barrel
│ └── json-line-stream.ts createJsonLineStream: shared JSON-line transport
├── acp/
│ ├── index.ts acp/ barrel
│ ├── types.ts Shared ACP types and interfaces
│ ├── constants.ts Protocol constants (method names, timeouts)
│ ├── json.ts JSON-line parsing helpers for ACP stdout
│ ├── models.ts normalizeModels, detectAcpModels
│ ├── rpc.ts Low-level RPC send/receive helpers
│ ├── session-params.ts buildAcpSessionNewParams
│ ├── session.ts attachAcpSession: session lifecycle
│ └── updates.ts ACP update-event handling
└── pi-rpc/
├── index.ts pi-rpc/ barrel
├── internal.ts Shared primitives: JsonRecord, SendAgentEvent, TokenUsage, guards
├── events.ts mapPiRpcEvent: pure pi RPC → daemon event mapper
├── models.ts parsePiModels: `pi --list-models` parser
└── session.ts attachPiRpcSession: session lifecycle, image forwarding, abort
Consumers
| Consumer | What it imports |
|---|---|
runtimes/defs/shared.ts |
detectAcpModels, parsePiModels |
connectionTest.ts |
attachAcpSession |
server.ts |
attachAcpSession, attachPiRpcSession |