Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74a423567f | ||
|
|
b225fbdd69 | ||
|
|
244e8d2927 |
@@ -438,6 +438,16 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_session_update_tag_the_acp_dependency_does_not_know_fails_to_deserialize() {
|
||||||
|
// `SessionUpdate` has no unknown-variant fallback, so this never reaches `Projection::apply`.
|
||||||
|
let result: Result<SessionNotification, _> = serde_json::from_value(serde_json::json!({
|
||||||
|
"sessionId": "acp-1",
|
||||||
|
"update": {"sessionUpdate": "some_future_update_variant"},
|
||||||
|
}));
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn folds_content_tool_diff_plan_and_notice_rows() {
|
fn folds_content_tool_diff_plan_and_notice_rows() {
|
||||||
let mut projection = Projection::default();
|
let mut projection = Projection::default();
|
||||||
|
|||||||
+23
-8
@@ -113,13 +113,25 @@ crate.
|
|||||||
| `ToolDiff` | The same tool-call machinery, when the tool's content includes a `Diff` block. | Same `tool:<tool_call_id>` identity as `ToolCall`; the *kind* flips between the two as the tool's own content changes, not the row. |
|
| `ToolDiff` | The same tool-call machinery, when the tool's content includes a `Diff` block. | Same `tool:<tool_call_id>` identity as `ToolCall`; the *kind* flips between the two as the tool's own content changes, not the row. |
|
||||||
| `Approval` | A daemon-originated permission request (`RequestPermissionRequest`), serialized without its nonce. | `approval:<uuid>` — minted fresh per request, unrelated to the ACP request's own id. |
|
| `Approval` | A daemon-originated permission request (`RequestPermissionRequest`), serialized without its nonce. | `approval:<uuid>` — minted fresh per request, unrelated to the ACP request's own id. |
|
||||||
| `Plan` | A `plan` update — the agent's current step list. | The fixed id `"plan"`. Each new plan **replaces** the row wholesale; entries are not diffed against the previous plan. |
|
| `Plan` | A `plan` update — the agent's current step list. | The fixed id `"plan"`. Each new plan **replaces** the row wholesale; entries are not diffed against the previous plan. |
|
||||||
| `Notice` | The raw JSON of any `session/update` variant the projection does not special-case. | `notice:<seq>`. |
|
| `Notice` | The raw JSON of any `session/update` variant the ACP dependency already decodes but the projection does not special-case. | `notice:<seq>`. |
|
||||||
|
|
||||||
`Notice` is the projection's forward-compatibility seam: ACP adds
|
`Notice` covers known-but-unspecialized `SessionUpdate` variants: ones the
|
||||||
notification variants over time, and an unrecognised one becomes a generic
|
`agent-client-protocol` dependency's schema already deserializes (it
|
||||||
row instead of being silently dropped or making the daemon reject the whole
|
currently folds `available_commands_update` and `session_info_update`
|
||||||
stream. A client that does not know what to do with a `Notice` can render it
|
there) but the projection's `match` doesn't fold into one of the other
|
||||||
as an opaque, collapsed line — that is a reasonable floor, not a client bug.
|
seven kinds. It is not a wire-level forward-compatibility seam:
|
||||||
|
`SessionUpdate` is an internally-tagged enum with no unknown-variant
|
||||||
|
fallback, so a `session/update` whose `sessionUpdate` tag this build's
|
||||||
|
dependency has never heard of — including a variant gated behind an
|
||||||
|
unstable ACP feature this build doesn't enable, which is compiled out of
|
||||||
|
the enum entirely — fails to deserialize before the projection ever runs,
|
||||||
|
rather than becoming a `Notice`. A genuinely new ACP variant needs the
|
||||||
|
`agent-client-protocol` dependency upgraded (and, typically, the projection's
|
||||||
|
`match` taught about it) before it can appear on this transcript at all — see
|
||||||
|
`projection.rs`'s test asserting that an unrecognised `sessionUpdate` tag is a
|
||||||
|
deserialization error, not a `Notice`. A client that does not know what to do
|
||||||
|
with a `Notice` can still render it as an opaque, collapsed line — that
|
||||||
|
remains a reasonable floor, not a client bug.
|
||||||
|
|
||||||
Rendering is entirely a client concern. The projection's job stops at
|
Rendering is entirely a client concern. The projection's job stops at
|
||||||
folding ACP into these eight shapes; markdown formatting, diff rendering,
|
folding ACP into these eight shapes; markdown formatting, diff rendering,
|
||||||
@@ -139,8 +151,11 @@ choices, not daemon ones.
|
|||||||
- Approval nonce minting, secrecy, and the single-active-approval rule (see
|
- Approval nonce minting, secrecy, and the single-active-approval rule (see
|
||||||
below) — a client never sees an ACP permission request before the daemon
|
below) — a client never sees an ACP permission request before the daemon
|
||||||
has already turned it into an `Approval` row and a nonce.
|
has already turned it into an `Approval` row and a nonce.
|
||||||
- What happens to an ACP update variant nobody has taught the daemon about
|
- What happens to an ACP update variant the dependency decodes but nobody has
|
||||||
yet (falls back to `Notice` rather than erroring).
|
taught the projection to special-case (falls back to `Notice` rather than
|
||||||
|
erroring). A variant the dependency itself can't decode is a deserialization
|
||||||
|
error, not a `Notice` — see [The eight transcript row
|
||||||
|
kinds](#the-eight-transcript-row-kinds).
|
||||||
- Persistence and replay: what is written to the event log, in what order,
|
- Persistence and replay: what is written to the event log, in what order,
|
||||||
and how a restart resumes it.
|
and how a restart resumes it.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user