Agent adapters
An adapter teaches wheeljack how to launch and talk to one coding agent CLI. The contract
lives in crates/wheeljack-core/src/adapters.rs; the wire shape is AdapterDto in
crates/wheeljack-core/src/dto.rs.
Adapters come in two grades:
- Shell adapters run the CLI in a PTY and inject prompts as text. Everything the user sees is terminal output.
- Structured adapters additionally speak a machine protocol, so wheeljack can render a real chat transcript with reasoning, tool calls, approvals, and cancellation.
Only structured adapters can drive the Plan board, autonomy, or review routing.
Manifest
Section titled “Manifest”{ "id": "claude-code", // [A-Za-z0-9._-], 1-80 chars "displayName": "Claude Code", "icon": "…", "executables": ["claude"], // at least one PATH candidate "supportedPlatforms": ["macos", "windows"], // only these two values "supportedApprovalPolicies": ["default", "full"], "launchCommand": "claude", // required, non-empty "promptInjection": "stdin", // stdin | paste_then_enter | manual "status": "installed", // installed | missing | unknown "setupHint": "Install with npm i -g @anthropic-ai/claude-code", "enabled": true, "supportsStructured": true, "presentation": { }, "streaming": { "preferred": { } } // see below}validate_adapter_manifest in adapters.rs rejects anything outside those
constraints before the adapter is ever launched.
Executable resolution is resolve_executable_path: a bare name is searched across PATH;
on Windows each candidate is expanded through PATHEXT, and .cmd/.bat shims are
re-wrapped so they launch correctly.
The structured profile
Section titled “The structured profile”streaming.preferred is what promotes an adapter to structured. It is resolved by
resolve_structured_adapter_launch in adapters.rs, which enforces every rule below and
fails the launch rather than degrading silently:
{ "launchCommand": "claude --output-format stream-json …", "promptDelivery": "stdin", "protocol": "claude-stream-json", "sessionMode": "persistent-session", // must start with "persistent-" "supportsFollowUp": true // must be exactly true}Rules:
sessionModemust begin withpersistent-andsupportsFollowUpmust betrue. A one-shot agent is not accepted, because the shell assumes a session survives a turn.protocolmust parse to a knownStructuredProtocol.promptDeliverymust equal the protocol’s ownprompt_delivery(). A mismatch is a manifest bug and is rejected at launch.
Protocols
Section titled “Protocols”StructuredProtocol in crates/wheeljack-core/src/terminal_runtime.rs is a closed set.
Adding an agent means either reusing one of these or implementing a new driver.
| Protocol | Prompt delivery | Driver |
|---|---|---|
claude-stream-json |
stdin |
claude |
codex-app-server |
json-rpc |
codex |
opencode-sse |
sse |
opencode |
pi-rpc |
json-rpc |
pi |
hermes-gateway |
json-rpc |
hermes-gateway |
hermes-acp |
json-rpc |
hermes-acp |
plain-argv |
argv |
plain |
plain-stdin |
json-rpc |
plain |
Capabilities
Section titled “Capabilities”Each protocol declares StructuredDriverCapabilities, and the shell renders from that
rather than hard-coding per-adapter behaviour:
| Capability | Protocols |
|---|---|
cancel |
claude-stream-json, codex-app-server, opencode-sse, pi-rpc |
interact (answer questions / approvals) |
claude-stream-json, codex-app-server, opencode-sse |
resume |
claude-stream-json, codex-app-server, opencode-sse, pi-rpc |
attached_terminal |
opencode-sse |
image_input |
claude-stream-json, and others per capabilities() |
On the WebView side these arrive as runtime.capabilities and are consumed through
agentRuntime.ts (agentRuntimeCapabilities, supportsAgentTurnCancel,
supportsAgentImageInput). Components should read the capability, never the adapter id.
Lifecycle
Section titled “Lifecycle”| Step | Command | Notes |
|---|---|---|
| Discover | adapter_list, adapter_detect |
registry plus PATH detection |
| Probe | adapter_probe |
cheap liveness check |
| Verify | adapter_verify |
full launch check; run twice before declaring failure |
| Repair | — | shell surfaces adapterRepairCommand() from the setup hint |
| Spawn | agent_structured_spawn |
starts a persistent session |
| Prompt | agent_structured_prompt |
delivered per promptDelivery |
| Respond | agent_structured_respond |
answers a question or approval |
| Cancel | agent_structured_cancel |
only when capabilities.cancel |
| Attach | agent_structured_terminal_attach |
only when capabilities.attached_terminal |
| Kill | agent_structured_kill |
intentional teardown, kept distinct from process failure |
| Parse | agent_protocol_parse |
converts raw lines into messages and events |
agent_protocol_parse runs in the core, not the WebView: the shell buffers output lines,
debounces, and sends them for parsing. Results carry a protocolSequence, and the shell
drops any result older than the runtime it already holds.
Launch configuration
Section titled “Launch configuration”The shell composes launch arguments through agentLaunchArgs, agentLaunchConfig, and
agentProjectAccessConfig in App.tsx. Project access maps to each agent’s own
permission vocabulary — approvalPolicy and sandbox — so “full access” means the same
thing to the user regardless of which agent is running. Changing a profile field marks the
adapter stale so the next launch re-verifies instead of reusing a stale probe.
Adding an adapter
Section titled “Adding an adapter”- Add a built-in manifest in
built_in_adapters()incrates/wheeljack-core/src/adapters.rs, or save a custom manifest through the adapter registry. - If it speaks an existing protocol, set
streaming.preferredand stop — no Rust changes. - If it needs a new protocol, add a
StructuredProtocolvariant with itsprompt_delivery(),driver_id(), andcapabilities(), then implement the driver interminal_runtime.rs. - Cover it in
crates/wheeljack-core/src/tests/adapters.rsand, for parsing, intests/agent_protocol.rs.