Repository navigation
v0.9.1
Fixed — ACP v2 wire-shape blockers
The 0.9.0 release shipped with three wire-shape violations against the
v2 protocol that a spec-compliant v2 Client (e.g. an early OpenCode v2
preview) would have rejected. All three are addressed in this release:
state_updatediscriminator — the v2 schema requires every
state_updatepayload to have astatefield set to one of
"running" | "idle" | "requires_action". The previous code emitted
"available": false(a non-spec field) and was missingstate. Now
emits{sessionUpdate: "state_update", state: "idle", stopReason?}
per theIdleStateUpdateschema.session/promptv2 response carriesmessageId— the v2
PromptResponseis{required: ["messageId"]}. The previous v1-style
response ({stopReason, status, text}) would have failed schema
validation. acp-bridge now mints a UUID-derived messageId per prompt
and emits the v2 wire shape for v2 Clients. The v1 wire is unchanged.
Legacystatus/text/stopReasonnow live onstate_update
for v2 Clients, exactly as the v2 spec requires.- v2 session lifecycle methods — the v2 baseline includes
session/new | session/list | session/resume | session/close | session/prompt | session/cancel | session/update. acp-bridge
previously only implemented the v1 surface (session/new,
session/end,session/prompt,session/cancel). New:session/close(v2 baseline; shares the implementation with
session/end)session/list(v2 baseline; returns active sessions as
{sessions: [{sessionId, cwd}], nextCursor: null})
session/delete(v2 optional) andsession/resume/
session/loadreturn graceful-32601 not_implemented/
-32001 no_persistencerejections with the stabledata.reason
field.
Fixed — Ollama native protocol bugs
Three pre-existing bugs in the Ollama native code path that had been
documented as fix-plan priorities P0-C. None were wired through the
test suite (tests pointed the harness at 127.0.0.1:1, so Ollama
native was never exercised in CI):
tool.argumentsobject vs string — Ollama native/api/chat
returnsfunction.argumentsas a JSON object; OpenAI-compatible
backends return it as a JSON-encoded string. The previous code called
as_str()on the value and silently fell back to"{}"when it
wasn't a string, which meant every Ollama native tool call ran
with empty arguments. Now handles object / array / string uniformly.options.*sampling fields — Ollama native wants
temperatureandmax_tokens(renamednum_predict) inside an
optionsobject; OpenAI-compatible expects them at the top level.
The previous code only sent top-level fields, so every Ollama
native request silently used the model's defaults for sampling.format_tool_resultOllama field — Ollama native tool messages
use{"role": "tool", "content": …}and ignore (some versions
reject) thetool_call_idfield that acp-bridge included. The new
helper emitstool_call_idonly when the backend is not Ollama
native.
Fixed — sandbox escapes
search_codewalks symlinks — previous code usedpath.is_dir()
/path.is_file(), which follow symlinks. A symlink inside the
sandbox pointing at/etc/passwd(or anywhere outsideworking_dir)
would be read and its contents returned. Now:- Skip entries whose
symlink_metadatareportsfile_type().is_symlink() - Canonicalize the entry and skip it if it does not start with the
canonicalized working dir - Bound recursion depth to
MAX_LIST_DEPTH * 4to prevent
adversarial directory structures
- Skip entries whose
web_fetchredirects bypassLLM_WEB_ALLOWLIST— reqwest's
default redirect policy follows up to 10 hops to any host. A
server on an allowlisted domain could 302 to an internal host and
acp-bridge would happily return the body. Now uses
redirect::Policy::customthat re-validates the next hop's host
againstLLM_WEB_ALLOWLISTon every redirect, with a 5-hop cap.
Changed
docs/scope.mdsynced with current state — the previous version
still said "Not a v2 protocol agent yet" (incorrect as of 0.9.0),
listed the wrong tool set (5 tools instead of the 11 actually
shipped in 0.8.2), and referenced acodex_style.rstest file
that was renamed tominimal_style.rslong ago. Now reflects the
real v1 / v2 dual implementation, the full tool surface, and the
currenttests/clients/layout.CHANGELOG.mdcorrections — 0.9.0 entry over-claimed
Session::protocol_versionis read at emit sites; the field is
stored on each Session but the emit helpers currently use
AppState.protocol_version(one Client per process in practice).
0.8.2 entry under-reported the test count.
Tests
171 tests total (from 168 in 0.9.0). Added in tests/clients/protocol_version.rs:
v2_session_prompt_response_carries_message_id— asserts the v2
PromptResponsecarries the requiredmessageIdand does not
carry the legacy v1stopReason/statusfields.v2_session_close_succeeds_and_v2_session_delete_gracefully_rejects
— confirms the newsession/closerouting and the stable
data.reason: "not_implemented"error onsession/delete.v2_session_list_returns_session_info_with_cwd— asserts
session/listreturns the active sessions in the v2 wire shape.
Hardened existing tests:
v2_emits_state_update_at_end_of_turnnow asserts the
state: "idle"discriminator (the previous version only checked
the discriminator string, which let the bug through).tests/clients/inspector_style.rs::inspector_style_session_*_returns_method_not_found_gracefully
were updated to positive tests — the methods are now implemented
and the previous negative assertions no longer held.