Add Canvas channel (spec + all clients)#298
Conversation
Implement the Canvas surface proposed in #297. A Canvas is a host-driven, interactive UI surface (editor, browser, terminal, document view, …) that a session can open and drive over AHP. This adds the full protocol surface plus matching support in all five client mirrors. Protocol surface (types/): - ClientCapabilities.canvas opt-in capability. - Session-state discovery surface: SessionState.canvases (declared canvas types) and SessionState.openCanvases (live instances), plus SessionActiveClient.canvasProviders published atomically like tools. New supporting types CanvasAvailability, CanvasProviderKind, CanvasProviderSource (named discriminated union), SessionCanvasDeclaration, ClientCanvasDeclaration, OpenCanvasRef, SessionCanvasAction. - Per-instance channel ahp-canvas:/<id> carrying CanvasState and the canvas/updated, canvas/closeRequested, canvas/message actions, with canvasReducer applying a sparse merge for canvas/updated. - Server -> client canvas provider request family (canvasOpen / canvasInvokeAction / canvasClose) mirrored in CommandMap for symmetry with the resource* family, and client -> server canvasReadResource for the in-band content path. - New CanvasProviderError (-32012) with CanvasProviderErrorData. - Protocol version 0.5.1 -> 0.6.0; canvas actions registered at 0.6.0. Generators/tooling: canvas sources wired into find-protocol-sources, generate-action-origin, and the Go/Rust/Swift/Kotlin generators; regenerated client types, JSON schemas, action-origin table, and reference docs. Clients: hand-ported canvasReducer and the session canvas arms to Rust, Go, Swift, Kotlin, and TypeScript. The exhaustive Rust/Swift state mirrors also route the ahp-canvas: channel; the prefix-routing Go/TS mirrors need no change. Fixtures: 8 shared reducer fixtures (232-239) exercising the session registry replacement, open-canvas catalogue replacement, canvas/updated full and partial merges, and the closeRequested / message / unknown-action no-ops. Validated across the TypeScript, Rust, Go, Swift, and Kotlin fixture runners. Docs: new specification/canvas-channel.md and guide/canvases.md, plus subscriptions.md, session-channel.md, and sidebar updates. CHANGELOGs updated for the spec and all five clients. Refs #297 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Reflow the `ahp_types::state` import block to satisfy `cargo fmt`. - Copy `CanvasAvailability` by deref instead of `.clone()` (it is `Copy`), fixing `clippy::clone_on_copy` under `-D warnings`. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds the new Canvas protocol surface (capability + discovery + per-instance channel + provider request family) to the canonical types/ tree, then propagates it through schema/docs generation and the Rust/Go/Swift/Kotlin/TypeScript client mirrors, alongside a protocol version bump to 0.6.0.
Changes:
- Introduces Canvas state, actions, commands, errors, and session discovery fields in
types/(plus reducers + fixtures). - Updates generators and regenerated artifacts (client generated types, JSON schemas, action-origin tables, version registries).
- Adds/updates specification and guide documentation, release metadata, and per-artifact CHANGELOG entries.
Show a summary per file
| File | Description |
|---|---|
| types/version/registry.ts | Bumps protocol version + registers canvas action introduction versions. |
| types/version/message-checks.ts | Updates compile-time command map expectations for canvas methods. |
| types/test-cases/reducers/232-session-canvaseschanged-sets-registry.json | Adds session reducer fixture for canvases registry replacement. |
| types/test-cases/reducers/233-session-opencanvaseschanged-sets-catalogue.json | Adds session reducer fixture for open canvases catalogue replacement. |
| types/test-cases/reducers/234-canvas-updated-merges-all-fields.json | Adds canvas reducer fixture for full sparse-merge update. |
| types/test-cases/reducers/235-canvas-updated-partial-preserves-absent.json | Adds canvas reducer fixture for partial merge (preserve absent). |
| types/test-cases/reducers/236-canvas-updated-partial-complementary.json | Adds canvas reducer fixture for complementary partial merge. |
| types/test-cases/reducers/237-canvas-closerequested-no-op.json | Adds canvas reducer fixture ensuring closeRequested is a no-op. |
| types/test-cases/reducers/238-canvas-message-no-op.json | Adds canvas reducer fixture ensuring message is a no-op. |
| types/test-cases/reducers/239-canvas-unknown-action-type-is-no-op.json | Adds fixture for unknown canvas action type no-op behavior. |
| types/state.ts | Exports canvas channel state from the state barrel. |
| types/reducers.ts | Exports the new canvas reducer from reducer barrel. |
| types/reducers.test.ts | Wires canvas reducer into fixture runner + source scanning. |
| types/messages.test.ts | Adds canvas channel sources to message tests’ scan set. |
| types/index.ts | Re-exports canvas types/actions/commands/reducer from public API. |
| types/common/state.ts | Extends snapshot state union to include CanvasState. |
| types/common/messages.ts | Registers canvas commands in CommandMap + ServerCommandMap. |
| types/common/errors.ts | Adds CanvasProviderError code and data payload type. |
| types/common/commands.ts | Adds ClientCapabilities.canvas capability definition. |
| types/common/actions.ts | Adds canvas/session canvas action types into common action unions. |
| types/commands.ts | Exports canvas channel commands from the commands barrel. |
| types/channels-session/state.ts | Adds canvases/openCanvases fields and canvas discovery types. |
| types/channels-session/reducer.ts | Adds session reducer handling for canvases/openCanvases actions. |
| types/channels-session/actions.ts | Defines session/canvasesChanged and session/openCanvasesChanged. |
| types/channels-canvas/state.ts | Defines CanvasState for per-instance ahp-canvas: channels. |
| types/channels-canvas/reducer.ts | Implements sparse-merge reducer and no-op signal handling. |
| types/channels-canvas/commands.ts | Defines canvas provider family + canvasReadResource content request. |
| types/channels-canvas/actions.ts | Defines canvas channel actions + dispatchability annotations. |
| types/actions.ts | Exports canvas channel actions from the actions barrel. |
| types/action-origin.generated.ts | Adds canvas action unions and dispatchability map entries. |
| scripts/generate-swift.ts | Updates Swift generator for canvas types/unions/commands/actions/errors. |
| scripts/generate-rust.ts | Updates Rust generator for canvas types/unions/commands/actions/errors. |
| scripts/generate-kotlin.ts | Updates Kotlin generator for canvas types/unions/commands/actions/errors. |
| scripts/generate-go.ts | Updates Go generator for canvas types/unions/commands/actions/errors. |
| scripts/generate-action-origin.ts | Extends action-origin generation to include canvas-scoped actions. |
| scripts/find-protocol-sources.ts | Adds channels-canvas to protocol source discovery. |
| schema/state.schema.json | Regenerates JSON schema to include CanvasState + session canvas fields/types. |
| schema/notifications.schema.json | Regenerates notification schema to include canvas-related state shapes. |
| docs/specification/subscriptions.md | Documents ahp-canvas: scheme + canvasReadResource in channel-scoped commands. |
| docs/specification/session-channel.md | Documents session-level canvas discovery fields and actions. |
| docs/specification/canvas-channel.md | Adds full Canvas Channel specification page (state/actions/commands/errors). |
| docs/guide/canvases.md | Adds a high-level “Canvases” guide describing roles and lifecycle. |
| docs/.vitepress/config.mts | Adds Canvases to guide/spec sidebar navigation. |
| clients/typescript/release-metadata.json | Adds 0.6.0 to supported protocol versions. |
| clients/typescript/CHANGELOG.md | Adds TypeScript client changelog entry for canvas support. |
| clients/swift/release-metadata.json | Adds 0.6.0 to supported protocol versions. |
| clients/swift/CHANGELOG.md | Adds Swift client changelog entry for canvas support. |
| clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift | Adds fixture runner support for canvas reducer fixtures. |
| clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift | Mirrors ahp-canvas: channels in the multi-host state mirror. |
| clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift | Mirrors ahp-canvas: channels in the single-host state mirror. |
| clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift | Adds session canvas handling + a canvas reducer implementation. |
| clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Version.generated.swift | Bumps generated Swift protocol version + supported versions. |
| clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Errors.generated.swift | Adds generated Swift error code/data for CanvasProviderError. |
| clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift | Adds generated Swift capability + canvas command structs. |
| clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift | Adds generated Swift canvas/session actions + union wiring. |
| clients/rust/release-metadata.json | Adds 0.6.0 to supported protocol versions. |
| clients/rust/crates/ahp/tests/multi_host_state_mirror.rs | Updates Rust test helpers for new session canvas fields. |
| clients/rust/crates/ahp/src/reducers.rs | Adds session canvas reducers + new apply_action_to_canvas reducer. |
| clients/rust/crates/ahp/src/multi_host_state_mirror.rs | Mirrors ahp-canvas: channels in Rust multi-host state mirror. |
| clients/rust/crates/ahp-types/src/version.rs | Bumps Rust types crate protocol version + supported versions. |
| clients/rust/crates/ahp-types/src/state.rs | Adds Rust canvas types and session fields + snapshot state union update. |
| clients/rust/crates/ahp-types/src/errors.rs | Adds Rust CanvasProviderError code + data struct. |
| clients/rust/crates/ahp-types/src/commands.rs | Adds Rust ClientCapabilities.canvas + canvas command structs. |
| clients/rust/crates/ahp-types/src/actions.rs | Adds Rust canvas/session canvas actions + StateAction wiring. |
| clients/rust/CHANGELOG.md | Adds Rust client changelog entry for canvas support. |
| clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt | Adds Kotlin fixture runner support for canvas reducer fixtures. |
| clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt | Adds Kotlin session canvas handling + canvas reducer implementation. |
| clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Version.generated.kt | Bumps generated Kotlin protocol version + supported versions. |
| clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Errors.generated.kt | Adds generated Kotlin error code/data for CanvasProviderError. |
| clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt | Adds generated Kotlin capability + canvas command structs. |
| clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt | Adds generated Kotlin canvas/session actions + union wiring. |
| clients/kotlin/release-metadata.json | Adds 0.6.0 to supported protocol versions. |
| clients/kotlin/CHANGELOG.md | Adds Kotlin client changelog entry for canvas support. |
| clients/go/release-metadata.json | Adds 0.6.0 to supported protocol versions. |
| clients/go/CHANGELOG.md | Adds Go client changelog entry for canvas support. |
| clients/go/ahptypes/version.generated.go | Bumps generated Go protocol version + supported versions. |
| clients/go/ahptypes/state.generated.go | Adds generated Go canvas types/session fields + snapshot union updates. |
| clients/go/ahptypes/errors.generated.go | Adds generated Go error code/data for CanvasProviderError. |
| clients/go/ahptypes/commands.generated.go | Adds generated Go capability + canvas command structs. |
| clients/go/ahptypes/actions.generated.go | Adds generated Go canvas/session actions + StateAction wiring. |
| clients/go/ahp/reducers.go | Adds Go session canvas handling + ApplyActionToCanvas reducer. |
| clients/go/ahp/reducers_fixture_test.go | Adds Go fixture runner support for canvas reducer fixtures. |
| CHANGELOG.md | Adds spec changelog entry describing the canvas channel addition. |
Review details
- Files reviewed: 78/88 changed files
- Comments generated: 4
- Review effort level: Low
Peer review of the canvas PR surfaced one design gap and three doc issues, each validated against the code before changing: - canvas/updated is now @clientDispatchable. The PR already models client-side canvas providers (ClientCanvasDeclaration, the client CanvasProviderSource variant, CanvasAvailability.Stale), and title / availability are mirrored onto OpenCanvasRef for other subscribers, yet canvas/updated was server-only — leaving a client-side provider no wire path to push its own presentation changes. This mirrors the established terminal/titleChanged pattern (a client may originate it; the host validates, applies, and re-broadcasts). Regenerated: IS_CLIENT_DISPATCHABLE now maps canvas/updated to true, ClientCanvasAction gains it, and ServerCanvasAction becomes `never`. - Fixed the canvas-channel.md sequence diagram to reference the real session/activeClientSet action (there is no session/activeClientsChanged). - Documented that a View to provider canvas/message routes to the single resolved provider, and that multi-renderer provider to View targeting is deferred to the open render-targeting question. - Clarified the deliberate channel split in the command family (session-scoped provider RPCs vs. instance-scoped canvasReadResource). No client reducer/mirror changes are needed: the dispatchability split lives only in the canonical action-origin table; the per-language reducers switch on action type. Verified across TS core (291 tests), Rust (fmt + clippy + test), Go, Swift, Kotlin, the TS client, and the docs build. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ntDispatchable Copilot review follow-ups on the canvas PR: - Don't bump PROTOCOL_VERSION or invent a `## [0.6.0]` CHANGELOG heading in a feature PR. Per repo convention (AGENTS.md + the ea386ee precedent), new actions register at the *current* spec version and their bullets accumulate under `## [Unreleased]`; the version bump and `## [X.Y.Z]` rotation are a dedicated release chore. Revert PROTOCOL_VERSION 0.6.0 -> 0.5.1, drop 0.6.0 from SUPPORTED_PROTOCOL_VERSIONS, register the five canvas actions/session actions at 0.5.1, and fold the canvas bullet back under `## [Unreleased]`. Regenerate the per-client Version.generated.* and release-metadata.json. - Extend `isClientDispatchable` to accept `CanvasAction` and narrow to `ClientCanvasAction`, so consumers can validate the canvas channel's client-dispatchable actions (canvas/updated, canvas/closeRequested, canvas/message) without a cast. Add a canvas assertion to its test. The two mermaid comments (session/activeClientSet naming, canvas/updated direction) were already resolved in the prior review-response commit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…0.5.2 main released AHP 0.5.1 (#301) and bumped PROTOCOL_VERSION to 0.5.2 for ongoing development. Re-register the canvas actions at the current spec version 0.5.2 (was 0.5.1), move the canvas CHANGELOG bullet under the spec [0.5.2] section and each client's [Unreleased], and combine the Rust generator's chat-turn-paging additions with the canvas imports. Regenerated all client mirrors, schemas, docs, and release metadata. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
main added the child-customization model (#285/#288) and an AHP doctrine guide (#304), both landing under the upcoming spec 0.5.2. Resolved the six CHANGELOG conflicts by folding the canvas bullet into each [0.5.2] section alongside the child-customization entries (spec still at PROTOCOL_VERSION 0.5.2, so canvas registration is unchanged). Regenerated all client mirrors, schemas, docs, and release metadata. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Brings in the JSON-schema-generator fix (#305, which resolves the empty-oneOf-branch and dangling-$ref issues tracked in #302), the changeset `reviewed` field + `changeset/filesReviewedChanged` action (#310), `serverInfo`/`clientInfo` on the initialize handshake (#309), and chat-channel docs (#311). Resolved the six CHANGELOGs by keeping main's new bullets and appending the canvas bullet under the current unreleased section, then regenerated all client artifacts and schemas with the fixed generator. The canvas channel still registers its actions at spec 0.5.2; PROTOCOL_VERSION is unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Brings in the MCP server start/stop actions (`session/mcpServerStartRequested` / `session/mcpServerStopRequested`, #319), the optional `version` field on `PluginCustomization` (#317), the `ToolResultTerminalCompleteContent` shell exit tool-result metadata (#314), and the guide/spec docs accuracy pass (#312). Resolved conflicts: - The six CHANGELOGs — kept main's new bullets and appended the canvas bullet under the current unreleased section (matching main's per-file placement). - `clients/rust/crates/ahp/src/reducers.rs` — merged the state-import list so it keeps both `CanvasState` and main's new `McpServer*State` types (cargo fmt re-wrapped). - `docs/specification/subscriptions.md` — kept main's edits (dropped `unsubscribe` from the channel-scoped command row; reworded the resource-watch id as "receiver-assigned") and re-added the canvas rows. Regenerated all client artifacts and schemas; the canvas channel still registers its actions at spec 0.5.2 and PROTOCOL_VERSION is unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Main released AHP 0.5.2 (#329) and bumped PROTOCOL_VERSION to 0.6.0 for ongoing development, and switched the repo to a JSON changelog-fragment workflow (#331): normal PRs now add a fragment under `docs/.changes/` instead of editing the six shared CHANGELOGs. Also brings in the optional `_meta` slot on `SystemNotificationResponsePart` (#308), the resolved input-request turn-transcript record (#324), resource convenience methods, and assorted docs. Adopted the new conventions for the canvas feature: - Dropped the manual canvas bullets from the six CHANGELOGs (took main's released `## [0.5.2]` + empty `## [0.6.0] — Unreleased`) and replaced them with a single `docs/.changes/20260709-canvas-channel.json` fragment. - Moved the five canvas action registrations from the now-released 0.5.2 to 0.6.0 in `types/version/registry.ts`, since the canvas channel was not part of the 0.5.2 release. Resolved `clients/rust/crates/ahp/src/reducers.rs` by unioning the state imports so it keeps both `CanvasState` and main's new `InputRequestResponsePart` (cargo fmt re-wrapped). Regenerated all client artifacts and schemas. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| /** Human-readable description of the canvas. */ | ||
| description: string; |
There was a problem hiding this comment.
Is this needed for a canvas the client publishes? The server isn't going to be consuming this most likely right?
There was a problem hiding this comment.
It is consumed — just not by the transport. The agent reads description (and the per-operation descriptions) to decide when to open a canvas and what to invoke on it: the host folds it into the canvases section of the agent's system prompt and exposes it through the capability-listing tool, exactly the way ToolDefinition.description drives tool selection. The client is the source of truth for what its canvas does, so it publishes the description and the host aggregates it into SessionState.canvases. That's why it lives here rather than being server-consumed.
| /** Owning provider id. */ | ||
| extensionId: string; | ||
| /** Human-readable provider name. */ | ||
| extensionName?: string; |
There was a problem hiding this comment.
What do these mean in the context of AHP? It doesn't know about extensions. We do know about plugins and customizations. Is that the right place for this?
There was a problem hiding this comment.
Agreed — "extension" is borrowed vocabulary AHP doesn't own. Renamed to providerId, documented as an opaque namespace string AHP does not interpret. It's load-bearing rather than removable: canvasId is provider-local (two providers can each declare a diff canvas), so a namespace qualifier is needed to keep ids unique and to route canvasOpen/canvasInvokeOperation/canvasClose to the right provider. providerId lines up with the provider vocabulary already in the surface (canvasProviders, CanvasProviderSource, CanvasProviderError). I also dropped extensionName — displayName already carries the human-readable label.
| /** Server-assigned instance handle, unique within the session. */ | ||
| instanceId: string; | ||
| /** | ||
| * The instance's channel URI (`ahp-canvas:/<id>`). Subscribe to it to load | ||
| * the full {@link CanvasState}. | ||
| */ | ||
| channel: URI; | ||
| /** Provider-local canvas id this instance was opened from. */ | ||
| canvasId: string; |
There was a problem hiding this comment.
Do we need all these ID's? AHP doesn't know what a "provider" is. Channel URI should be enough to uniquely identify an open canvas, as they will already be unique.
There was a problem hiding this comment.
Agreed. Trimmed OpenCanvasRef to { channel, canvasId, title?, availability } — the channel URI is now the sole identity (dropped instanceId, providerId, extensionName). I kept canvasId because the renderer keys the native surface type off it (editor vs browser vs a custom canvas), so a catalogue view can render the right thing without subscribing to every instance; title/availability are just for a lightweight list. Full identity now lives only on the CanvasState behind the channel.
| export const enum CanvasAvailability { | ||
| /** The provider is connected and can service requests for this canvas. */ | ||
| Ready = 'ready', | ||
| /** | ||
| * The provider is temporarily unavailable (for example, a client provider | ||
| * that disconnected). The entry is retained so it can be restored when the | ||
| * provider reconnects; in-flight requests fail until then. | ||
| */ | ||
| Stale = 'stale', | ||
| } |
There was a problem hiding this comment.
Does this need to be on the session state? If it's only client disconnects, I wonder if we should signal that in general for an activeClient vs specifically for the canvas alone
There was a problem hiding this comment.
It's not only client disconnects, which is why it's per-instance. A provider can be server-side (host-internal) — no activeClient entry at all — yet its canvas can still go stale if the host tears the provider down. And one client can provide several canvases with independent lifecycles, so a client-level "connected" bit wouldn't tell a renderer whether the specific instance it's showing is still live. availability is the derived per-instance projection of "is this instance's resolved provider reachable", which is what a renderer needs to grey out a stale surface. It's redundant with activeClient state for the pure disconnect case, but it's the general signal that also covers the server-provider case — so I'd keep it on the canvas ref/state.
| * | ||
| * @category Canvas Types | ||
| */ | ||
| export interface SessionCanvasAction { |
There was a problem hiding this comment.
"Operation" would match the existing wording we use for changesets
There was a problem hiding this comment.
Done — renamed to match changesets: SessionCanvasAction → SessionCanvasOperation, the field actions → operations, and canvasInvokeAction → canvasInvokeOperation (actionName → operationName). I left the two channel state-mutation Actions (canvas/updated, canvas/closeRequested) as Actions, since those are protocol-level Actions in the same sense changesets keep both an operation vocabulary and channel Actions.
| export interface CanvasMessageAction { | ||
| type: ActionType.CanvasMessage; | ||
| /** Opaque, provider-defined message payload. */ | ||
| payload: unknown; | ||
| } |
There was a problem hiding this comment.
If this channel encompasses an existing protocol and doesn't really need its own state or OOB messages, we can always do what we did with mcp:// or otel:// where we say that it's a channel that exists, refer to its own documentation for the protocol definition -- just with the addition of a channel: string for routing data to th eright place.
There was a problem hiding this comment.
Agreed there's no need for its own OOB messages here — but it turns out there's also nothing to encompass. Canvas interaction is just three fixed RPCs (canvasOpen / canvasClose / canvasInvokeOperation) plus availability signals; provider→client never carries opaque data. So instead of a channel: string bridge I removed canvas/message outright. I also dropped the bespoke canvasReadResource path — content now rides the general, symmetrical resourceRead (which the relay already carries), so a canvas renders over a relay with no direct host connection. The channel keeps only the two state-mutation Actions (canvas/updated, canvas/closeRequested).
…message/read paths
Incorporate maintainer feedback on the canvas channel:
- Rename the opaque `extensionId` namespace to `providerId` and drop
`extensionName`; AHP treats it as an uninterpreted routing key that
keeps provider-local `canvasId`s unique, matching the surrounding
`canvasProviders` / `CanvasProviderSource` vocabulary.
- Rename the declared-invokable surface from "action" to "operation" to
match the changeset channel: `SessionCanvasOperation`, `operations`,
`canvasInvokeOperation` (`operationName`). Channel state-mutation
Actions (`canvas/updated`, `canvas/closeRequested`) stay Actions.
- Trim `OpenCanvasRef` to `{ channel, canvasId, title?, availability }`;
the channel URI is the sole identity, with full identity on CanvasState.
- Remove `canvas/message` and the bespoke `canvasReadResource` /
`CanvasResourceContent` content path. Canvas content is resolved over
the general symmetrical `resourceRead` command, so a canvas renders
over a relay with no direct host connection and no canvas-specific
content method.
Regenerate all five client mirrors, schema, and reference docs; update
the spec/guide prose, reducer fixtures, and changelog fragment.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9cd480c0-612c-4854-85cc-20fb91681bae
Implements the Canvas surface proposed in #297. A Canvas is a host-driven, interactive UI surface (editor, browser, terminal, document view, …) that a session can open and drive over AHP. This PR adds the full protocol surface in the canonical
types/tree, wires it through the four per-language generators, and hand-ports the runtime pieces to all five client mirrors.What's included
canvasclient capability advertising which native canvas types the client can render.canvasProviderspublished atomically onSessionActiveClient(same lifecycle astools), plusCanvasAvailability/CanvasProviderSourcein session state.ahp-canvas:/<id>channel per open canvas instance (mirrors the terminal / changeset pattern), carryingcanvas/updated,canvas/closeRequested, and the opaquecanvas/messagebridge.canvasProviderOpen/canvasProviderInvokeAction/canvasProviderCloseinServerCommandMap, mirrored inCommandMap(theresource*precedent).canvasReadResource+ the opaquecanvas/messagepath.CanvasProviderErrorcarrying{ code, message }.PROTOCOL_VERSIONbump. The canvas actions register at the current spec version (0.5.1) inACTION_INTRODUCED_IN, and the CHANGELOG bullets land under## [Unreleased]. Per repo convention (and thesession/inputNeededprecedent), bumpingPROTOCOL_VERSIONand rotating the## [X.Y.Z]heading is a separate release-time chore, not part of a feature PR. Client package versions stay 0.5.0 (bumped at release time).Cross-language surface
types/(errors, capability,channels-canvas/,channels-session/state + actions + reducer, common actions enum/union, messages maps, version registry, index).generate-markdowngets the canvas slug + channel section.Fixtures
Reducer fixtures cover: provider-registry replace, open-catalogue replace,
canvas/updatedsparse merge (present overwrites / absent preserves),canvas/closeRequestedno-op, andcanvas/messageno-op.Validated deviations from the proposal
canvas/updateduses a value-based sparse merge (present field overwrites, absent preserves) rather than the proposal's three-statenull-clears / absent-preserves model. The three-state model isn't portable: the conformance harness collapses JSONnull→undefined, and the generated Rust/Kotlin/Swift/Go nullable fields can't distinguishnullfrom absent after deserialization. Action fields are thereforetitle?: string(no| null), and fresh snapshots on (re)subscribe cover the reset case.channelscopes it), socanvas/updatedcarries noinstanceIdand the reducer has no instance-lookup branch.CanvasProviderSourceis modeled as a named discriminated union (CanvasProviderKindenum +CanvasServerProviderSource/CanvasClientProviderSource) rather than an inline anonymous union, because the per-language generators need named variants and the editorial rules mandate a*Kinddiscriminant.canvas/messageis bidirectional; since the action-origin model is binary it's marked@clientDispatchable(clients may send; the server may also emit). The reducer handles it via the fullCanvasActionunion regardless.