From 271edac3d1a6f7607e4380e9221531b14f16a99c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:07:58 +0200 Subject: [PATCH 1/2] refactor(session): remove changesets catalogue from SessionSummary Drop the `changesets` field from `SessionSummary` and the matching `session/changesetsChanged` action. The per-changeset `changeset/*` channel and `Changeset` type remain; clients now obtain changeset URIs out of band and subscribe to them directly. Updates the spec types, reducer, version registry, code generators, schemas, generated client code, and the hand-written reducer/runtime code in the Rust, Go, Kotlin, Swift, and TypeScript clients. Refreshes the changesets guide, state-model doc, and channels-migration skill to remove the catalogue concept, and adds CHANGELOG entries to the root spec and every client. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 7 ++- clients/go/CHANGELOG.md | 3 +- clients/go/ahp/reducers.go | 3 -- clients/go/ahptypes/actions.generated.go | 23 ---------- .../go/ahptypes/notifications.generated.go | 9 +--- clients/go/ahptypes/state.generated.go | 6 --- clients/kotlin/CHANGELOG.md | 3 +- .../microsoft/agenthostprotocol/Reducers.kt | 7 --- .../generated/Actions.generated.kt | 14 ------ .../generated/Notifications.generated.kt | 8 ---- .../generated/State.generated.kt | 8 ---- clients/rust/CHANGELOG.md | 3 +- clients/rust/crates/ahp-types/src/actions.rs | 32 +++---------- .../crates/ahp-types/src/notifications.rs | 10 +---- clients/rust/crates/ahp-types/src/state.rs | 7 --- clients/rust/crates/ahp/src/hosts/runtime.rs | 3 -- clients/rust/crates/ahp/src/reducers.rs | 5 --- clients/rust/crates/ahp/tests/hosts.rs | 1 - .../ahp/tests/multi_host_state_mirror.rs | 2 - .../swift/AHPApp/AHPApp/Store/AppStore.swift | 1 - .../Generated/Actions.generated.swift | 19 -------- .../Generated/Notifications.generated.swift | 8 ---- .../Generated/State.generated.swift | 8 ---- .../Sources/AgentHostProtocol/Reducers.swift | 5 --- .../Hosts/HostRuntime.swift | 1 - clients/swift/CHANGELOG.md | 3 +- clients/typescript/CHANGELOG.md | 3 +- .../typescript/src/client/hosts/runtime.ts | 1 - docs/guide/changesets.md | 28 +++++------- docs/guide/state-model.md | 2 +- .../skills/channels-migration/SKILL.md | 14 +++--- schema/actions.schema.json | 30 ------------- schema/commands.schema.json | 27 ----------- schema/errors.schema.json | 7 --- schema/notifications.schema.json | 16 +------ schema/state.schema.json | 7 --- scripts/generate-go.ts | 1 - scripts/generate-kotlin.ts | 1 - scripts/generate-rust.ts | 3 +- scripts/generate-swift.ts | 1 - types/action-origin.generated.ts | 4 -- types/channels-root/notifications.ts | 3 +- types/channels-session/actions.ts | 21 --------- types/channels-session/reducer.ts | 8 ---- types/channels-session/state.ts | 9 ---- types/common/actions.ts | 3 -- ...sion-changesetschanged-sets-catalogue.json | 45 ------------------- ...on-changesetschanged-clears-catalogue.json | 40 ----------------- types/version/registry.ts | 1 - 49 files changed, 43 insertions(+), 431 deletions(-) delete mode 100644 types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json delete mode 100644 types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json diff --git a/CHANGELOG.md b/CHANGELOG.md index b4bb82a8..072a3ad2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,8 +38,11 @@ Spec version: `0.3.0` - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. -- Renamed the `ChangesetSummary` interface to `Changeset` (catalogue entry - on `SessionSummary.changesets`). The on-the-wire shape is unchanged. +- Removed the `changesets` catalogue from `SessionSummary` and the + matching `session/changesetsChanged` action. Clients discover + changeset URIs out of band and subscribe to them directly. +- Renamed the `ChangesetSummary` interface to `Changeset`. The + on-the-wire shape is unchanged. - Renamed the `UserMessage` type to `Message` and surfaced it consistently across turn state (`Turn.message`, `ActiveTurn.message`, `PendingMessage.message`) and the actions that carry it (`session/turnStarted`, diff --git a/clients/go/CHANGELOG.md b/clients/go/CHANGELOG.md index c9fd35ad..0c50ce91 100644 --- a/clients/go/CHANGELOG.md +++ b/clients/go/CHANGELOG.md @@ -25,11 +25,12 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file. ### Changed -- Renamed the `ChangesetSummary` type to `Changeset` (catalogue entry on `SessionSummary.changesets`). The on-the-wire shape is unchanged. +- Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. ### Removed - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. +- Removed the `changesets` catalogue from `SessionSummary` and the matching `session/changesetsChanged` action. Clients discover changeset URIs out of band and subscribe to them directly. ## [0.1.0] — 2026-05-28 diff --git a/clients/go/ahp/reducers.go b/clients/go/ahp/reducers.go index e081790c..dacdbdfb 100644 --- a/clients/go/ahp/reducers.go +++ b/clients/go/ahp/reducers.go @@ -498,9 +498,6 @@ func ApplyActionToSession(state *ahptypes.SessionState, action ahptypes.StateAct case *ahptypes.SessionActivityChangedAction: state.Summary.Activity = a.Activity return ReduceOutcomeApplied - case *ahptypes.SessionChangesetsChangedAction: - state.Summary.Changesets = append([]ahptypes.Changeset(nil), a.Changesets...) - return ReduceOutcomeApplied case *ahptypes.SessionConfigChangedAction: if state.Config == nil { return ReduceOutcomeNoOp diff --git a/clients/go/ahptypes/actions.generated.go b/clients/go/ahptypes/actions.generated.go index 0f13bda5..b0d49312 100644 --- a/clients/go/ahptypes/actions.generated.go +++ b/clients/go/ahptypes/actions.generated.go @@ -58,7 +58,6 @@ const ( ActionTypeSessionIsReadChanged ActionType = "session/isReadChanged" ActionTypeSessionIsArchivedChanged ActionType = "session/isArchivedChanged" ActionTypeSessionActivityChanged ActionType = "session/activityChanged" - ActionTypeSessionChangesetsChanged ActionType = "session/changesetsChanged" ActionTypeSessionConfigChanged ActionType = "session/configChanged" ActionTypeSessionMetaChanged ActionType = "session/metaChanged" ActionTypeChangesetStatusChanged ActionType = "changeset/statusChanged" @@ -441,21 +440,6 @@ type SessionActivityChangedAction struct { Activity *string `json:"activity,omitempty"` } -// The {@link Changeset | catalogue of changesets} the agent host -// advertises for this session changed. Replaces -// `state.summary.changesets` entirely (full-replacement semantics) — set -// to `undefined` to clear the catalogue. -// -// Producers dispatch this whenever entries are added or removed. The -// fan-out happens through this action so observers see catalogue -// mutations in the same {@link ChangesetAction | per-changeset} action -// stream they already follow for file-level updates. -type SessionChangesetsChangedAction struct { - Type ActionType `json:"type"` - // New catalogue, or `undefined` to clear it - Changesets []Changeset `json:"changesets,omitempty"` -} - // Server tools for this session have changed. // // Full-replacement semantics: the `tools` array replaces the previous `serverTools` entirely. @@ -940,7 +924,6 @@ func (*SessionAgentChangedAction) isStateAction() {} func (*SessionIsReadChangedAction) isStateAction() {} func (*SessionIsArchivedChangedAction) isStateAction() {} func (*SessionActivityChangedAction) isStateAction() {} -func (*SessionChangesetsChangedAction) isStateAction() {} func (*SessionServerToolsChangedAction) isStateAction() {} func (*SessionActiveClientChangedAction) isStateAction() {} func (*SessionActiveClientToolsChangedAction) isStateAction() {} @@ -1142,12 +1125,6 @@ func (u *StateAction) UnmarshalJSON(data []byte) error { return err } u.Value = &value - case "session/changesetsChanged": - var value SessionChangesetsChangedAction - if err := json.Unmarshal(data, &value); err != nil { - return err - } - u.Value = &value case "session/serverToolsChanged": var value SessionServerToolsChangedAction if err := json.Unmarshal(data, &value); err != nil { diff --git a/clients/go/ahptypes/notifications.generated.go b/clients/go/ahptypes/notifications.generated.go index e7b96979..3d37cdc7 100644 --- a/clients/go/ahptypes/notifications.generated.go +++ b/clients/go/ahptypes/notifications.generated.go @@ -70,8 +70,7 @@ type SessionRemovedParams struct { // {@link SessionSummary | `SessionSummary`} changes for a session the // server has surfaced via `listSessions()` or `root/sessionAdded`. // Servers MAY coalesce or debounce updates for noisy fields (for example, -// `modifiedAt` bumps while a turn is streaming, or rapidly changing -// `changesets`) at their discretion. +// `modifiedAt` bumps while a turn is streaming) at their discretion. // - Clients that have no cached entry for `session` MAY ignore the // notification; it is not a substitute for `root/sessionAdded`. type SessionSummaryChangedParams struct { @@ -185,12 +184,6 @@ type PartialSessionSummary struct { Agent *AgentSelection `json:"agent,omitempty"` // The working directory URI for this session WorkingDirectory *URI `json:"workingDirectory,omitempty"` - // Catalogue of changesets the server can produce for this session. Each - // entry advertises a subscribable view of file changes (uncommitted, - // session-wide, per-turn, etc.) and the URI template the client expands - // before subscribing. See {@link Changeset} for the full shape and - // {@link /guide/changesets | Changesets} for an overview of the model. - Changesets []Changeset `json:"changesets,omitempty"` // Aggregate summary of file changes associated with this session. Servers // may populate this to give clients a quick at-a-glance view of the // session's footprint (e.g., for list rendering) without requiring the diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 5cde6456..59e371c3 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -609,12 +609,6 @@ type SessionSummary struct { Agent *AgentSelection `json:"agent,omitempty"` // The working directory URI for this session WorkingDirectory *URI `json:"workingDirectory,omitempty"` - // Catalogue of changesets the server can produce for this session. Each - // entry advertises a subscribable view of file changes (uncommitted, - // session-wide, per-turn, etc.) and the URI template the client expands - // before subscribing. See {@link Changeset} for the full shape and - // {@link /guide/changesets | Changesets} for an overview of the model. - Changesets []Changeset `json:"changesets,omitempty"` // Aggregate summary of file changes associated with this session. Servers // may populate this to give clients a quick at-a-glance view of the // session's footprint (e.g., for list rendering) without requiring the diff --git a/clients/kotlin/CHANGELOG.md b/clients/kotlin/CHANGELOG.md index a1bc4578..ce6614f7 100644 --- a/clients/kotlin/CHANGELOG.md +++ b/clients/kotlin/CHANGELOG.md @@ -26,11 +26,12 @@ versions (`*-SNAPSHOT`) are explicitly rejected by the publish pipeline; bump ### Changed -- Renamed the `ChangesetSummary` type to `Changeset` (catalogue entry on `SessionSummary.changesets`). The on-the-wire shape is unchanged. +- Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. ### Removed - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. +- Removed the `changesets` catalogue from `SessionSummary` and the matching `session/changesetsChanged` action. Clients discover changeset URIs out of band and subscribe to them directly. ## [0.2.0] — 2026-05-28 diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt index f7c009b1..8d043afe 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt @@ -60,7 +60,6 @@ import com.microsoft.agenthostprotocol.generated.StateActionSessionActiveClientC import com.microsoft.agenthostprotocol.generated.StateActionSessionActiveClientToolsChanged import com.microsoft.agenthostprotocol.generated.StateActionSessionActivityChanged import com.microsoft.agenthostprotocol.generated.StateActionSessionAgentChanged -import com.microsoft.agenthostprotocol.generated.StateActionSessionChangesetsChanged import com.microsoft.agenthostprotocol.generated.StateActionSessionConfigChanged import com.microsoft.agenthostprotocol.generated.StateActionSessionCreationFailed import com.microsoft.agenthostprotocol.generated.StateActionSessionCustomizationRemoved @@ -923,12 +922,6 @@ public fun sessionReducer(state: SessionState, action: StateAction): SessionStat summary = state.summary.copy(activity = action.value.activity), ) - is StateActionSessionChangesetsChanged -> state.copy( - // Clear the field entirely when no changesets are provided, matching - // TS `{ ...summaryWithoutChangesets }` semantics (omits the key). - summary = state.summary.copy(changesets = action.value.changesets), - ) - is StateActionSessionConfigChanged -> { val a = action.value val config = state.config diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt index 9d19ca9e..3f0bb004 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt @@ -104,8 +104,6 @@ enum class ActionType { SESSION_IS_ARCHIVED_CHANGED, @SerialName("session/activityChanged") SESSION_ACTIVITY_CHANGED, - @SerialName("session/changesetsChanged") - SESSION_CHANGESETS_CHANGED, @SerialName("session/configChanged") SESSION_CONFIG_CHANGED, @SerialName("session/metaChanged") @@ -577,15 +575,6 @@ data class SessionActivityChangedAction( val activity: String? = null ) -@Serializable -data class SessionChangesetsChangedAction( - val type: ActionType, - /** - * New catalogue, or `undefined` to clear it - */ - val changesets: List? = null -) - @Serializable data class SessionServerToolsChangedAction( val type: ActionType, @@ -1039,7 +1028,6 @@ sealed interface StateAction @JvmInline value class StateActionSessionIsReadChanged(val value: SessionIsReadChangedAction) : StateAction @JvmInline value class StateActionSessionIsArchivedChanged(val value: SessionIsArchivedChangedAction) : StateAction @JvmInline value class StateActionSessionActivityChanged(val value: SessionActivityChangedAction) : StateAction -@JvmInline value class StateActionSessionChangesetsChanged(val value: SessionChangesetsChangedAction) : StateAction @JvmInline value class StateActionSessionServerToolsChanged(val value: SessionServerToolsChangedAction) : StateAction @JvmInline value class StateActionSessionActiveClientChanged(val value: SessionActiveClientChangedAction) : StateAction @JvmInline value class StateActionSessionActiveClientToolsChanged(val value: SessionActiveClientToolsChangedAction) : StateAction @@ -1116,7 +1104,6 @@ internal object StateActionSerializer : KSerializer { "session/isReadChanged" -> StateActionSessionIsReadChanged(input.json.decodeFromJsonElement(SessionIsReadChangedAction.serializer(), element)) "session/isArchivedChanged" -> StateActionSessionIsArchivedChanged(input.json.decodeFromJsonElement(SessionIsArchivedChangedAction.serializer(), element)) "session/activityChanged" -> StateActionSessionActivityChanged(input.json.decodeFromJsonElement(SessionActivityChangedAction.serializer(), element)) - "session/changesetsChanged" -> StateActionSessionChangesetsChanged(input.json.decodeFromJsonElement(SessionChangesetsChangedAction.serializer(), element)) "session/serverToolsChanged" -> StateActionSessionServerToolsChanged(input.json.decodeFromJsonElement(SessionServerToolsChangedAction.serializer(), element)) "session/activeClientChanged" -> StateActionSessionActiveClientChanged(input.json.decodeFromJsonElement(SessionActiveClientChangedAction.serializer(), element)) "session/activeClientToolsChanged" -> StateActionSessionActiveClientToolsChanged(input.json.decodeFromJsonElement(SessionActiveClientToolsChangedAction.serializer(), element)) @@ -1186,7 +1173,6 @@ internal object StateActionSerializer : KSerializer { is StateActionSessionIsReadChanged -> output.json.encodeToJsonElement(SessionIsReadChangedAction.serializer(), value.value) is StateActionSessionIsArchivedChanged -> output.json.encodeToJsonElement(SessionIsArchivedChangedAction.serializer(), value.value) is StateActionSessionActivityChanged -> output.json.encodeToJsonElement(SessionActivityChangedAction.serializer(), value.value) - is StateActionSessionChangesetsChanged -> output.json.encodeToJsonElement(SessionChangesetsChangedAction.serializer(), value.value) is StateActionSessionServerToolsChanged -> output.json.encodeToJsonElement(SessionServerToolsChangedAction.serializer(), value.value) is StateActionSessionActiveClientChanged -> output.json.encodeToJsonElement(SessionActiveClientChangedAction.serializer(), value.value) is StateActionSessionActiveClientToolsChanged -> output.json.encodeToJsonElement(SessionActiveClientToolsChangedAction.serializer(), value.value) diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt index 9ba67479..425ef812 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Notifications.generated.kt @@ -192,14 +192,6 @@ data class PartialSessionSummary( * The working directory URI for this session */ val workingDirectory: String? = null, - /** - * Catalogue of changesets the server can produce for this session. Each - * entry advertises a subscribable view of file changes (uncommitted, - * session-wide, per-turn, etc.) and the URI template the client expands - * before subscribing. See {@link Changeset} for the full shape and - * {@link /guide/changesets | Changesets} for an overview of the model. - */ - val changesets: List? = null, /** * Aggregate summary of file changes associated with this session. Servers * may populate this to give clients a quick at-a-glance view of the diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt index ee2b2f84..4dbb0a61 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt @@ -984,14 +984,6 @@ data class SessionSummary( * The working directory URI for this session */ val workingDirectory: String? = null, - /** - * Catalogue of changesets the server can produce for this session. Each - * entry advertises a subscribable view of file changes (uncommitted, - * session-wide, per-turn, etc.) and the URI template the client expands - * before subscribing. See {@link Changeset} for the full shape and - * {@link /guide/changesets | Changesets} for an overview of the model. - */ - val changesets: List? = null, /** * Aggregate summary of file changes associated with this session. Servers * may populate this to give clients a quick at-a-glance view of the diff --git a/clients/rust/CHANGELOG.md b/clients/rust/CHANGELOG.md index 4ced95cc..85ce46a7 100644 --- a/clients/rust/CHANGELOG.md +++ b/clients/rust/CHANGELOG.md @@ -26,11 +26,12 @@ matching `## [X.Y.Z]` heading is missing from this file. ### Changed -- Renamed the `ChangesetSummary` type to `Changeset` (catalogue entry on `SessionSummary.changesets`). The on-the-wire shape is unchanged. +- Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. ### Removed - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. +- Removed the `changesets` catalogue from `SessionSummary` and the matching `session/changesetsChanged` action. Clients discover changeset URIs out of band and subscribe to them directly. ## [0.2.0] — 2026-05-28 diff --git a/clients/rust/crates/ahp-types/src/actions.rs b/clients/rust/crates/ahp-types/src/actions.rs index b0d250a3..bf16ed97 100644 --- a/clients/rust/crates/ahp-types/src/actions.rs +++ b/clients/rust/crates/ahp-types/src/actions.rs @@ -12,12 +12,11 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::state::{ - AgentInfo, AgentSelection, Changeset, ChangesetFile, ChangesetOperation, - ChangesetOperationStatus, ChangesetStatus, ConfirmationOption, Customization, ErrorInfo, - Message, ModelSelection, PendingMessageKind, ResponsePart, SessionActiveClient, - SessionInputAnswer, SessionInputRequest, SessionInputResponseKind, TerminalClaim, TerminalInfo, - ToolCallCancellationReason, ToolCallConfirmationReason, ToolCallResult, ToolDefinition, - ToolResultContent, UsageInfo, + AgentInfo, AgentSelection, ChangesetFile, ChangesetOperation, ChangesetOperationStatus, + ChangesetStatus, ConfirmationOption, Customization, ErrorInfo, Message, ModelSelection, + PendingMessageKind, ResponsePart, SessionActiveClient, SessionInputAnswer, SessionInputRequest, + SessionInputResponseKind, TerminalClaim, TerminalInfo, ToolCallCancellationReason, + ToolCallConfirmationReason, ToolCallResult, ToolDefinition, ToolResultContent, UsageInfo, }; // ─── ActionType ────────────────────────────────────────────────────── @@ -103,8 +102,6 @@ pub enum ActionType { SessionIsArchivedChanged, #[serde(rename = "session/activityChanged")] SessionActivityChanged, - #[serde(rename = "session/changesetsChanged")] - SessionChangesetsChanged, #[serde(rename = "session/configChanged")] SessionConfigChanged, #[serde(rename = "session/metaChanged")] @@ -575,23 +572,6 @@ pub struct SessionActivityChangedAction { pub activity: Option, } -/// The {@link Changeset | catalogue of changesets} the agent host -/// advertises for this session changed. Replaces -/// `state.summary.changesets` entirely (full-replacement semantics) — set -/// to `undefined` to clear the catalogue. -/// -/// Producers dispatch this whenever entries are added or removed. The -/// fan-out happens through this action so observers see catalogue -/// mutations in the same {@link ChangesetAction | per-changeset} action -/// stream they already follow for file-level updates. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct SessionChangesetsChangedAction { - /// New catalogue, or `undefined` to clear it - #[serde(default, skip_serializing_if = "Option::is_none")] - pub changesets: Option>, -} - /// Server tools for this session have changed. /// /// Full-replacement semantics: the `tools` array replaces the previous `serverTools` entirely. @@ -1142,8 +1122,6 @@ pub enum StateAction { SessionIsArchivedChanged(SessionIsArchivedChangedAction), #[serde(rename = "session/activityChanged")] SessionActivityChanged(SessionActivityChangedAction), - #[serde(rename = "session/changesetsChanged")] - SessionChangesetsChanged(SessionChangesetsChangedAction), #[serde(rename = "session/serverToolsChanged")] SessionServerToolsChanged(SessionServerToolsChangedAction), #[serde(rename = "session/activeClientChanged")] diff --git a/clients/rust/crates/ahp-types/src/notifications.rs b/clients/rust/crates/ahp-types/src/notifications.rs index 06064129..f1a55a22 100644 --- a/clients/rust/crates/ahp-types/src/notifications.rs +++ b/clients/rust/crates/ahp-types/src/notifications.rs @@ -79,8 +79,7 @@ pub struct SessionRemovedParams { /// {@link SessionSummary | `SessionSummary`} changes for a session the /// server has surfaced via `listSessions()` or `root/sessionAdded`. /// Servers MAY coalesce or debounce updates for noisy fields (for example, -/// `modifiedAt` bumps while a turn is streaming, or rapidly changing -/// `changesets`) at their discretion. +/// `modifiedAt` bumps while a turn is streaming) at their discretion. /// - Clients that have no cached entry for `session` MAY ignore the /// notification; it is not a substitute for `root/sessionAdded`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -218,13 +217,6 @@ pub struct PartialSessionSummary { /// The working directory URI for this session #[serde(default, skip_serializing_if = "Option::is_none")] pub working_directory: Option, - /// Catalogue of changesets the server can produce for this session. Each - /// entry advertises a subscribable view of file changes (uncommitted, - /// session-wide, per-turn, etc.) and the URI template the client expands - /// before subscribing. See {@link Changeset} for the full shape and - /// {@link /guide/changesets | Changesets} for an overview of the model. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub changesets: Option>, /// Aggregate summary of file changes associated with this session. Servers /// may populate this to give clients a quick at-a-glance view of the /// session's footprint (e.g., for list rendering) without requiring the diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 73447c55..c38ee123 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -782,13 +782,6 @@ pub struct SessionSummary { /// The working directory URI for this session #[serde(default, skip_serializing_if = "Option::is_none")] pub working_directory: Option, - /// Catalogue of changesets the server can produce for this session. Each - /// entry advertises a subscribable view of file changes (uncommitted, - /// session-wide, per-turn, etc.) and the URI template the client expands - /// before subscribing. See {@link Changeset} for the full shape and - /// {@link /guide/changesets | Changesets} for an overview of the model. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub changesets: Option>, /// Aggregate summary of file changes associated with this session. Servers /// may populate this to give clients a quick at-a-glance view of the /// session's footprint (e.g., for list rendering) without requiring the diff --git a/clients/rust/crates/ahp/src/hosts/runtime.rs b/clients/rust/crates/ahp/src/hosts/runtime.rs index a8a2f92d..941bbd85 100644 --- a/clients/rust/crates/ahp/src/hosts/runtime.rs +++ b/clients/rust/crates/ahp/src/hosts/runtime.rs @@ -720,9 +720,6 @@ fn apply_summary_changes( if let Some(v) = &changes.working_directory { existing.working_directory = Some(v.clone()); } - if let Some(v) = &changes.changesets { - existing.changesets = Some(v.clone()); - } } // ─── Random helpers (no external dep on `rand`) ───────────────────────────── diff --git a/clients/rust/crates/ahp/src/reducers.rs b/clients/rust/crates/ahp/src/reducers.rs index 3b680848..611c33dd 100644 --- a/clients/rust/crates/ahp/src/reducers.rs +++ b/clients/rust/crates/ahp/src/reducers.rs @@ -618,10 +618,6 @@ pub fn apply_action_to_session(state: &mut SessionState, action: &StateAction) - state.summary.activity = a.activity.clone(); ReduceOutcome::Applied } - StateAction::SessionChangesetsChanged(a) => { - state.summary.changesets = a.changesets.clone(); - ReduceOutcome::Applied - } StateAction::SessionConfigChanged(a) => { let Some(config) = state.config.as_mut() else { return ReduceOutcome::NoOp; @@ -1219,7 +1215,6 @@ mod tests { model: None, agent: None, working_directory: None, - changesets: None, changes: None, }, lifecycle: SessionLifecycle::Creating, diff --git a/clients/rust/crates/ahp/tests/hosts.rs b/clients/rust/crates/ahp/tests/hosts.rs index 9a6a8afd..cf5e9da7 100644 --- a/clients/rust/crates/ahp/tests/hosts.rs +++ b/clients/rust/crates/ahp/tests/hosts.rs @@ -1032,7 +1032,6 @@ fn make_summary(uri: &str, title: &str, modified_at: i64) -> ahp_types::state::S model: None, agent: None, working_directory: None, - changesets: None, changes: None, } } diff --git a/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs b/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs index 313fba76..04ef476f 100644 --- a/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs +++ b/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs @@ -56,7 +56,6 @@ fn session_state(title: &str, resource: &str) -> SessionState { model: None, agent: None, working_directory: None, - changesets: None, changes: None, }, lifecycle: SessionLifecycle::Ready, @@ -364,7 +363,6 @@ fn non_action_event_is_ignored() { model: None, agent: None, working_directory: None, - changesets: None, changes: None, }, }), diff --git a/clients/swift/AHPApp/AHPApp/Store/AppStore.swift b/clients/swift/AHPApp/AHPApp/Store/AppStore.swift index 0bf3a193..7e01ae47 100644 --- a/clients/swift/AHPApp/AHPApp/Store/AppStore.swift +++ b/clients/swift/AHPApp/AHPApp/Store/AppStore.swift @@ -1283,7 +1283,6 @@ final class AppStore { if let v = changes.project { summary.project = v } if let v = changes.model { summary.model = v } if let v = changes.workingDirectory { summary.workingDirectory = v } - if let v = changes.changesets { summary.changesets = v } sessionSummariesCache[uri] = summary } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift index 30158295..303fa216 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift @@ -45,7 +45,6 @@ public enum ActionType: String, Codable, Sendable { case sessionIsReadChanged = "session/isReadChanged" case sessionIsArchivedChanged = "session/isArchivedChanged" case sessionActivityChanged = "session/activityChanged" - case sessionChangesetsChanged = "session/changesetsChanged" case sessionConfigChanged = "session/configChanged" case sessionMetaChanged = "session/metaChanged" case changesetStatusChanged = "changeset/statusChanged" @@ -701,20 +700,6 @@ public struct SessionActivityChangedAction: Codable, Sendable { } } -public struct SessionChangesetsChangedAction: Codable, Sendable { - public var type: ActionType - /// New catalogue, or `undefined` to clear it - public var changesets: [Changeset]? - - public init( - type: ActionType, - changesets: [Changeset]? = nil - ) { - self.type = type - self.changesets = changesets - } -} - public struct SessionServerToolsChangedAction: Codable, Sendable { public var type: ActionType /// Updated server tools list (full replacement) @@ -1354,7 +1339,6 @@ public enum StateAction: Codable, Sendable { case sessionIsReadChanged(SessionIsReadChangedAction) case sessionIsArchivedChanged(SessionIsArchivedChangedAction) case sessionActivityChanged(SessionActivityChangedAction) - case sessionChangesetsChanged(SessionChangesetsChangedAction) case sessionServerToolsChanged(SessionServerToolsChangedAction) case sessionActiveClientChanged(SessionActiveClientChangedAction) case sessionActiveClientToolsChanged(SessionActiveClientToolsChangedAction) @@ -1449,8 +1433,6 @@ public enum StateAction: Codable, Sendable { self = .sessionIsArchivedChanged(try SessionIsArchivedChangedAction(from: decoder)) case "session/activityChanged": self = .sessionActivityChanged(try SessionActivityChangedAction(from: decoder)) - case "session/changesetsChanged": - self = .sessionChangesetsChanged(try SessionChangesetsChangedAction(from: decoder)) case "session/serverToolsChanged": self = .sessionServerToolsChanged(try SessionServerToolsChangedAction(from: decoder)) case "session/activeClientChanged": @@ -1556,7 +1538,6 @@ public enum StateAction: Codable, Sendable { case .sessionIsReadChanged(let v): try v.encode(to: encoder) case .sessionIsArchivedChanged(let v): try v.encode(to: encoder) case .sessionActivityChanged(let v): try v.encode(to: encoder) - case .sessionChangesetsChanged(let v): try v.encode(to: encoder) case .sessionServerToolsChanged(let v): try v.encode(to: encoder) case .sessionActiveClientChanged(let v): try v.encode(to: encoder) case .sessionActiveClientToolsChanged(let v): try v.encode(to: encoder) diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift index cecc034c..e5f9ff00 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift @@ -164,12 +164,6 @@ public struct PartialSessionSummary: Codable, Sendable { public var agent: AgentSelection? /// The working directory URI for this session public var workingDirectory: String? - /// Catalogue of changesets the server can produce for this session. Each - /// entry advertises a subscribable view of file changes (uncommitted, - /// session-wide, per-turn, etc.) and the URI template the client expands - /// before subscribing. See {@link Changeset} for the full shape and - /// {@link /guide/changesets | Changesets} for an overview of the model. - public var changesets: [Changeset]? /// Aggregate summary of file changes associated with this session. Servers /// may populate this to give clients a quick at-a-glance view of the /// session's footprint (e.g., for list rendering) without requiring the @@ -188,7 +182,6 @@ public struct PartialSessionSummary: Codable, Sendable { model: ModelSelection? = nil, agent: AgentSelection? = nil, workingDirectory: String? = nil, - changesets: [Changeset]? = nil, changes: ChangesSummary? = nil ) { self.resource = resource @@ -202,7 +195,6 @@ public struct PartialSessionSummary: Codable, Sendable { self.model = model self.agent = agent self.workingDirectory = workingDirectory - self.changesets = changesets self.changes = changes } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index de23729c..5d89b6e4 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -795,12 +795,6 @@ public struct SessionSummary: Codable, Sendable { public var agent: AgentSelection? /// The working directory URI for this session public var workingDirectory: String? - /// Catalogue of changesets the server can produce for this session. Each - /// entry advertises a subscribable view of file changes (uncommitted, - /// session-wide, per-turn, etc.) and the URI template the client expands - /// before subscribing. See {@link Changeset} for the full shape and - /// {@link /guide/changesets | Changesets} for an overview of the model. - public var changesets: [Changeset]? /// Aggregate summary of file changes associated with this session. Servers /// may populate this to give clients a quick at-a-glance view of the /// session's footprint (e.g., for list rendering) without requiring the @@ -819,7 +813,6 @@ public struct SessionSummary: Codable, Sendable { model: ModelSelection? = nil, agent: AgentSelection? = nil, workingDirectory: String? = nil, - changesets: [Changeset]? = nil, changes: ChangesSummary? = nil ) { self.resource = resource @@ -833,7 +826,6 @@ public struct SessionSummary: Codable, Sendable { self.model = model self.agent = agent self.workingDirectory = workingDirectory - self.changesets = changesets self.changes = changes } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift index f6524a86..9399b85f 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift @@ -374,11 +374,6 @@ public func sessionReducer(state: SessionState, action: StateAction) -> SessionS next.summary.activity = a.activity return next - case .sessionChangesetsChanged(let a): - var next = state - next.summary.changesets = a.changesets - return next - case .sessionConfigChanged(let a): guard var config = state.config else { return state } config.values = a.replace == true ? a.config : config.values.merging(a.config) { _, new in new } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/Hosts/HostRuntime.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/Hosts/HostRuntime.swift index 42d1d0e4..f4d3a463 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/Hosts/HostRuntime.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/Hosts/HostRuntime.swift @@ -829,7 +829,6 @@ private func applySummaryChanges( if let v = changes.project { existing.project = v } if let v = changes.model { existing.model = v } if let v = changes.workingDirectory { existing.workingDirectory = v } - if let v = changes.changesets { existing.changesets = v } } /// Protocol version offered on `initialize`. Mirrors the Rust SDK's use of diff --git a/clients/swift/CHANGELOG.md b/clients/swift/CHANGELOG.md index 5617f9fb..7884a8be 100644 --- a/clients/swift/CHANGELOG.md +++ b/clients/swift/CHANGELOG.md @@ -28,11 +28,12 @@ the tag matches the version pinned in [`VERSION`](VERSION). ### Changed -- Renamed the `ChangesetSummary` type to `Changeset` (catalogue entry on `SessionSummary.changesets`). The on-the-wire shape is unchanged. +- Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. ### Removed - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. +- Removed the `changesets` catalogue from `SessionSummary` and the matching `session/changesetsChanged` action. Clients discover changeset URIs out of band and subscribe to them directly. ## [0.2.0] — 2026-05-28 diff --git a/clients/typescript/CHANGELOG.md b/clients/typescript/CHANGELOG.md index 3e7104d4..05ffbbf2 100644 --- a/clients/typescript/CHANGELOG.md +++ b/clients/typescript/CHANGELOG.md @@ -31,11 +31,12 @@ hotfix escape hatch. ### Changed -- Renamed the `ChangesetSummary` type to `Changeset` (catalogue entry on `SessionSummary.changesets`). The on-the-wire shape is unchanged. +- Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. ### Removed - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. +- Removed the `changesets` catalogue from `SessionSummary` and the matching `session/changesetsChanged` action. Clients discover changeset URIs out of band and subscribe to them directly. ## [0.2.0] — 2026-05-28 diff --git a/clients/typescript/src/client/hosts/runtime.ts b/clients/typescript/src/client/hosts/runtime.ts index 6c98bb44..f1927e24 100644 --- a/clients/typescript/src/client/hosts/runtime.ts +++ b/clients/typescript/src/client/hosts/runtime.ts @@ -882,6 +882,5 @@ function applySummaryChange( if (changes.project !== undefined) merged.project = changes.project; if (changes.model !== undefined) merged.model = changes.model; if (changes.workingDirectory !== undefined) merged.workingDirectory = changes.workingDirectory; - if (changes.changesets !== undefined) merged.changesets = changes.changesets; cache.set(params.session, merged); } diff --git a/docs/guide/changesets.md b/docs/guide/changesets.md index 2113511b..42d227a8 100644 --- a/docs/guide/changesets.md +++ b/docs/guide/changesets.md @@ -9,19 +9,15 @@ each with its own URI, lifecycle, and update stream. ## Concepts -### Changeset Catalogue +### Changeset Discovery -Each session's `SessionSummary` advertises the set of changesets the -server can produce. The summary entry is intentionally lightweight — -just enough to render a chip or list row without subscribing — and -references a full subscribable `ChangesetState` by URI. +Clients discover a session's changesets out of band — typically through +provider-specific knowledge of the URI scheme used for changeset +resources, or by expanding RFC 6570 URI templates the provider +documents. Once a client has a concrete changeset URI it can subscribe +to it like any other resource. ```typescript -SessionSummary { - // ...existing fields... - changesets?: Changeset[] -} - Changeset { /** Human-readable label, e.g. `"Uncommitted Changes"`. */ label: string @@ -148,17 +144,15 @@ a JSON-RPC error. ## Lifecycle -1. The server publishes the catalogue on `SessionSummary.changesets`. - Updates ride on `root/sessionSummaryChanged`. -2. The client picks summary entries whose template variables it can - satisfy and subscribes to the resulting URIs. -3. The server returns a `ChangesetState` snapshot (`status: 'computing'` +1. The client obtains a concrete changeset URI (e.g. by expanding a + template the provider documents) and subscribes to it. +2. The server returns a `ChangesetState` snapshot (`status: 'computing'` is allowed if scanning is async) and pushes `changeset/*` actions as files become available. -4. The user invokes a `ChangesetOperation`. The client calls +3. The user invokes a `ChangesetOperation`. The client calls `invokeChangesetOperation`. The server applies the operation and emits any resulting changeset updates. -5. When a session ends, all of its changesets implicitly become +4. When a session ends, all of its changesets implicitly become un-subscribable. Existing subscriptions receive `changeset/disposed` and the server unsubscribes them. diff --git a/docs/guide/state-model.md b/docs/guide/state-model.md index 44c59a20..8bb4cd29 100644 --- a/docs/guide/state-model.md +++ b/docs/guide/state-model.md @@ -378,7 +378,7 @@ The session list can be arbitrarily large and is **not** part of the state tree. - Clients fetch the list imperatively via `listSessions()` RPC. - The server sends lightweight **notifications** to keep connected clients' caches in sync without re-fetching: - `root/sessionAdded` and `root/sessionRemoved` signal lifecycle (creation and disposal). - - `root/sessionSummaryChanged` streams partial updates to an existing session's summary (title, status, `modifiedAt`, project, model, working directory, `changesets`) so clients that are displaying a session list can stay in sync without subscribing to every session URI individually. Only fields present in `changes` carry new values; omitted fields are unchanged. The server SHOULD emit this notification whenever any mutable summary field changes, and MAY coalesce or debounce noisy updates (for example, rapid `modifiedAt` bumps while a turn is streaming) at its discretion. + - `root/sessionSummaryChanged` streams partial updates to an existing session's summary (title, status, `modifiedAt`, project, model, working directory) so clients that are displaying a session list can stay in sync without subscribing to every session URI individually. Only fields present in `changes` carry new values; omitted fields are unchanged. The server SHOULD emit this notification whenever any mutable summary field changes, and MAY coalesce or debounce noisy updates (for example, rapid `modifiedAt` bumps while a turn is streaming) at its discretion. Notifications are ephemeral — not processed by reducers, not stored in state, not replayed on reconnect. On reconnect, clients re-fetch the list. diff --git a/plugins/channels-migration-plugin/skills/channels-migration/SKILL.md b/plugins/channels-migration-plugin/skills/channels-migration/SKILL.md index 7e525029..8466e16a 100644 --- a/plugins/channels-migration-plugin/skills/channels-migration/SKILL.md +++ b/plugins/channels-migration-plugin/skills/channels-migration/SKILL.md @@ -74,9 +74,8 @@ Concretely: fields are gone from individual actions. Routing is by envelope. - **Notifications are top-level methods**: the `notification` wrapper and the `ProtocolNotification` union are gone. -- **`SessionDiffsChangedAction` is gone**: replaced by - `SessionChangesetsChangedAction` (catalogue updates on a session) plus a - new family of per-changeset actions (`changeset/statusChanged`, +- **`SessionDiffsChangedAction` is gone**: replaced by a new family of + per-changeset actions (`changeset/statusChanged`, `changeset/fileSet`, `changeset/fileRemoved`, `changeset/operationsChanged`, `changeset/cleared`) and the `invokeChangesetOperation` command. See `docs/guide/changesets.md`. @@ -209,8 +208,6 @@ Action interfaces that lost their channel field — full list: `SessionCustomizationUpdatedAction`, `SessionTruncatedAction`, `SessionIsReadChangedAction`, `SessionIsArchivedChangedAction`, `SessionActivityChangedAction`, - `SessionChangesetsChangedAction` (replaces the removed - `SessionDiffsChangedAction`), `SessionConfigChangedAction`, `SessionMetaChangedAction`. - Tool-call actions also lost `session` because `ToolCallActionBase` no longer carries it. `turnId` and `toolCallId` remain. @@ -432,8 +429,9 @@ After the migration, your code should: `NotificationMethodParams`, or `NotificationMap`. - [ ] Resolve a session's provider via `SessionSummary.provider`, not via the URI scheme. -- [ ] No `SessionDiffsChangedAction` / `summary.diffs` references; consume - `summary.changesets` plus the `changeset/*` action family instead. +- [ ] No `SessionDiffsChangedAction` / `summary.diffs` references; + subscribe to a changeset URI and consume the `changeset/*` action + family instead. - [ ] Every command's params carries `channel: URI`. Channel-scoped commands (`createSession`, `disposeSession`, `createTerminal`, `disposeTerminal`, `fetchTurns`, `completions`, @@ -456,7 +454,7 @@ documents in the `microsoft/agent-host-protocol` repository: - `docs/specification/terminal-channel.md` — Terminal channel data flow and command detection - `docs/specification/lifecycle.md` — Connection handshake and reconnection -- `docs/guide/changesets.md` — Changeset channel model, catalogue, +- `docs/guide/changesets.md` — Changeset channel model, per-changeset state, and `invokeChangesetOperation` - `types/actions.ts`, `types/commands.ts`, `types/messages.ts`, `types/notifications.ts` — Source-of-truth type definitions diff --git a/schema/actions.schema.json b/schema/actions.schema.json index ec8b6508..fd22ad9d 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -807,26 +807,6 @@ "activity" ] }, - "SessionChangesetsChangedAction": { - "type": "object", - "description": "The {@link Changeset | catalogue of changesets} the agent host\nadvertises for this session changed. Replaces\n`state.summary.changesets` entirely (full-replacement semantics) — set\nto `undefined` to clear the catalogue.\n\nProducers dispatch this whenever entries are added or removed. The\nfan-out happens through this action so observers see catalogue\nmutations in the same {@link ChangesetAction | per-changeset} action\nstream they already follow for file-level updates.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionChangesetsChanged" - }, - "changesets": { - "type": "array", - "items": { - "$ref": "#/$defs/Changeset" - }, - "description": "New catalogue, or `undefined` to clear it" - } - }, - "required": [ - "type", - "changesets" - ] - }, "SessionServerToolsChangedAction": { "type": "object", "description": "Server tools for this session have changed.\n\nFull-replacement semantics: the `tools` array replaces the previous `serverTools` entirely.", @@ -2282,13 +2262,6 @@ "$ref": "#/$defs/URI", "description": "The working directory URI for this session" }, - "changesets": { - "type": "array", - "items": { - "$ref": "#/$defs/Changeset" - }, - "description": "Catalogue of changesets the server can produce for this session. Each\nentry advertises a subscribable view of file changes (uncommitted,\nsession-wide, per-turn, etc.) and the URI template the client expands\nbefore subscribing. See {@link Changeset} for the full shape and\n{@link /guide/changesets | Changesets} for an overview of the model." - }, "changes": { "$ref": "#/$defs/ChangesSummary", "description": "Aggregate summary of file changes associated with this session. Servers\nmay populate this to give clients a quick at-a-glance view of the\nsession's footprint (e.g., for list rendering) without requiring the\nclient to subscribe to a changeset." @@ -5452,9 +5425,6 @@ { "$ref": "#/$defs/SessionActivityChangedAction" }, - { - "$ref": "#/$defs/SessionChangesetsChangedAction" - }, { "$ref": "#/$defs/SessionConfigChangedAction" }, diff --git a/schema/commands.schema.json b/schema/commands.schema.json index b9ea5bc4..f80bbfd8 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -1933,13 +1933,6 @@ "$ref": "#/$defs/URI", "description": "The working directory URI for this session" }, - "changesets": { - "type": "array", - "items": { - "$ref": "#/$defs/Changeset" - }, - "description": "Catalogue of changesets the server can produce for this session. Each\nentry advertises a subscribable view of file changes (uncommitted,\nsession-wide, per-turn, etc.) and the URI template the client expands\nbefore subscribing. See {@link Changeset} for the full shape and\n{@link /guide/changesets | Changesets} for an overview of the model." - }, "changes": { "$ref": "#/$defs/ChangesSummary", "description": "Aggregate summary of file changes associated with this session. Servers\nmay populate this to give clients a quick at-a-glance view of the\nsession's footprint (e.g., for list rendering) without requiring the\nclient to subscribe to a changeset." @@ -5524,26 +5517,6 @@ "activity" ] }, - "SessionChangesetsChangedAction": { - "type": "object", - "description": "The {@link Changeset | catalogue of changesets} the agent host\nadvertises for this session changed. Replaces\n`state.summary.changesets` entirely (full-replacement semantics) — set\nto `undefined` to clear the catalogue.\n\nProducers dispatch this whenever entries are added or removed. The\nfan-out happens through this action so observers see catalogue\nmutations in the same {@link ChangesetAction | per-changeset} action\nstream they already follow for file-level updates.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionChangesetsChanged" - }, - "changesets": { - "type": "array", - "items": { - "$ref": "#/$defs/Changeset" - }, - "description": "New catalogue, or `undefined` to clear it" - } - }, - "required": [ - "type", - "changesets" - ] - }, "SessionServerToolsChangedAction": { "type": "object", "description": "Server tools for this session have changed.\n\nFull-replacement semantics: the `tools` array replaces the previous `serverTools` entirely.", diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 6d8f9d84..6c7aab66 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -865,13 +865,6 @@ "$ref": "#/$defs/URI", "description": "The working directory URI for this session" }, - "changesets": { - "type": "array", - "items": { - "$ref": "#/$defs/Changeset" - }, - "description": "Catalogue of changesets the server can produce for this session. Each\nentry advertises a subscribable view of file changes (uncommitted,\nsession-wide, per-turn, etc.) and the URI template the client expands\nbefore subscribing. See {@link Changeset} for the full shape and\n{@link /guide/changesets | Changesets} for an overview of the model." - }, "changes": { "$ref": "#/$defs/ChangesSummary", "description": "Aggregate summary of file changes associated with this session. Servers\nmay populate this to give clients a quick at-a-glance view of the\nsession's footprint (e.g., for list rendering) without requiring the\nclient to subscribe to a changeset." diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 9408126b..8b98e491 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -65,7 +65,7 @@ }, "SessionSummaryChangedParams": { "type": "object", - "description": "Broadcast to all clients subscribed to the root channel when an existing\nsession's summary changes (title, status, `modifiedAt`, model, working\ndirectory, read/done state, or diff statistics).\n\nThis notification lets clients that maintain a cached session list — for\nexample, the result of a previous `listSessions()` call — stay in sync with\nin-flight sessions without having to subscribe to every session URI\nindividually. It is complementary to, not a replacement for,\n`root/sessionAdded` and `root/sessionRemoved`: those signal lifecycle\n(creation/disposal), while this signals summary-level mutations on an\nalready-known session.\n\nSemantics:\n\n- Only fields present in `changes` have new values; omitted fields are\n unchanged on the client's cached summary.\n- Identity fields (`resource`, `provider`, `createdAt`) never change and\n are not carried.\n- Like all protocol notifications, this is ephemeral: it is **not**\n replayed on reconnect. On reconnect, clients should re-fetch the full\n catalog via `listSessions()` as usual.\n- The server SHOULD emit this notification whenever any mutable field on\n {@link SessionSummary | `SessionSummary`} changes for a session the\n server has surfaced via `listSessions()` or `root/sessionAdded`.\n Servers MAY coalesce or debounce updates for noisy fields (for example,\n `modifiedAt` bumps while a turn is streaming, or rapidly changing\n `changesets`) at their discretion.\n- Clients that have no cached entry for `session` MAY ignore the\n notification; it is not a substitute for `root/sessionAdded`.", + "description": "Broadcast to all clients subscribed to the root channel when an existing\nsession's summary changes (title, status, `modifiedAt`, model, working\ndirectory, read/done state, or diff statistics).\n\nThis notification lets clients that maintain a cached session list — for\nexample, the result of a previous `listSessions()` call — stay in sync with\nin-flight sessions without having to subscribe to every session URI\nindividually. It is complementary to, not a replacement for,\n`root/sessionAdded` and `root/sessionRemoved`: those signal lifecycle\n(creation/disposal), while this signals summary-level mutations on an\nalready-known session.\n\nSemantics:\n\n- Only fields present in `changes` have new values; omitted fields are\n unchanged on the client's cached summary.\n- Identity fields (`resource`, `provider`, `createdAt`) never change and\n are not carried.\n- Like all protocol notifications, this is ephemeral: it is **not**\n replayed on reconnect. On reconnect, clients should re-fetch the full\n catalog via `listSessions()` as usual.\n- The server SHOULD emit this notification whenever any mutable field on\n {@link SessionSummary | `SessionSummary`} changes for a session the\n server has surfaced via `listSessions()` or `root/sessionAdded`.\n Servers MAY coalesce or debounce updates for noisy fields (for example,\n `modifiedAt` bumps while a turn is streaming) at their discretion.\n- Clients that have no cached entry for `session` MAY ignore the\n notification; it is not a substitute for `root/sessionAdded`.", "properties": { "channel": { "$ref": "#/$defs/URI", @@ -122,13 +122,6 @@ "$ref": "#/$defs/URI", "description": "The working directory URI for this session" }, - "changesets": { - "type": "array", - "items": { - "$ref": "#/$defs/Changeset" - }, - "description": "Catalogue of changesets the server can produce for this session. Each\nentry advertises a subscribable view of file changes (uncommitted,\nsession-wide, per-turn, etc.) and the URI template the client expands\nbefore subscribing. See {@link Changeset} for the full shape and\n{@link /guide/changesets | Changesets} for an overview of the model." - }, "changes": { "$ref": "#/$defs/ChangesSummary", "description": "Aggregate summary of file changes associated with this session. Servers\nmay populate this to give clients a quick at-a-glance view of the\nsession's footprint (e.g., for list rendering) without requiring the\nclient to subscribe to a changeset." @@ -997,13 +990,6 @@ "$ref": "#/$defs/URI", "description": "The working directory URI for this session" }, - "changesets": { - "type": "array", - "items": { - "$ref": "#/$defs/Changeset" - }, - "description": "Catalogue of changesets the server can produce for this session. Each\nentry advertises a subscribable view of file changes (uncommitted,\nsession-wide, per-turn, etc.) and the URI template the client expands\nbefore subscribing. See {@link Changeset} for the full shape and\n{@link /guide/changesets | Changesets} for an overview of the model." - }, "changes": { "$ref": "#/$defs/ChangesSummary", "description": "Aggregate summary of file changes associated with this session. Servers\nmay populate this to give clients a quick at-a-glance view of the\nsession's footprint (e.g., for list rendering) without requiring the\nclient to subscribe to a changeset." diff --git a/schema/state.schema.json b/schema/state.schema.json index 7da12efb..c0b5c246 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -776,13 +776,6 @@ "$ref": "#/$defs/URI", "description": "The working directory URI for this session" }, - "changesets": { - "type": "array", - "items": { - "$ref": "#/$defs/Changeset" - }, - "description": "Catalogue of changesets the server can produce for this session. Each\nentry advertises a subscribable view of file changes (uncommitted,\nsession-wide, per-turn, etc.) and the URI template the client expands\nbefore subscribing. See {@link Changeset} for the full shape and\n{@link /guide/changesets | Changesets} for an overview of the model." - }, "changes": { "$ref": "#/$defs/ChangesSummary", "description": "Aggregate summary of file changes associated with this session. Servers\nmay populate this to give clients a quick at-a-glance view of the\nsession's footprint (e.g., for list rendering) without requiring the\nclient to subscribe to a changeset." diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 35f8d68b..e9893c56 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -1053,7 +1053,6 @@ const ACTION_VARIANTS: { { type: 'session/isReadChanged', variantName: 'SessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, { type: 'session/isArchivedChanged', variantName: 'SessionIsArchivedChanged', tsInterface: 'SessionIsArchivedChangedAction' }, { type: 'session/activityChanged', variantName: 'SessionActivityChanged', tsInterface: 'SessionActivityChangedAction' }, - { type: 'session/changesetsChanged', variantName: 'SessionChangesetsChanged', tsInterface: 'SessionChangesetsChangedAction' }, { type: 'session/serverToolsChanged', variantName: 'SessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', variantName: 'SessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', variantName: 'SessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 3e8a53eb..cf8aa439 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -1012,7 +1012,6 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'session/isReadChanged', caseName: 'SessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, { type: 'session/isArchivedChanged', caseName: 'SessionIsArchivedChanged', tsInterface: 'SessionIsArchivedChangedAction' }, { type: 'session/activityChanged', caseName: 'SessionActivityChanged', tsInterface: 'SessionActivityChangedAction' }, - { type: 'session/changesetsChanged', caseName: 'SessionChangesetsChanged', tsInterface: 'SessionChangesetsChangedAction' }, { type: 'session/serverToolsChanged', caseName: 'SessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', caseName: 'SessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', caseName: 'SessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 59ba3101..b6fb64a1 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -894,7 +894,6 @@ const ACTION_VARIANTS: { { type: 'session/isReadChanged', variantName: 'SessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, { type: 'session/isArchivedChanged', variantName: 'SessionIsArchivedChanged', tsInterface: 'SessionIsArchivedChangedAction' }, { type: 'session/activityChanged', variantName: 'SessionActivityChanged', tsInterface: 'SessionActivityChangedAction' }, - { type: 'session/changesetsChanged', variantName: 'SessionChangesetsChanged', tsInterface: 'SessionChangesetsChangedAction' }, { type: 'session/serverToolsChanged', variantName: 'SessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', variantName: 'SessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', variantName: 'SessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, @@ -968,7 +967,7 @@ pub struct SessionToolCallConfirmedAction { function generateActionsFile(project: Project): string { const lines: string[] = [GENERATED_HEADER]; - lines.push('use crate::state::{AgentInfo, AgentSelection, ConfirmationOption, Customization, ErrorInfo, ModelSelection, ResponsePart, SessionActiveClient, SessionInputAnswer, SessionInputRequest, SessionInputResponseKind, TerminalClaim, TerminalInfo, ToolCallResult, ToolCallConfirmationReason, ToolCallCancellationReason, ToolDefinition, ToolResultContent, UsageInfo, Message, PendingMessageKind, ChangesetStatus, ChangesetFile, ChangesetOperation, ChangesetOperationStatus, Changeset};'); + lines.push('use crate::state::{AgentInfo, AgentSelection, ConfirmationOption, Customization, ErrorInfo, ModelSelection, ResponsePart, SessionActiveClient, SessionInputAnswer, SessionInputRequest, SessionInputResponseKind, TerminalClaim, TerminalInfo, ToolCallResult, ToolCallConfirmationReason, ToolCallCancellationReason, ToolDefinition, ToolResultContent, UsageInfo, Message, PendingMessageKind, ChangesetStatus, ChangesetFile, ChangesetOperation, ChangesetOperationStatus};'); lines.push(''); // ActionType enum diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index b10219b8..02d2bb28 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -871,7 +871,6 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'session/isReadChanged', caseName: 'sessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, { type: 'session/isArchivedChanged', caseName: 'sessionIsArchivedChanged', tsInterface: 'SessionIsArchivedChangedAction' }, { type: 'session/activityChanged', caseName: 'sessionActivityChanged', tsInterface: 'SessionActivityChangedAction' }, - { type: 'session/changesetsChanged', caseName: 'sessionChangesetsChanged', tsInterface: 'SessionChangesetsChangedAction' }, { type: 'session/serverToolsChanged', caseName: 'sessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', caseName: 'sessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', caseName: 'sessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, diff --git a/types/action-origin.generated.ts b/types/action-origin.generated.ts index 53d66fdc..851b9392 100644 --- a/types/action-origin.generated.ts +++ b/types/action-origin.generated.ts @@ -44,7 +44,6 @@ import type { SessionIsReadChangedAction, SessionIsArchivedChangedAction, SessionActivityChangedAction, - SessionChangesetsChangedAction, SessionConfigChangedAction, SessionMetaChangedAction, ChangesetStatusChangedAction, @@ -130,7 +129,6 @@ export type SessionAction = | SessionIsReadChangedAction | SessionIsArchivedChangedAction | SessionActivityChangedAction - | SessionChangesetsChangedAction | SessionConfigChangedAction | SessionMetaChangedAction ; @@ -179,7 +177,6 @@ export type ServerSessionAction = | SessionCustomizationUpdatedAction | SessionCustomizationRemovedAction | SessionActivityChangedAction - | SessionChangesetsChangedAction | SessionMetaChangedAction ; @@ -305,7 +302,6 @@ export const IS_CLIENT_DISPATCHABLE: { readonly [K in StateAction['type']]: bool [ActionType.SessionIsReadChanged]: true, [ActionType.SessionIsArchivedChanged]: true, [ActionType.SessionActivityChanged]: false, - [ActionType.SessionChangesetsChanged]: false, [ActionType.SessionConfigChanged]: true, [ActionType.SessionMetaChanged]: false, [ActionType.ChangesetStatusChanged]: false, diff --git a/types/channels-root/notifications.ts b/types/channels-root/notifications.ts index b7f15662..32fea359 100644 --- a/types/channels-root/notifications.ts +++ b/types/channels-root/notifications.ts @@ -103,8 +103,7 @@ export interface SessionRemovedParams { * {@link SessionSummary | `SessionSummary`} changes for a session the * server has surfaced via `listSessions()` or `root/sessionAdded`. * Servers MAY coalesce or debounce updates for noisy fields (for example, - * `modifiedAt` bumps while a turn is streaming, or rapidly changing - * `changesets`) at their discretion. + * `modifiedAt` bumps while a turn is streaming) at their discretion. * - Clients that have no cached entry for `session` MAY ignore the * notification; it is not a substitute for `root/sessionAdded`. * diff --git a/types/channels-session/actions.ts b/types/channels-session/actions.ts index 02be1a5b..c376c441 100644 --- a/types/channels-session/actions.ts +++ b/types/channels-session/actions.ts @@ -26,7 +26,6 @@ import { ToolCallCancellationReason, PendingMessageKind, } from './state.js'; -import type { Changeset } from '../channels-changeset/state.js'; // ─── Tool Call Action Base ─────────────────────────────────────────────────── @@ -491,26 +490,6 @@ export interface SessionActivityChangedAction { activity: string | undefined; } -/** - * The {@link Changeset | catalogue of changesets} the agent host - * advertises for this session changed. Replaces - * `state.summary.changesets` entirely (full-replacement semantics) — set - * to `undefined` to clear the catalogue. - * - * Producers dispatch this whenever entries are added or removed. The - * fan-out happens through this action so observers see catalogue - * mutations in the same {@link ChangesetAction | per-changeset} action - * stream they already follow for file-level updates. - * - * @category Session Actions - * @version 1 - */ -export interface SessionChangesetsChangedAction { - type: ActionType.SessionChangesetsChanged; - /** New catalogue, or `undefined` to clear it */ - changesets: Changeset[] | undefined; -} - /** * Server tools for this session have changed. * diff --git a/types/channels-session/reducer.ts b/types/channels-session/reducer.ts index ea162676..50735904 100644 --- a/types/channels-session/reducer.ts +++ b/types/channels-session/reducer.ts @@ -567,14 +567,6 @@ export function sessionReducer(state: SessionState, action: SessionAction, log?: summary: { ...state.summary, activity: action.activity }, }; - case ActionType.SessionChangesetsChanged: { - const { changesets: _omit, ...summaryWithoutChangesets } = state.summary; - const newSummary = action.changesets - ? { ...summaryWithoutChangesets, changesets: action.changesets } - : summaryWithoutChangesets; - return { ...state, summary: newSummary }; - } - case ActionType.SessionConfigChanged: if (!state.config) { return state; diff --git a/types/channels-session/state.ts b/types/channels-session/state.ts index d2bf6a42..94ade168 100644 --- a/types/channels-session/state.ts +++ b/types/channels-session/state.ts @@ -17,7 +17,6 @@ import type { TextSelection, UsageInfo, } from '../common/state.js'; -import type { Changeset } from '../channels-changeset/state.js'; import type { ModelSelection } from '../channels-root/state.js'; // ─── Pending Message Types ─────────────────────────────────────────────────── @@ -208,14 +207,6 @@ export interface SessionSummary { agent?: AgentSelection; /** The working directory URI for this session */ workingDirectory?: URI; - /** - * Catalogue of changesets the server can produce for this session. Each - * entry advertises a subscribable view of file changes (uncommitted, - * session-wide, per-turn, etc.) and the URI template the client expands - * before subscribing. See {@link Changeset} for the full shape and - * {@link /guide/changesets | Changesets} for an overview of the model. - */ - changesets?: Changeset[]; /** * Aggregate summary of file changes associated with this session. Servers * may populate this to give clients a quick at-a-glance view of the diff --git a/types/common/actions.ts b/types/common/actions.ts index 1121f09d..5ad2b7e3 100644 --- a/types/common/actions.ts +++ b/types/common/actions.ts @@ -53,7 +53,6 @@ import type { SessionIsReadChangedAction, SessionIsArchivedChangedAction, SessionActivityChangedAction, - SessionChangesetsChangedAction, SessionConfigChangedAction, SessionMetaChangedAction, } from '../channels-session/actions.js'; @@ -132,7 +131,6 @@ export const enum ActionType { SessionIsReadChanged = 'session/isReadChanged', SessionIsArchivedChanged = 'session/isArchivedChanged', SessionActivityChanged = 'session/activityChanged', - SessionChangesetsChanged = 'session/changesetsChanged', SessionConfigChanged = 'session/configChanged', SessionMetaChanged = 'session/metaChanged', ChangesetStatusChanged = 'changeset/statusChanged', @@ -232,7 +230,6 @@ export type StateAction = | SessionIsReadChangedAction | SessionIsArchivedChangedAction | SessionActivityChangedAction - | SessionChangesetsChangedAction | SessionConfigChangedAction | SessionMetaChangedAction | ChangesetStatusChangedAction diff --git a/types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json b/types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json deleted file mode 100644 index 9f6cb3fc..00000000 --- a/types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "description": "session/changesetsChanged replaces summary.changesets with the new catalogue", - "reducer": "session", - "initial": { - "summary": { - "resource": "copilot:/test-session", - "provider": "copilot", - "title": "Test Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "turns": [] - }, - "actions": [ - { - "type": "session/changesetsChanged", - "changesets": [ - { - "label": "Session Changes", - "uriTemplate": "copilot:/test-session/changeset/session" - } - ] - } - ], - "expected": { - "summary": { - "resource": "copilot:/test-session", - "provider": "copilot", - "title": "Test Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000, - "changesets": [ - { - "label": "Session Changes", - "uriTemplate": "copilot:/test-session/changeset/session" - } - ] - }, - "lifecycle": "ready", - "turns": [] - } -} diff --git a/types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json b/types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json deleted file mode 100644 index 37d01991..00000000 --- a/types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "description": "session/changesetsChanged with undefined clears summary.changesets", - "reducer": "session", - "initial": { - "summary": { - "resource": "copilot:/test-session", - "provider": "copilot", - "title": "Test Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000, - "changesets": [ - { - "label": "Session Changes", - "uriTemplate": "copilot:/test-session/changeset/session" - } - ] - }, - "lifecycle": "ready", - "turns": [] - }, - "actions": [ - { - "type": "session/changesetsChanged", - "changesets": null - } - ], - "expected": { - "summary": { - "resource": "copilot:/test-session", - "provider": "copilot", - "title": "Test Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "turns": [] - } -} diff --git a/types/version/registry.ts b/types/version/registry.ts index b27704cf..976c83e1 100644 --- a/types/version/registry.ts +++ b/types/version/registry.ts @@ -114,7 +114,6 @@ export const ACTION_INTRODUCED_IN: { readonly [K in StateAction['type']]: string [ActionType.SessionIsReadChanged]: '0.1.0', [ActionType.SessionIsArchivedChanged]: '0.1.0', [ActionType.SessionActivityChanged]: '0.1.0', - [ActionType.SessionChangesetsChanged]: '0.2.0', [ActionType.SessionConfigChanged]: '0.1.0', [ActionType.SessionMetaChanged]: '0.1.0', [ActionType.ChangesetStatusChanged]: '0.2.0', From 6ac50a8a2dc479b167af92465555dc2378b9881f Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:50:06 +0200 Subject: [PATCH 2/2] feat(session): add changesets catalogue to SessionState Restore the changeset catalogue moved out of `SessionSummary` in the previous commit, placing it on `SessionState` instead. The `session/changesetsChanged` action now mutates `state.changesets` directly so the catalogue lives alongside the rest of the per-session state rather than on the lightweight summary used by `root/sessionSummaryChanged`. Re-adds the action across the spec, reducer, version registry, code generators, schemas, regenerated client code, hand-written reducer cases in Rust/Go/Kotlin/Swift, and the reducer test fixtures (now asserting against `state.changesets`). Refreshes the changesets guide and channels-migration skill, and updates CHANGELOGs to describe the move rather than a removal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 6 +-- clients/go/CHANGELOG.md | 2 +- clients/go/ahp/reducers.go | 7 +++ clients/go/ahptypes/actions.generated.go | 24 ++++++++++ clients/go/ahptypes/state.generated.go | 6 +++ clients/kotlin/CHANGELOG.md | 2 +- .../microsoft/agenthostprotocol/Reducers.kt | 5 +++ .../generated/Actions.generated.kt | 14 ++++++ .../generated/State.generated.kt | 8 ++++ clients/rust/CHANGELOG.md | 2 +- clients/rust/crates/ahp-types/src/actions.rs | 33 +++++++++++--- clients/rust/crates/ahp-types/src/state.rs | 7 +++ clients/rust/crates/ahp/src/reducers.rs | 5 +++ .../ahp/tests/multi_host_state_mirror.rs | 1 + .../Generated/Actions.generated.swift | 19 ++++++++ .../Generated/State.generated.swift | 9 ++++ .../Sources/AgentHostProtocol/Reducers.swift | 5 +++ clients/swift/CHANGELOG.md | 2 +- clients/typescript/CHANGELOG.md | 2 +- docs/guide/changesets.md | 28 +++++++----- .../skills/channels-migration/SKILL.md | 14 +++--- schema/actions.schema.json | 30 +++++++++++++ schema/commands.schema.json | 27 +++++++++++ schema/errors.schema.json | 7 +++ schema/notifications.schema.json | 7 +++ schema/state.schema.json | 7 +++ scripts/generate-go.ts | 1 + scripts/generate-kotlin.ts | 1 + scripts/generate-rust.ts | 3 +- scripts/generate-swift.ts | 1 + types/action-origin.generated.ts | 4 ++ types/channels-session/actions.ts | 22 +++++++++ types/channels-session/reducer.ts | 7 +++ types/channels-session/state.ts | 9 ++++ types/common/actions.ts | 3 ++ ...sion-changesetschanged-sets-catalogue.json | 45 +++++++++++++++++++ ...on-changesetschanged-clears-catalogue.json | 40 +++++++++++++++++ types/version/registry.ts | 1 + 38 files changed, 385 insertions(+), 31 deletions(-) create mode 100644 types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json create mode 100644 types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 072a3ad2..26ad8832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,9 +38,9 @@ Spec version: `0.3.0` - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. -- Removed the `changesets` catalogue from `SessionSummary` and the - matching `session/changesetsChanged` action. Clients discover - changeset URIs out of band and subscribe to them directly. +- Moved the `changesets` catalogue from `SessionSummary` to + `SessionState`. The `session/changesetsChanged` action now updates + `state.changesets` directly instead of `state.summary.changesets`. - Renamed the `ChangesetSummary` interface to `Changeset`. The on-the-wire shape is unchanged. - Renamed the `UserMessage` type to `Message` and surfaced it consistently diff --git a/clients/go/CHANGELOG.md b/clients/go/CHANGELOG.md index 0c50ce91..23fd26d1 100644 --- a/clients/go/CHANGELOG.md +++ b/clients/go/CHANGELOG.md @@ -26,11 +26,11 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file. ### Changed - Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. +- Moved the `changesets` catalogue from `SessionSummary` to `SessionState`. The `session/changesetsChanged` action now updates `state.changesets` directly instead of `state.summary.changesets`. ### Removed - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. -- Removed the `changesets` catalogue from `SessionSummary` and the matching `session/changesetsChanged` action. Clients discover changeset URIs out of band and subscribe to them directly. ## [0.1.0] — 2026-05-28 diff --git a/clients/go/ahp/reducers.go b/clients/go/ahp/reducers.go index dacdbdfb..6f6c4eb4 100644 --- a/clients/go/ahp/reducers.go +++ b/clients/go/ahp/reducers.go @@ -498,6 +498,13 @@ func ApplyActionToSession(state *ahptypes.SessionState, action ahptypes.StateAct case *ahptypes.SessionActivityChangedAction: state.Summary.Activity = a.Activity return ReduceOutcomeApplied + case *ahptypes.SessionChangesetsChangedAction: + if a.Changesets == nil { + state.Changesets = nil + } else { + state.Changesets = append([]ahptypes.Changeset(nil), a.Changesets...) + } + return ReduceOutcomeApplied case *ahptypes.SessionConfigChangedAction: if state.Config == nil { return ReduceOutcomeNoOp diff --git a/clients/go/ahptypes/actions.generated.go b/clients/go/ahptypes/actions.generated.go index b0d49312..452aec88 100644 --- a/clients/go/ahptypes/actions.generated.go +++ b/clients/go/ahptypes/actions.generated.go @@ -58,6 +58,7 @@ const ( ActionTypeSessionIsReadChanged ActionType = "session/isReadChanged" ActionTypeSessionIsArchivedChanged ActionType = "session/isArchivedChanged" ActionTypeSessionActivityChanged ActionType = "session/activityChanged" + ActionTypeSessionChangesetsChanged ActionType = "session/changesetsChanged" ActionTypeSessionConfigChanged ActionType = "session/configChanged" ActionTypeSessionMetaChanged ActionType = "session/metaChanged" ActionTypeChangesetStatusChanged ActionType = "changeset/statusChanged" @@ -440,6 +441,22 @@ type SessionActivityChangedAction struct { Activity *string `json:"activity,omitempty"` } +// The {@link Changeset | catalogue of changesets} the agent host +// advertises for this session changed. Replaces +// {@link SessionState.changesets | `state.changesets`} entirely +// (full-replacement semantics) — set to `undefined` to clear the +// catalogue. +// +// Producers dispatch this whenever entries are added or removed. The +// fan-out happens through this action so observers see catalogue +// mutations in the same {@link ChangesetAction | per-changeset} action +// stream they already follow for file-level updates. +type SessionChangesetsChangedAction struct { + Type ActionType `json:"type"` + // New catalogue, or `undefined` to clear it + Changesets []Changeset `json:"changesets,omitempty"` +} + // Server tools for this session have changed. // // Full-replacement semantics: the `tools` array replaces the previous `serverTools` entirely. @@ -924,6 +941,7 @@ func (*SessionAgentChangedAction) isStateAction() {} func (*SessionIsReadChangedAction) isStateAction() {} func (*SessionIsArchivedChangedAction) isStateAction() {} func (*SessionActivityChangedAction) isStateAction() {} +func (*SessionChangesetsChangedAction) isStateAction() {} func (*SessionServerToolsChangedAction) isStateAction() {} func (*SessionActiveClientChangedAction) isStateAction() {} func (*SessionActiveClientToolsChangedAction) isStateAction() {} @@ -1125,6 +1143,12 @@ func (u *StateAction) UnmarshalJSON(data []byte) error { return err } u.Value = &value + case "session/changesetsChanged": + var value SessionChangesetsChangedAction + if err := json.Unmarshal(data, &value); err != nil { + return err + } + u.Value = &value case "session/serverToolsChanged": var value SessionServerToolsChangedAction if err := json.Unmarshal(data, &value); err != nil { diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 59e371c3..9e8a8354 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -555,6 +555,12 @@ type SessionState struct { // and the host propagates them into this list (typically with the // container's `clientId` set and `children` populated). Customizations []Customization `json:"customizations,omitempty"` + // Catalogue of changesets the server can produce for this session. Each + // entry advertises a subscribable view of file changes (uncommitted, + // session-wide, per-turn, etc.) and the URI template the client expands + // before subscribing. See {@link Changeset} for the full shape and + // {@link /guide/changesets | Changesets} for an overview of the model. + Changesets []Changeset `json:"changesets,omitempty"` // Additional provider-specific metadata for this session. // // Clients MAY look for well-known keys here to provide enhanced UI. diff --git a/clients/kotlin/CHANGELOG.md b/clients/kotlin/CHANGELOG.md index ce6614f7..365f76eb 100644 --- a/clients/kotlin/CHANGELOG.md +++ b/clients/kotlin/CHANGELOG.md @@ -27,11 +27,11 @@ versions (`*-SNAPSHOT`) are explicitly rejected by the publish pipeline; bump ### Changed - Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. +- Moved the `changesets` catalogue from `SessionSummary` to `SessionState`. The `session/changesetsChanged` action now updates `state.changesets` directly instead of `state.summary.changesets`. ### Removed - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. -- Removed the `changesets` catalogue from `SessionSummary` and the matching `session/changesetsChanged` action. Clients discover changeset URIs out of band and subscribe to them directly. ## [0.2.0] — 2026-05-28 diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt index 8d043afe..5c58e74d 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt @@ -60,6 +60,7 @@ import com.microsoft.agenthostprotocol.generated.StateActionSessionActiveClientC import com.microsoft.agenthostprotocol.generated.StateActionSessionActiveClientToolsChanged import com.microsoft.agenthostprotocol.generated.StateActionSessionActivityChanged import com.microsoft.agenthostprotocol.generated.StateActionSessionAgentChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionChangesetsChanged import com.microsoft.agenthostprotocol.generated.StateActionSessionConfigChanged import com.microsoft.agenthostprotocol.generated.StateActionSessionCreationFailed import com.microsoft.agenthostprotocol.generated.StateActionSessionCustomizationRemoved @@ -922,6 +923,10 @@ public fun sessionReducer(state: SessionState, action: StateAction): SessionStat summary = state.summary.copy(activity = action.value.activity), ) + is StateActionSessionChangesetsChanged -> state.copy( + changesets = action.value.changesets, + ) + is StateActionSessionConfigChanged -> { val a = action.value val config = state.config diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt index 3f0bb004..9d19ca9e 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Actions.generated.kt @@ -104,6 +104,8 @@ enum class ActionType { SESSION_IS_ARCHIVED_CHANGED, @SerialName("session/activityChanged") SESSION_ACTIVITY_CHANGED, + @SerialName("session/changesetsChanged") + SESSION_CHANGESETS_CHANGED, @SerialName("session/configChanged") SESSION_CONFIG_CHANGED, @SerialName("session/metaChanged") @@ -575,6 +577,15 @@ data class SessionActivityChangedAction( val activity: String? = null ) +@Serializable +data class SessionChangesetsChangedAction( + val type: ActionType, + /** + * New catalogue, or `undefined` to clear it + */ + val changesets: List? = null +) + @Serializable data class SessionServerToolsChangedAction( val type: ActionType, @@ -1028,6 +1039,7 @@ sealed interface StateAction @JvmInline value class StateActionSessionIsReadChanged(val value: SessionIsReadChangedAction) : StateAction @JvmInline value class StateActionSessionIsArchivedChanged(val value: SessionIsArchivedChangedAction) : StateAction @JvmInline value class StateActionSessionActivityChanged(val value: SessionActivityChangedAction) : StateAction +@JvmInline value class StateActionSessionChangesetsChanged(val value: SessionChangesetsChangedAction) : StateAction @JvmInline value class StateActionSessionServerToolsChanged(val value: SessionServerToolsChangedAction) : StateAction @JvmInline value class StateActionSessionActiveClientChanged(val value: SessionActiveClientChangedAction) : StateAction @JvmInline value class StateActionSessionActiveClientToolsChanged(val value: SessionActiveClientToolsChangedAction) : StateAction @@ -1104,6 +1116,7 @@ internal object StateActionSerializer : KSerializer { "session/isReadChanged" -> StateActionSessionIsReadChanged(input.json.decodeFromJsonElement(SessionIsReadChangedAction.serializer(), element)) "session/isArchivedChanged" -> StateActionSessionIsArchivedChanged(input.json.decodeFromJsonElement(SessionIsArchivedChangedAction.serializer(), element)) "session/activityChanged" -> StateActionSessionActivityChanged(input.json.decodeFromJsonElement(SessionActivityChangedAction.serializer(), element)) + "session/changesetsChanged" -> StateActionSessionChangesetsChanged(input.json.decodeFromJsonElement(SessionChangesetsChangedAction.serializer(), element)) "session/serverToolsChanged" -> StateActionSessionServerToolsChanged(input.json.decodeFromJsonElement(SessionServerToolsChangedAction.serializer(), element)) "session/activeClientChanged" -> StateActionSessionActiveClientChanged(input.json.decodeFromJsonElement(SessionActiveClientChangedAction.serializer(), element)) "session/activeClientToolsChanged" -> StateActionSessionActiveClientToolsChanged(input.json.decodeFromJsonElement(SessionActiveClientToolsChangedAction.serializer(), element)) @@ -1173,6 +1186,7 @@ internal object StateActionSerializer : KSerializer { is StateActionSessionIsReadChanged -> output.json.encodeToJsonElement(SessionIsReadChangedAction.serializer(), value.value) is StateActionSessionIsArchivedChanged -> output.json.encodeToJsonElement(SessionIsArchivedChangedAction.serializer(), value.value) is StateActionSessionActivityChanged -> output.json.encodeToJsonElement(SessionActivityChangedAction.serializer(), value.value) + is StateActionSessionChangesetsChanged -> output.json.encodeToJsonElement(SessionChangesetsChangedAction.serializer(), value.value) is StateActionSessionServerToolsChanged -> output.json.encodeToJsonElement(SessionServerToolsChangedAction.serializer(), value.value) is StateActionSessionActiveClientChanged -> output.json.encodeToJsonElement(SessionActiveClientChangedAction.serializer(), value.value) is StateActionSessionActiveClientToolsChanged -> output.json.encodeToJsonElement(SessionActiveClientToolsChangedAction.serializer(), value.value) diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt index 4dbb0a61..0ebd79d8 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt @@ -899,6 +899,14 @@ data class SessionState( * container's `clientId` set and `children` populated). */ val customizations: List? = null, + /** + * Catalogue of changesets the server can produce for this session. Each + * entry advertises a subscribable view of file changes (uncommitted, + * session-wide, per-turn, etc.) and the URI template the client expands + * before subscribing. See {@link Changeset} for the full shape and + * {@link /guide/changesets | Changesets} for an overview of the model. + */ + val changesets: List? = null, /** * Additional provider-specific metadata for this session. * diff --git a/clients/rust/CHANGELOG.md b/clients/rust/CHANGELOG.md index 85ce46a7..399a16ec 100644 --- a/clients/rust/CHANGELOG.md +++ b/clients/rust/CHANGELOG.md @@ -27,11 +27,11 @@ matching `## [X.Y.Z]` heading is missing from this file. ### Changed - Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. +- Moved the `changesets` catalogue from `SessionSummary` to `SessionState`. The `session/changesetsChanged` action now updates `state.changesets` directly instead of `state.summary.changesets`. ### Removed - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. -- Removed the `changesets` catalogue from `SessionSummary` and the matching `session/changesetsChanged` action. Clients discover changeset URIs out of band and subscribe to them directly. ## [0.2.0] — 2026-05-28 diff --git a/clients/rust/crates/ahp-types/src/actions.rs b/clients/rust/crates/ahp-types/src/actions.rs index bf16ed97..49fdd8e9 100644 --- a/clients/rust/crates/ahp-types/src/actions.rs +++ b/clients/rust/crates/ahp-types/src/actions.rs @@ -12,11 +12,12 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::state::{ - AgentInfo, AgentSelection, ChangesetFile, ChangesetOperation, ChangesetOperationStatus, - ChangesetStatus, ConfirmationOption, Customization, ErrorInfo, Message, ModelSelection, - PendingMessageKind, ResponsePart, SessionActiveClient, SessionInputAnswer, SessionInputRequest, - SessionInputResponseKind, TerminalClaim, TerminalInfo, ToolCallCancellationReason, - ToolCallConfirmationReason, ToolCallResult, ToolDefinition, ToolResultContent, UsageInfo, + AgentInfo, AgentSelection, Changeset, ChangesetFile, ChangesetOperation, + ChangesetOperationStatus, ChangesetStatus, ConfirmationOption, Customization, ErrorInfo, + Message, ModelSelection, PendingMessageKind, ResponsePart, SessionActiveClient, + SessionInputAnswer, SessionInputRequest, SessionInputResponseKind, TerminalClaim, TerminalInfo, + ToolCallCancellationReason, ToolCallConfirmationReason, ToolCallResult, ToolDefinition, + ToolResultContent, UsageInfo, }; // ─── ActionType ────────────────────────────────────────────────────── @@ -102,6 +103,8 @@ pub enum ActionType { SessionIsArchivedChanged, #[serde(rename = "session/activityChanged")] SessionActivityChanged, + #[serde(rename = "session/changesetsChanged")] + SessionChangesetsChanged, #[serde(rename = "session/configChanged")] SessionConfigChanged, #[serde(rename = "session/metaChanged")] @@ -572,6 +575,24 @@ pub struct SessionActivityChangedAction { pub activity: Option, } +/// The {@link Changeset | catalogue of changesets} the agent host +/// advertises for this session changed. Replaces +/// {@link SessionState.changesets | `state.changesets`} entirely +/// (full-replacement semantics) — set to `undefined` to clear the +/// catalogue. +/// +/// Producers dispatch this whenever entries are added or removed. The +/// fan-out happens through this action so observers see catalogue +/// mutations in the same {@link ChangesetAction | per-changeset} action +/// stream they already follow for file-level updates. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct SessionChangesetsChangedAction { + /// New catalogue, or `undefined` to clear it + #[serde(default, skip_serializing_if = "Option::is_none")] + pub changesets: Option>, +} + /// Server tools for this session have changed. /// /// Full-replacement semantics: the `tools` array replaces the previous `serverTools` entirely. @@ -1122,6 +1143,8 @@ pub enum StateAction { SessionIsArchivedChanged(SessionIsArchivedChangedAction), #[serde(rename = "session/activityChanged")] SessionActivityChanged(SessionActivityChangedAction), + #[serde(rename = "session/changesetsChanged")] + SessionChangesetsChanged(SessionChangesetsChangedAction), #[serde(rename = "session/serverToolsChanged")] SessionServerToolsChanged(SessionServerToolsChangedAction), #[serde(rename = "session/activeClientChanged")] diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index c38ee123..2770b915 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -716,6 +716,13 @@ pub struct SessionState { /// container's `clientId` set and `children` populated). #[serde(default, skip_serializing_if = "Option::is_none")] pub customizations: Option>, + /// Catalogue of changesets the server can produce for this session. Each + /// entry advertises a subscribable view of file changes (uncommitted, + /// session-wide, per-turn, etc.) and the URI template the client expands + /// before subscribing. See {@link Changeset} for the full shape and + /// {@link /guide/changesets | Changesets} for an overview of the model. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub changesets: Option>, /// Additional provider-specific metadata for this session. /// /// Clients MAY look for well-known keys here to provide enhanced UI. diff --git a/clients/rust/crates/ahp/src/reducers.rs b/clients/rust/crates/ahp/src/reducers.rs index 611c33dd..5599bb83 100644 --- a/clients/rust/crates/ahp/src/reducers.rs +++ b/clients/rust/crates/ahp/src/reducers.rs @@ -618,6 +618,10 @@ pub fn apply_action_to_session(state: &mut SessionState, action: &StateAction) - state.summary.activity = a.activity.clone(); ReduceOutcome::Applied } + StateAction::SessionChangesetsChanged(a) => { + state.changesets = a.changesets.clone(); + ReduceOutcome::Applied + } StateAction::SessionConfigChanged(a) => { let Some(config) = state.config.as_mut() else { return ReduceOutcome::NoOp; @@ -1228,6 +1232,7 @@ mod tests { input_requests: None, config: None, customizations: None, + changesets: None, meta: None, } } diff --git a/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs b/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs index 04ef476f..5f830bd7 100644 --- a/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs +++ b/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs @@ -69,6 +69,7 @@ fn session_state(title: &str, resource: &str) -> SessionState { input_requests: None, config: None, customizations: None, + changesets: None, meta: None, } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift index 303fa216..30158295 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift @@ -45,6 +45,7 @@ public enum ActionType: String, Codable, Sendable { case sessionIsReadChanged = "session/isReadChanged" case sessionIsArchivedChanged = "session/isArchivedChanged" case sessionActivityChanged = "session/activityChanged" + case sessionChangesetsChanged = "session/changesetsChanged" case sessionConfigChanged = "session/configChanged" case sessionMetaChanged = "session/metaChanged" case changesetStatusChanged = "changeset/statusChanged" @@ -700,6 +701,20 @@ public struct SessionActivityChangedAction: Codable, Sendable { } } +public struct SessionChangesetsChangedAction: Codable, Sendable { + public var type: ActionType + /// New catalogue, or `undefined` to clear it + public var changesets: [Changeset]? + + public init( + type: ActionType, + changesets: [Changeset]? = nil + ) { + self.type = type + self.changesets = changesets + } +} + public struct SessionServerToolsChangedAction: Codable, Sendable { public var type: ActionType /// Updated server tools list (full replacement) @@ -1339,6 +1354,7 @@ public enum StateAction: Codable, Sendable { case sessionIsReadChanged(SessionIsReadChangedAction) case sessionIsArchivedChanged(SessionIsArchivedChangedAction) case sessionActivityChanged(SessionActivityChangedAction) + case sessionChangesetsChanged(SessionChangesetsChangedAction) case sessionServerToolsChanged(SessionServerToolsChangedAction) case sessionActiveClientChanged(SessionActiveClientChangedAction) case sessionActiveClientToolsChanged(SessionActiveClientToolsChangedAction) @@ -1433,6 +1449,8 @@ public enum StateAction: Codable, Sendable { self = .sessionIsArchivedChanged(try SessionIsArchivedChangedAction(from: decoder)) case "session/activityChanged": self = .sessionActivityChanged(try SessionActivityChangedAction(from: decoder)) + case "session/changesetsChanged": + self = .sessionChangesetsChanged(try SessionChangesetsChangedAction(from: decoder)) case "session/serverToolsChanged": self = .sessionServerToolsChanged(try SessionServerToolsChangedAction(from: decoder)) case "session/activeClientChanged": @@ -1538,6 +1556,7 @@ public enum StateAction: Codable, Sendable { case .sessionIsReadChanged(let v): try v.encode(to: encoder) case .sessionIsArchivedChanged(let v): try v.encode(to: encoder) case .sessionActivityChanged(let v): try v.encode(to: encoder) + case .sessionChangesetsChanged(let v): try v.encode(to: encoder) case .sessionServerToolsChanged(let v): try v.encode(to: encoder) case .sessionActiveClientChanged(let v): try v.encode(to: encoder) case .sessionActiveClientToolsChanged(let v): try v.encode(to: encoder) diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 5d89b6e4..d6fbd9cb 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -687,6 +687,12 @@ public struct SessionState: Codable, Sendable { /// and the host propagates them into this list (typically with the /// container's `clientId` set and `children` populated). public var customizations: [Customization]? + /// Catalogue of changesets the server can produce for this session. Each + /// entry advertises a subscribable view of file changes (uncommitted, + /// session-wide, per-turn, etc.) and the URI template the client expands + /// before subscribing. See {@link Changeset} for the full shape and + /// {@link /guide/changesets | Changesets} for an overview of the model. + public var changesets: [Changeset]? /// Additional provider-specific metadata for this session. /// /// Clients MAY look for well-known keys here to provide enhanced UI. @@ -707,6 +713,7 @@ public struct SessionState: Codable, Sendable { case inputRequests case config case customizations + case changesets case meta = "_meta" } @@ -723,6 +730,7 @@ public struct SessionState: Codable, Sendable { inputRequests: [SessionInputRequest]? = nil, config: SessionConfigState? = nil, customizations: [Customization]? = nil, + changesets: [Changeset]? = nil, meta: [String: AnyCodable]? = nil ) { self.summary = summary @@ -737,6 +745,7 @@ public struct SessionState: Codable, Sendable { self.inputRequests = inputRequests self.config = config self.customizations = customizations + self.changesets = changesets self.meta = meta } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift index 9399b85f..042af8a3 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift @@ -374,6 +374,11 @@ public func sessionReducer(state: SessionState, action: StateAction) -> SessionS next.summary.activity = a.activity return next + case .sessionChangesetsChanged(let a): + var next = state + next.changesets = a.changesets + return next + case .sessionConfigChanged(let a): guard var config = state.config else { return state } config.values = a.replace == true ? a.config : config.values.merging(a.config) { _, new in new } diff --git a/clients/swift/CHANGELOG.md b/clients/swift/CHANGELOG.md index 7884a8be..4a4408e2 100644 --- a/clients/swift/CHANGELOG.md +++ b/clients/swift/CHANGELOG.md @@ -29,11 +29,11 @@ the tag matches the version pinned in [`VERSION`](VERSION). ### Changed - Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. +- Moved the `changesets` catalogue from `SessionSummary` to `SessionState`. The `session/changesetsChanged` action now updates `state.changesets` directly instead of `state.summary.changesets`. ### Removed - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. -- Removed the `changesets` catalogue from `SessionSummary` and the matching `session/changesetsChanged` action. Clients discover changeset URIs out of band and subscribe to them directly. ## [0.2.0] — 2026-05-28 diff --git a/clients/typescript/CHANGELOG.md b/clients/typescript/CHANGELOG.md index 05ffbbf2..5d80d4e7 100644 --- a/clients/typescript/CHANGELOG.md +++ b/clients/typescript/CHANGELOG.md @@ -32,11 +32,11 @@ hotfix escape hatch. ### Changed - Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. +- Moved the `changesets` catalogue from `SessionSummary` to `SessionState`. The `session/changesetsChanged` action now updates `state.changesets` directly instead of `state.summary.changesets`. ### Removed - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. -- Removed the `changesets` catalogue from `SessionSummary` and the matching `session/changesetsChanged` action. Clients discover changeset URIs out of band and subscribe to them directly. ## [0.2.0] — 2026-05-28 diff --git a/docs/guide/changesets.md b/docs/guide/changesets.md index 42d227a8..70f55c3b 100644 --- a/docs/guide/changesets.md +++ b/docs/guide/changesets.md @@ -9,15 +9,19 @@ each with its own URI, lifecycle, and update stream. ## Concepts -### Changeset Discovery +### Changeset Catalogue -Clients discover a session's changesets out of band — typically through -provider-specific knowledge of the URI scheme used for changeset -resources, or by expanding RFC 6570 URI templates the provider -documents. Once a client has a concrete changeset URI it can subscribe -to it like any other resource. +Each session's `SessionState` advertises the set of changesets the +server can produce. The catalogue entry is intentionally lightweight — +just enough to render a chip or list row without subscribing — and +references a full subscribable `ChangesetState` by URI. ```typescript +SessionState { + // ...existing fields... + changesets?: Changeset[] +} + Changeset { /** Human-readable label, e.g. `"Uncommitted Changes"`. */ label: string @@ -144,15 +148,17 @@ a JSON-RPC error. ## Lifecycle -1. The client obtains a concrete changeset URI (e.g. by expanding a - template the provider documents) and subscribes to it. -2. The server returns a `ChangesetState` snapshot (`status: 'computing'` +1. The server publishes the catalogue on `SessionState.changesets`. + Updates ride on the `session/changesetsChanged` action. +2. The client picks catalogue entries whose template variables it can + satisfy and subscribes to the resulting URIs. +3. The server returns a `ChangesetState` snapshot (`status: 'computing'` is allowed if scanning is async) and pushes `changeset/*` actions as files become available. -3. The user invokes a `ChangesetOperation`. The client calls +4. The user invokes a `ChangesetOperation`. The client calls `invokeChangesetOperation`. The server applies the operation and emits any resulting changeset updates. -4. When a session ends, all of its changesets implicitly become +5. When a session ends, all of its changesets implicitly become un-subscribable. Existing subscriptions receive `changeset/disposed` and the server unsubscribes them. diff --git a/plugins/channels-migration-plugin/skills/channels-migration/SKILL.md b/plugins/channels-migration-plugin/skills/channels-migration/SKILL.md index 8466e16a..bfaaab3b 100644 --- a/plugins/channels-migration-plugin/skills/channels-migration/SKILL.md +++ b/plugins/channels-migration-plugin/skills/channels-migration/SKILL.md @@ -74,8 +74,9 @@ Concretely: fields are gone from individual actions. Routing is by envelope. - **Notifications are top-level methods**: the `notification` wrapper and the `ProtocolNotification` union are gone. -- **`SessionDiffsChangedAction` is gone**: replaced by a new family of - per-changeset actions (`changeset/statusChanged`, +- **`SessionDiffsChangedAction` is gone**: replaced by + `SessionChangesetsChangedAction` (catalogue updates on a session) plus a + new family of per-changeset actions (`changeset/statusChanged`, `changeset/fileSet`, `changeset/fileRemoved`, `changeset/operationsChanged`, `changeset/cleared`) and the `invokeChangesetOperation` command. See `docs/guide/changesets.md`. @@ -208,6 +209,8 @@ Action interfaces that lost their channel field — full list: `SessionCustomizationUpdatedAction`, `SessionTruncatedAction`, `SessionIsReadChangedAction`, `SessionIsArchivedChangedAction`, `SessionActivityChangedAction`, + `SessionChangesetsChangedAction` (replaces the removed + `SessionDiffsChangedAction`), `SessionConfigChangedAction`, `SessionMetaChangedAction`. - Tool-call actions also lost `session` because `ToolCallActionBase` no longer carries it. `turnId` and `toolCallId` remain. @@ -429,9 +432,8 @@ After the migration, your code should: `NotificationMethodParams`, or `NotificationMap`. - [ ] Resolve a session's provider via `SessionSummary.provider`, not via the URI scheme. -- [ ] No `SessionDiffsChangedAction` / `summary.diffs` references; - subscribe to a changeset URI and consume the `changeset/*` action - family instead. +- [ ] No `SessionDiffsChangedAction` / `summary.diffs` references; consume + `SessionState.changesets` plus the `changeset/*` action family instead. - [ ] Every command's params carries `channel: URI`. Channel-scoped commands (`createSession`, `disposeSession`, `createTerminal`, `disposeTerminal`, `fetchTurns`, `completions`, @@ -454,7 +456,7 @@ documents in the `microsoft/agent-host-protocol` repository: - `docs/specification/terminal-channel.md` — Terminal channel data flow and command detection - `docs/specification/lifecycle.md` — Connection handshake and reconnection -- `docs/guide/changesets.md` — Changeset channel model, +- `docs/guide/changesets.md` — Changeset channel model, catalogue, per-changeset state, and `invokeChangesetOperation` - `types/actions.ts`, `types/commands.ts`, `types/messages.ts`, `types/notifications.ts` — Source-of-truth type definitions diff --git a/schema/actions.schema.json b/schema/actions.schema.json index fd22ad9d..5eed1229 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -807,6 +807,26 @@ "activity" ] }, + "SessionChangesetsChangedAction": { + "type": "object", + "description": "The {@link Changeset | catalogue of changesets} the agent host\nadvertises for this session changed. Replaces\n{@link SessionState.changesets | `state.changesets`} entirely\n(full-replacement semantics) — set to `undefined` to clear the\ncatalogue.\n\nProducers dispatch this whenever entries are added or removed. The\nfan-out happens through this action so observers see catalogue\nmutations in the same {@link ChangesetAction | per-changeset} action\nstream they already follow for file-level updates.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.SessionChangesetsChanged" + }, + "changesets": { + "type": "array", + "items": { + "$ref": "#/$defs/Changeset" + }, + "description": "New catalogue, or `undefined` to clear it" + } + }, + "required": [ + "type", + "changesets" + ] + }, "SessionServerToolsChangedAction": { "type": "object", "description": "Server tools for this session have changed.\n\nFull-replacement semantics: the `tools` array replaces the previous `serverTools` entirely.", @@ -2153,6 +2173,13 @@ }, "description": "Top-level customizations active in this session.\n\nAlways container customizations — {@link PluginCustomization} or\n{@link DirectoryCustomization}. Children (agents, skills, prompts,\nrules, hooks, MCP servers) live in each container's\n{@link ContainerCustomizationBase.children | `children`} array.\n\nClient-published plugins arrive via\n{@link SessionActiveClient.customizations | `activeClient.customizations`}\nand the host propagates them into this list (typically with the\ncontainer's `clientId` set and `children` populated)." }, + "changesets": { + "type": "array", + "items": { + "$ref": "#/$defs/Changeset" + }, + "description": "Catalogue of changesets the server can produce for this session. Each\nentry advertises a subscribable view of file changes (uncommitted,\nsession-wide, per-turn, etc.) and the URI template the client expands\nbefore subscribing. See {@link Changeset} for the full shape and\n{@link /guide/changesets | Changesets} for an overview of the model." + }, "_meta": { "type": "object", "additionalProperties": {}, @@ -5425,6 +5452,9 @@ { "$ref": "#/$defs/SessionActivityChangedAction" }, + { + "$ref": "#/$defs/SessionChangesetsChangedAction" + }, { "$ref": "#/$defs/SessionConfigChangedAction" }, diff --git a/schema/commands.schema.json b/schema/commands.schema.json index f80bbfd8..4b2bbac0 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -1824,6 +1824,13 @@ }, "description": "Top-level customizations active in this session.\n\nAlways container customizations — {@link PluginCustomization} or\n{@link DirectoryCustomization}. Children (agents, skills, prompts,\nrules, hooks, MCP servers) live in each container's\n{@link ContainerCustomizationBase.children | `children`} array.\n\nClient-published plugins arrive via\n{@link SessionActiveClient.customizations | `activeClient.customizations`}\nand the host propagates them into this list (typically with the\ncontainer's `clientId` set and `children` populated)." }, + "changesets": { + "type": "array", + "items": { + "$ref": "#/$defs/Changeset" + }, + "description": "Catalogue of changesets the server can produce for this session. Each\nentry advertises a subscribable view of file changes (uncommitted,\nsession-wide, per-turn, etc.) and the URI template the client expands\nbefore subscribing. See {@link Changeset} for the full shape and\n{@link /guide/changesets | Changesets} for an overview of the model." + }, "_meta": { "type": "object", "additionalProperties": {}, @@ -5517,6 +5524,26 @@ "activity" ] }, + "SessionChangesetsChangedAction": { + "type": "object", + "description": "The {@link Changeset | catalogue of changesets} the agent host\nadvertises for this session changed. Replaces\n{@link SessionState.changesets | `state.changesets`} entirely\n(full-replacement semantics) — set to `undefined` to clear the\ncatalogue.\n\nProducers dispatch this whenever entries are added or removed. The\nfan-out happens through this action so observers see catalogue\nmutations in the same {@link ChangesetAction | per-changeset} action\nstream they already follow for file-level updates.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.SessionChangesetsChanged" + }, + "changesets": { + "type": "array", + "items": { + "$ref": "#/$defs/Changeset" + }, + "description": "New catalogue, or `undefined` to clear it" + } + }, + "required": [ + "type", + "changesets" + ] + }, "SessionServerToolsChangedAction": { "type": "object", "description": "Server tools for this session have changed.\n\nFull-replacement semantics: the `tools` array replaces the previous `serverTools` entirely.", diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 6c7aab66..7bfa2977 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -756,6 +756,13 @@ }, "description": "Top-level customizations active in this session.\n\nAlways container customizations — {@link PluginCustomization} or\n{@link DirectoryCustomization}. Children (agents, skills, prompts,\nrules, hooks, MCP servers) live in each container's\n{@link ContainerCustomizationBase.children | `children`} array.\n\nClient-published plugins arrive via\n{@link SessionActiveClient.customizations | `activeClient.customizations`}\nand the host propagates them into this list (typically with the\ncontainer's `clientId` set and `children` populated)." }, + "changesets": { + "type": "array", + "items": { + "$ref": "#/$defs/Changeset" + }, + "description": "Catalogue of changesets the server can produce for this session. Each\nentry advertises a subscribable view of file changes (uncommitted,\nsession-wide, per-turn, etc.) and the URI template the client expands\nbefore subscribing. See {@link Changeset} for the full shape and\n{@link /guide/changesets | Changesets} for an overview of the model." + }, "_meta": { "type": "object", "additionalProperties": {}, diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 8b98e491..b93d9cda 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -881,6 +881,13 @@ }, "description": "Top-level customizations active in this session.\n\nAlways container customizations — {@link PluginCustomization} or\n{@link DirectoryCustomization}. Children (agents, skills, prompts,\nrules, hooks, MCP servers) live in each container's\n{@link ContainerCustomizationBase.children | `children`} array.\n\nClient-published plugins arrive via\n{@link SessionActiveClient.customizations | `activeClient.customizations`}\nand the host propagates them into this list (typically with the\ncontainer's `clientId` set and `children` populated)." }, + "changesets": { + "type": "array", + "items": { + "$ref": "#/$defs/Changeset" + }, + "description": "Catalogue of changesets the server can produce for this session. Each\nentry advertises a subscribable view of file changes (uncommitted,\nsession-wide, per-turn, etc.) and the URI template the client expands\nbefore subscribing. See {@link Changeset} for the full shape and\n{@link /guide/changesets | Changesets} for an overview of the model." + }, "_meta": { "type": "object", "additionalProperties": {}, diff --git a/schema/state.schema.json b/schema/state.schema.json index c0b5c246..52e5371f 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -667,6 +667,13 @@ }, "description": "Top-level customizations active in this session.\n\nAlways container customizations — {@link PluginCustomization} or\n{@link DirectoryCustomization}. Children (agents, skills, prompts,\nrules, hooks, MCP servers) live in each container's\n{@link ContainerCustomizationBase.children | `children`} array.\n\nClient-published plugins arrive via\n{@link SessionActiveClient.customizations | `activeClient.customizations`}\nand the host propagates them into this list (typically with the\ncontainer's `clientId` set and `children` populated)." }, + "changesets": { + "type": "array", + "items": { + "$ref": "#/$defs/Changeset" + }, + "description": "Catalogue of changesets the server can produce for this session. Each\nentry advertises a subscribable view of file changes (uncommitted,\nsession-wide, per-turn, etc.) and the URI template the client expands\nbefore subscribing. See {@link Changeset} for the full shape and\n{@link /guide/changesets | Changesets} for an overview of the model." + }, "_meta": { "type": "object", "additionalProperties": {}, diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index e9893c56..35f8d68b 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -1053,6 +1053,7 @@ const ACTION_VARIANTS: { { type: 'session/isReadChanged', variantName: 'SessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, { type: 'session/isArchivedChanged', variantName: 'SessionIsArchivedChanged', tsInterface: 'SessionIsArchivedChangedAction' }, { type: 'session/activityChanged', variantName: 'SessionActivityChanged', tsInterface: 'SessionActivityChangedAction' }, + { type: 'session/changesetsChanged', variantName: 'SessionChangesetsChanged', tsInterface: 'SessionChangesetsChangedAction' }, { type: 'session/serverToolsChanged', variantName: 'SessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', variantName: 'SessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', variantName: 'SessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index cf8aa439..3e8a53eb 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -1012,6 +1012,7 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'session/isReadChanged', caseName: 'SessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, { type: 'session/isArchivedChanged', caseName: 'SessionIsArchivedChanged', tsInterface: 'SessionIsArchivedChangedAction' }, { type: 'session/activityChanged', caseName: 'SessionActivityChanged', tsInterface: 'SessionActivityChangedAction' }, + { type: 'session/changesetsChanged', caseName: 'SessionChangesetsChanged', tsInterface: 'SessionChangesetsChangedAction' }, { type: 'session/serverToolsChanged', caseName: 'SessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', caseName: 'SessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', caseName: 'SessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index b6fb64a1..59ba3101 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -894,6 +894,7 @@ const ACTION_VARIANTS: { { type: 'session/isReadChanged', variantName: 'SessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, { type: 'session/isArchivedChanged', variantName: 'SessionIsArchivedChanged', tsInterface: 'SessionIsArchivedChangedAction' }, { type: 'session/activityChanged', variantName: 'SessionActivityChanged', tsInterface: 'SessionActivityChangedAction' }, + { type: 'session/changesetsChanged', variantName: 'SessionChangesetsChanged', tsInterface: 'SessionChangesetsChangedAction' }, { type: 'session/serverToolsChanged', variantName: 'SessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', variantName: 'SessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', variantName: 'SessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, @@ -967,7 +968,7 @@ pub struct SessionToolCallConfirmedAction { function generateActionsFile(project: Project): string { const lines: string[] = [GENERATED_HEADER]; - lines.push('use crate::state::{AgentInfo, AgentSelection, ConfirmationOption, Customization, ErrorInfo, ModelSelection, ResponsePart, SessionActiveClient, SessionInputAnswer, SessionInputRequest, SessionInputResponseKind, TerminalClaim, TerminalInfo, ToolCallResult, ToolCallConfirmationReason, ToolCallCancellationReason, ToolDefinition, ToolResultContent, UsageInfo, Message, PendingMessageKind, ChangesetStatus, ChangesetFile, ChangesetOperation, ChangesetOperationStatus};'); + lines.push('use crate::state::{AgentInfo, AgentSelection, ConfirmationOption, Customization, ErrorInfo, ModelSelection, ResponsePart, SessionActiveClient, SessionInputAnswer, SessionInputRequest, SessionInputResponseKind, TerminalClaim, TerminalInfo, ToolCallResult, ToolCallConfirmationReason, ToolCallCancellationReason, ToolDefinition, ToolResultContent, UsageInfo, Message, PendingMessageKind, ChangesetStatus, ChangesetFile, ChangesetOperation, ChangesetOperationStatus, Changeset};'); lines.push(''); // ActionType enum diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index 02d2bb28..b10219b8 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -871,6 +871,7 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'session/isReadChanged', caseName: 'sessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, { type: 'session/isArchivedChanged', caseName: 'sessionIsArchivedChanged', tsInterface: 'SessionIsArchivedChangedAction' }, { type: 'session/activityChanged', caseName: 'sessionActivityChanged', tsInterface: 'SessionActivityChangedAction' }, + { type: 'session/changesetsChanged', caseName: 'sessionChangesetsChanged', tsInterface: 'SessionChangesetsChangedAction' }, { type: 'session/serverToolsChanged', caseName: 'sessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', caseName: 'sessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', caseName: 'sessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, diff --git a/types/action-origin.generated.ts b/types/action-origin.generated.ts index 851b9392..53d66fdc 100644 --- a/types/action-origin.generated.ts +++ b/types/action-origin.generated.ts @@ -44,6 +44,7 @@ import type { SessionIsReadChangedAction, SessionIsArchivedChangedAction, SessionActivityChangedAction, + SessionChangesetsChangedAction, SessionConfigChangedAction, SessionMetaChangedAction, ChangesetStatusChangedAction, @@ -129,6 +130,7 @@ export type SessionAction = | SessionIsReadChangedAction | SessionIsArchivedChangedAction | SessionActivityChangedAction + | SessionChangesetsChangedAction | SessionConfigChangedAction | SessionMetaChangedAction ; @@ -177,6 +179,7 @@ export type ServerSessionAction = | SessionCustomizationUpdatedAction | SessionCustomizationRemovedAction | SessionActivityChangedAction + | SessionChangesetsChangedAction | SessionMetaChangedAction ; @@ -302,6 +305,7 @@ export const IS_CLIENT_DISPATCHABLE: { readonly [K in StateAction['type']]: bool [ActionType.SessionIsReadChanged]: true, [ActionType.SessionIsArchivedChanged]: true, [ActionType.SessionActivityChanged]: false, + [ActionType.SessionChangesetsChanged]: false, [ActionType.SessionConfigChanged]: true, [ActionType.SessionMetaChanged]: false, [ActionType.ChangesetStatusChanged]: false, diff --git a/types/channels-session/actions.ts b/types/channels-session/actions.ts index c376c441..4881eca5 100644 --- a/types/channels-session/actions.ts +++ b/types/channels-session/actions.ts @@ -26,6 +26,7 @@ import { ToolCallCancellationReason, PendingMessageKind, } from './state.js'; +import type { Changeset } from '../channels-changeset/state.js'; // ─── Tool Call Action Base ─────────────────────────────────────────────────── @@ -490,6 +491,27 @@ export interface SessionActivityChangedAction { activity: string | undefined; } +/** + * The {@link Changeset | catalogue of changesets} the agent host + * advertises for this session changed. Replaces + * {@link SessionState.changesets | `state.changesets`} entirely + * (full-replacement semantics) — set to `undefined` to clear the + * catalogue. + * + * Producers dispatch this whenever entries are added or removed. The + * fan-out happens through this action so observers see catalogue + * mutations in the same {@link ChangesetAction | per-changeset} action + * stream they already follow for file-level updates. + * + * @category Session Actions + * @version 1 + */ +export interface SessionChangesetsChangedAction { + type: ActionType.SessionChangesetsChanged; + /** New catalogue, or `undefined` to clear it */ + changesets: Changeset[] | undefined; +} + /** * Server tools for this session have changed. * diff --git a/types/channels-session/reducer.ts b/types/channels-session/reducer.ts index 50735904..3273ad2a 100644 --- a/types/channels-session/reducer.ts +++ b/types/channels-session/reducer.ts @@ -567,6 +567,13 @@ export function sessionReducer(state: SessionState, action: SessionAction, log?: summary: { ...state.summary, activity: action.activity }, }; + case ActionType.SessionChangesetsChanged: { + const { changesets: _omit, ...stateWithoutChangesets } = state; + return action.changesets + ? { ...stateWithoutChangesets, changesets: action.changesets } + : stateWithoutChangesets; + } + case ActionType.SessionConfigChanged: if (!state.config) { return state; diff --git a/types/channels-session/state.ts b/types/channels-session/state.ts index 94ade168..488ed6d8 100644 --- a/types/channels-session/state.ts +++ b/types/channels-session/state.ts @@ -18,6 +18,7 @@ import type { UsageInfo, } from '../common/state.js'; import type { ModelSelection } from '../channels-root/state.js'; +import type { Changeset } from '../channels-changeset/state.js'; // ─── Pending Message Types ─────────────────────────────────────────────────── @@ -128,6 +129,14 @@ export interface SessionState { * container's `clientId` set and `children` populated). */ customizations?: Customization[]; + /** + * Catalogue of changesets the server can produce for this session. Each + * entry advertises a subscribable view of file changes (uncommitted, + * session-wide, per-turn, etc.) and the URI template the client expands + * before subscribing. See {@link Changeset} for the full shape and + * {@link /guide/changesets | Changesets} for an overview of the model. + */ + changesets?: Changeset[]; /** * Additional provider-specific metadata for this session. * diff --git a/types/common/actions.ts b/types/common/actions.ts index 5ad2b7e3..1121f09d 100644 --- a/types/common/actions.ts +++ b/types/common/actions.ts @@ -53,6 +53,7 @@ import type { SessionIsReadChangedAction, SessionIsArchivedChangedAction, SessionActivityChangedAction, + SessionChangesetsChangedAction, SessionConfigChangedAction, SessionMetaChangedAction, } from '../channels-session/actions.js'; @@ -131,6 +132,7 @@ export const enum ActionType { SessionIsReadChanged = 'session/isReadChanged', SessionIsArchivedChanged = 'session/isArchivedChanged', SessionActivityChanged = 'session/activityChanged', + SessionChangesetsChanged = 'session/changesetsChanged', SessionConfigChanged = 'session/configChanged', SessionMetaChanged = 'session/metaChanged', ChangesetStatusChanged = 'changeset/statusChanged', @@ -230,6 +232,7 @@ export type StateAction = | SessionIsReadChangedAction | SessionIsArchivedChangedAction | SessionActivityChangedAction + | SessionChangesetsChangedAction | SessionConfigChangedAction | SessionMetaChangedAction | ChangesetStatusChangedAction diff --git a/types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json b/types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json new file mode 100644 index 00000000..7bdb0405 --- /dev/null +++ b/types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json @@ -0,0 +1,45 @@ +{ + "description": "session/changesetsChanged replaces state.changesets with the new catalogue", + "reducer": "session", + "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", + "turns": [] + }, + "actions": [ + { + "type": "session/changesetsChanged", + "changesets": [ + { + "label": "Session Changes", + "uriTemplate": "copilot:/test-session/changeset/session" + } + ] + } + ], + "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", + "turns": [], + "changesets": [ + { + "label": "Session Changes", + "uriTemplate": "copilot:/test-session/changeset/session" + } + ] + } +} diff --git a/types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json b/types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json new file mode 100644 index 00000000..78c6c5b1 --- /dev/null +++ b/types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json @@ -0,0 +1,40 @@ +{ + "description": "session/changesetsChanged with undefined clears state.changesets", + "reducer": "session", + "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", + "turns": [], + "changesets": [ + { + "label": "Session Changes", + "uriTemplate": "copilot:/test-session/changeset/session" + } + ] + }, + "actions": [ + { + "type": "session/changesetsChanged", + "changesets": null + } + ], + "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", + "turns": [] + } +} diff --git a/types/version/registry.ts b/types/version/registry.ts index 976c83e1..b27704cf 100644 --- a/types/version/registry.ts +++ b/types/version/registry.ts @@ -114,6 +114,7 @@ export const ACTION_INTRODUCED_IN: { readonly [K in StateAction['type']]: string [ActionType.SessionIsReadChanged]: '0.1.0', [ActionType.SessionIsArchivedChanged]: '0.1.0', [ActionType.SessionActivityChanged]: '0.1.0', + [ActionType.SessionChangesetsChanged]: '0.2.0', [ActionType.SessionConfigChanged]: '0.1.0', [ActionType.SessionMetaChanged]: '0.1.0', [ActionType.ChangesetStatusChanged]: '0.2.0',