From 849e8461f6e346796f23edc419409c5fb825c0af Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 4 Jun 2026 21:45:40 +0200 Subject: [PATCH 01/11] Support for adding, editing and deleting comments --- CHANGELOG.md | 9 + clients/go/CHANGELOG.md | 10 + clients/go/ahp/reducers.go | 19 + clients/go/ahp/reducers_fixture_test.go | 3 + clients/go/ahptypes/actions.generated.go | 405 +++++++++++------- clients/go/ahptypes/commands.generated.go | 169 ++++++-- clients/go/ahptypes/errors.generated.go | 20 +- .../go/ahptypes/notifications.generated.go | 33 +- clients/go/ahptypes/state.generated.go | 367 ++++++++++------ clients/kotlin/CHANGELOG.md | 11 + .../microsoft/agenthostprotocol/Reducers.kt | 88 +++- .../generated/Actions.generated.kt | 74 ++++ .../generated/Commands.generated.kt | 132 ++++++ .../generated/Messages.generated.kt | 18 + .../generated/Notifications.generated.kt | 9 +- .../generated/State.generated.kt | 110 ++++- .../FixtureDrivenReducerTest.kt | 12 + clients/rust/CHANGELOG.md | 9 + clients/rust/crates/ahp-types/src/actions.rs | 97 ++++- clients/rust/crates/ahp-types/src/commands.rs | 134 +++++- .../crates/ahp-types/src/notifications.rs | 10 +- clients/rust/crates/ahp-types/src/state.rs | 103 ++++- .../crates/ahp/src/multi_host_state_mirror.rs | 15 +- clients/rust/crates/ahp/src/reducers.rs | 6 + clients/rust/crates/ahp/tests/hosts.rs | 1 + .../ahp/tests/multi_host_state_mirror.rs | 2 + .../Generated/Actions.generated.swift | 99 +++++ .../Generated/Commands.generated.swift | 156 +++++++ .../Generated/Notifications.generated.swift | 9 +- .../Generated/State.generated.swift | 138 +++++- .../FixtureDrivenReducerTests.swift | 6 +- clients/swift/CHANGELOG.md | 9 + clients/typescript/CHANGELOG.md | 7 + docs/.vitepress/config.mts | 1 + schema/actions.schema.json | 233 ++++++++++ schema/commands.schema.json | 404 +++++++++++++++++ schema/errors.schema.json | 314 ++++++++++++++ schema/notifications.schema.json | 132 ++++++ schema/state.schema.json | 128 ++++++ scripts/find-protocol-sources.ts | 1 + scripts/generate-action-origin.ts | 45 +- scripts/generate-go.ts | 35 +- scripts/generate-kotlin.ts | 43 +- scripts/generate-markdown.ts | 31 ++ scripts/generate-rust.ts | 33 +- scripts/generate-swift.ts | 21 +- types/action-origin.generated.ts | 33 ++ types/actions.ts | 1 + types/channels-comments/actions.ts | 105 +++++ types/channels-comments/commands.ts | 196 +++++++++ types/channels-comments/reducer.ts | 94 ++++ types/channels-comments/state.ts | 130 ++++++ types/channels-session/state.ts | 10 +- types/commands.ts | 1 + types/common/actions.ts | 18 + types/common/messages.ts | 16 + types/common/reducer-helpers.ts | 4 +- types/common/state.ts | 3 +- types/index.ts | 22 + types/messages.test.ts | 1 + types/reducers.test.ts | 12 +- types/reducers.ts | 1 + types/state.ts | 1 + ...comments-threadset-appends-new-thread.json | 77 ++++ ...ts-threadset-replaces-existing-thread.json | 55 +++ ...s-threadremoved-drops-matching-thread.json | 52 +++ ...ments-commentset-appends-and-replaces.json | 49 +++ ...ts-commentset-unknown-thread-is-no-op.json | 43 ++ ...commentremoved-drops-matching-comment.json | 44 ++ .../216-comments-cleared-empties-threads.json | 27 ++ ...comments-unknown-action-type-is-no-op.json | 39 ++ types/version/message-checks.ts | 8 +- types/version/registry.ts | 5 + 73 files changed, 4363 insertions(+), 395 deletions(-) create mode 100644 types/channels-comments/actions.ts create mode 100644 types/channels-comments/commands.ts create mode 100644 types/channels-comments/reducer.ts create mode 100644 types/channels-comments/state.ts create mode 100644 types/test-cases/reducers/210-comments-threadset-appends-new-thread.json create mode 100644 types/test-cases/reducers/211-comments-threadset-replaces-existing-thread.json create mode 100644 types/test-cases/reducers/212-comments-threadremoved-drops-matching-thread.json create mode 100644 types/test-cases/reducers/213-comments-commentset-appends-and-replaces.json create mode 100644 types/test-cases/reducers/214-comments-commentset-unknown-thread-is-no-op.json create mode 100644 types/test-cases/reducers/215-comments-commentremoved-drops-matching-comment.json create mode 100644 types/test-cases/reducers/216-comments-cleared-empties-threads.json create mode 100644 types/test-cases/reducers/217-comments-unknown-action-type-is-no-op.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 26ad8832..42867511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,15 @@ Spec version: `0.3.0` - Added optional `changes` field of type `ChangesSummary` to `SessionSummary`, carrying optional `additions`, `deletions`, and `files` counts so servers can advertise an at-a-glance view of a session's file-change footprint. +- Added a new comments channel exposed on `ahp-session://comments`. + Threads anchor to a `(turnId, resource, range)` triple and always carry + at least one comment; new `createCommentThread`, `updateCommentThread`, + `deleteCommentThread`, `addComment`, `editComment`, `deleteComment` + commands drive mutations and echo as `comments/threadSet`, + `comments/threadRemoved`, `comments/commentSet`, `comments/commentRemoved`, + and `comments/cleared` actions. `SessionSummary.comments` advertises the + per-session `CommentsSummary` (`{ resource, threadCount, commentCount }`) + for badge UI. - 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`. diff --git a/clients/go/CHANGELOG.md b/clients/go/CHANGELOG.md index 23fd26d1..a01eab3e 100644 --- a/clients/go/CHANGELOG.md +++ b/clients/go/CHANGELOG.md @@ -21,6 +21,16 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file. `idle → running → error` lifecycle of a changeset operation. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. +- New comments channel wire types (`ahp-session://comments`): + `CommentsState`, `CommentThread`, `Comment`, `NewComment`, + `CommentsSummary`; the `CommentsThreadSetAction`, + `CommentsThreadRemovedAction`, `CommentsCommentSetAction`, + `CommentsCommentRemovedAction`, `CommentsClearedAction` variants; + `CreateCommentThreadParams/Result`, `UpdateCommentThreadParams`, + `DeleteCommentThreadParams`, `AddCommentParams/Result`, + `EditCommentParams`, `DeleteCommentParams` command structs; + `ApplyActionToComments` (stub mirroring `ApplyActionToChangeset`); and + `SnapshotState.Comments`. ### Changed diff --git a/clients/go/ahp/reducers.go b/clients/go/ahp/reducers.go index 6f6c4eb4..2459eaf3 100644 --- a/clients/go/ahp/reducers.go +++ b/clients/go/ahp/reducers.go @@ -1119,3 +1119,22 @@ func ApplyActionToChangeset(state *ahptypes.ChangesetState, action ahptypes.Stat } return ReduceOutcomeOutOfScope } + +// ─── Comments Reducer ───────────────────────────────────────── + +// ApplyActionToComments is the entry point for comments actions. +// Mirrors the Rust client's stub: every recognized comments action +// short-circuits as [ReduceOutcomeNoOp] until the full comments +// reducer is ported. Unrelated actions return [ReduceOutcomeOutOfScope]. +func ApplyActionToComments(state *ahptypes.CommentsState, action ahptypes.StateAction) ReduceOutcome { + _ = state + switch action.Value.(type) { + case *ahptypes.CommentsThreadSetAction, + *ahptypes.CommentsThreadRemovedAction, + *ahptypes.CommentsCommentSetAction, + *ahptypes.CommentsCommentRemovedAction, + *ahptypes.CommentsClearedAction: + return ReduceOutcomeNoOp + } + return ReduceOutcomeOutOfScope +} diff --git a/clients/go/ahp/reducers_fixture_test.go b/clients/go/ahp/reducers_fixture_test.go index 4fe14b7b..0259175c 100644 --- a/clients/go/ahp/reducers_fixture_test.go +++ b/clients/go/ahp/reducers_fixture_test.go @@ -154,6 +154,9 @@ func TestFixtureDrivenReducerParity(t *testing.T) { case "changeset": // Changeset reducer logic is deferred — skip. tt.Skip("changeset reducer is a stub in this client (parity with Rust)") + case "comments": + // Comments reducer logic is deferred — skip. + tt.Skip("comments reducer is a stub in this client (parity with Rust)") case "resourceWatch": // Resource-watch reducer logic is deferred — skip. tt.Skip("resourceWatch reducer is a stub in this client (parity with Rust)") diff --git a/clients/go/ahptypes/actions.generated.go b/clients/go/ahptypes/actions.generated.go index 452aec88..3aeefa4a 100644 --- a/clients/go/ahptypes/actions.generated.go +++ b/clients/go/ahptypes/actions.generated.go @@ -19,76 +19,81 @@ var _ = json.RawMessage(nil) type ActionType string const ( - ActionTypeRootAgentsChanged ActionType = "root/agentsChanged" - ActionTypeRootActiveSessionsChanged ActionType = "root/activeSessionsChanged" - ActionTypeSessionReady ActionType = "session/ready" - ActionTypeSessionCreationFailed ActionType = "session/creationFailed" - ActionTypeSessionTurnStarted ActionType = "session/turnStarted" - ActionTypeSessionDelta ActionType = "session/delta" - ActionTypeSessionResponsePart ActionType = "session/responsePart" - ActionTypeSessionToolCallStart ActionType = "session/toolCallStart" - ActionTypeSessionToolCallDelta ActionType = "session/toolCallDelta" - ActionTypeSessionToolCallReady ActionType = "session/toolCallReady" - ActionTypeSessionToolCallConfirmed ActionType = "session/toolCallConfirmed" - ActionTypeSessionToolCallComplete ActionType = "session/toolCallComplete" - ActionTypeSessionToolCallResultConfirmed ActionType = "session/toolCallResultConfirmed" - ActionTypeSessionToolCallContentChanged ActionType = "session/toolCallContentChanged" - ActionTypeSessionTurnComplete ActionType = "session/turnComplete" - ActionTypeSessionTurnCancelled ActionType = "session/turnCancelled" - ActionTypeSessionError ActionType = "session/error" - ActionTypeSessionTitleChanged ActionType = "session/titleChanged" - ActionTypeSessionUsage ActionType = "session/usage" - ActionTypeSessionReasoning ActionType = "session/reasoning" - ActionTypeSessionModelChanged ActionType = "session/modelChanged" - ActionTypeSessionAgentChanged ActionType = "session/agentChanged" - ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged" - ActionTypeSessionActiveClientChanged ActionType = "session/activeClientChanged" - ActionTypeSessionActiveClientToolsChanged ActionType = "session/activeClientToolsChanged" - ActionTypeSessionPendingMessageSet ActionType = "session/pendingMessageSet" - ActionTypeSessionPendingMessageRemoved ActionType = "session/pendingMessageRemoved" - ActionTypeSessionQueuedMessagesReordered ActionType = "session/queuedMessagesReordered" - ActionTypeSessionInputRequested ActionType = "session/inputRequested" - ActionTypeSessionInputAnswerChanged ActionType = "session/inputAnswerChanged" - ActionTypeSessionInputCompleted ActionType = "session/inputCompleted" - ActionTypeSessionCustomizationsChanged ActionType = "session/customizationsChanged" - ActionTypeSessionCustomizationToggled ActionType = "session/customizationToggled" - ActionTypeSessionCustomizationUpdated ActionType = "session/customizationUpdated" - ActionTypeSessionCustomizationRemoved ActionType = "session/customizationRemoved" - ActionTypeSessionTruncated ActionType = "session/truncated" - 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" - ActionTypeChangesetFileSet ActionType = "changeset/fileSet" - ActionTypeChangesetFileRemoved ActionType = "changeset/fileRemoved" - ActionTypeChangesetOperationsChanged ActionType = "changeset/operationsChanged" - ActionTypeChangesetOperationStatusChanged ActionType = "changeset/operationStatusChanged" - ActionTypeChangesetCleared ActionType = "changeset/cleared" - ActionTypeRootTerminalsChanged ActionType = "root/terminalsChanged" - ActionTypeRootConfigChanged ActionType = "root/configChanged" - ActionTypeTerminalData ActionType = "terminal/data" - ActionTypeTerminalInput ActionType = "terminal/input" - ActionTypeTerminalResized ActionType = "terminal/resized" - ActionTypeTerminalClaimed ActionType = "terminal/claimed" - ActionTypeTerminalTitleChanged ActionType = "terminal/titleChanged" - ActionTypeTerminalCwdChanged ActionType = "terminal/cwdChanged" - ActionTypeTerminalExited ActionType = "terminal/exited" - ActionTypeTerminalCleared ActionType = "terminal/cleared" + ActionTypeRootAgentsChanged ActionType = "root/agentsChanged" + ActionTypeRootActiveSessionsChanged ActionType = "root/activeSessionsChanged" + ActionTypeSessionReady ActionType = "session/ready" + ActionTypeSessionCreationFailed ActionType = "session/creationFailed" + ActionTypeSessionTurnStarted ActionType = "session/turnStarted" + ActionTypeSessionDelta ActionType = "session/delta" + ActionTypeSessionResponsePart ActionType = "session/responsePart" + ActionTypeSessionToolCallStart ActionType = "session/toolCallStart" + ActionTypeSessionToolCallDelta ActionType = "session/toolCallDelta" + ActionTypeSessionToolCallReady ActionType = "session/toolCallReady" + ActionTypeSessionToolCallConfirmed ActionType = "session/toolCallConfirmed" + ActionTypeSessionToolCallComplete ActionType = "session/toolCallComplete" + ActionTypeSessionToolCallResultConfirmed ActionType = "session/toolCallResultConfirmed" + ActionTypeSessionToolCallContentChanged ActionType = "session/toolCallContentChanged" + ActionTypeSessionTurnComplete ActionType = "session/turnComplete" + ActionTypeSessionTurnCancelled ActionType = "session/turnCancelled" + ActionTypeSessionError ActionType = "session/error" + ActionTypeSessionTitleChanged ActionType = "session/titleChanged" + ActionTypeSessionUsage ActionType = "session/usage" + ActionTypeSessionReasoning ActionType = "session/reasoning" + ActionTypeSessionModelChanged ActionType = "session/modelChanged" + ActionTypeSessionAgentChanged ActionType = "session/agentChanged" + ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged" + ActionTypeSessionActiveClientChanged ActionType = "session/activeClientChanged" + ActionTypeSessionActiveClientToolsChanged ActionType = "session/activeClientToolsChanged" + ActionTypeSessionPendingMessageSet ActionType = "session/pendingMessageSet" + ActionTypeSessionPendingMessageRemoved ActionType = "session/pendingMessageRemoved" + ActionTypeSessionQueuedMessagesReordered ActionType = "session/queuedMessagesReordered" + ActionTypeSessionInputRequested ActionType = "session/inputRequested" + ActionTypeSessionInputAnswerChanged ActionType = "session/inputAnswerChanged" + ActionTypeSessionInputCompleted ActionType = "session/inputCompleted" + ActionTypeSessionCustomizationsChanged ActionType = "session/customizationsChanged" + ActionTypeSessionCustomizationToggled ActionType = "session/customizationToggled" + ActionTypeSessionCustomizationUpdated ActionType = "session/customizationUpdated" + ActionTypeSessionCustomizationRemoved ActionType = "session/customizationRemoved" + ActionTypeSessionTruncated ActionType = "session/truncated" + 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" + ActionTypeChangesetFileSet ActionType = "changeset/fileSet" + ActionTypeChangesetFileRemoved ActionType = "changeset/fileRemoved" + ActionTypeChangesetOperationsChanged ActionType = "changeset/operationsChanged" + ActionTypeChangesetOperationStatusChanged ActionType = "changeset/operationStatusChanged" + ActionTypeChangesetCleared ActionType = "changeset/cleared" + ActionTypeCommentsThreadSet ActionType = "comments/threadSet" + ActionTypeCommentsThreadRemoved ActionType = "comments/threadRemoved" + ActionTypeCommentsCommentSet ActionType = "comments/commentSet" + ActionTypeCommentsCommentRemoved ActionType = "comments/commentRemoved" + ActionTypeCommentsCleared ActionType = "comments/cleared" + ActionTypeRootTerminalsChanged ActionType = "root/terminalsChanged" + ActionTypeRootConfigChanged ActionType = "root/configChanged" + ActionTypeTerminalData ActionType = "terminal/data" + ActionTypeTerminalInput ActionType = "terminal/input" + ActionTypeTerminalResized ActionType = "terminal/resized" + ActionTypeTerminalClaimed ActionType = "terminal/claimed" + ActionTypeTerminalTitleChanged ActionType = "terminal/titleChanged" + ActionTypeTerminalCwdChanged ActionType = "terminal/cwdChanged" + ActionTypeTerminalExited ActionType = "terminal/exited" + ActionTypeTerminalCleared ActionType = "terminal/cleared" ActionTypeTerminalCommandDetectionAvailable ActionType = "terminal/commandDetectionAvailable" - ActionTypeTerminalCommandExecuted ActionType = "terminal/commandExecuted" - ActionTypeTerminalCommandFinished ActionType = "terminal/commandFinished" - ActionTypeResourceWatchChanged ActionType = "resourceWatch/changed" + ActionTypeTerminalCommandExecuted ActionType = "terminal/commandExecuted" + ActionTypeTerminalCommandFinished ActionType = "terminal/commandFinished" + ActionTypeResourceWatchChanged ActionType = "resourceWatch/changed" ) // ─── Action Envelope ───────────────────────────────────────────────── // Identifies the client that originally dispatched an action. type ActionOrigin struct { - ClientId string `json:"clientId"` - ClientSeq int64 `json:"clientSeq"` + ClientId string `json:"clientId"` + ClientSeq int64 `json:"clientSeq"` } // ActionEnvelope wraps every action with the channel URI it @@ -195,7 +200,7 @@ type SessionToolCallStartAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Internal tool name (for debugging/logging) ToolName string `json:"toolName"` // Human-readable tool name @@ -218,7 +223,7 @@ type SessionToolCallDeltaAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Partial parameter content to append Content string `json:"content"` // Updated progress message @@ -250,7 +255,7 @@ type SessionToolCallReadyAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Message describing what the tool will do or what confirmation is needed InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input @@ -273,17 +278,17 @@ type SessionToolCallReadyAction struct { // SessionToolCallConfirmedAction is the client approves or denies a // pending tool call (merged approved + denied variants on the wire). type SessionToolCallConfirmedAction struct { - Type ActionType `json:"type"` - TurnId string `json:"turnId"` - ToolCallId string `json:"toolCallId"` - Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Approved bool `json:"approved"` - Confirmed *ToolCallConfirmationReason `json:"confirmed,omitempty"` - Reason *ToolCallCancellationReason `json:"reason,omitempty"` - EditedToolInput *string `json:"editedToolInput,omitempty"` - UserSuggestion *Message `json:"userSuggestion,omitempty"` - ReasonMessage *StringOrMarkdown `json:"reasonMessage,omitempty"` - SelectedOptionId *string `json:"selectedOptionId,omitempty"` + Type ActionType `json:"type"` + TurnId string `json:"turnId"` + ToolCallId string `json:"toolCallId"` + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Approved bool `json:"approved"` + Confirmed *ToolCallConfirmationReason `json:"confirmed,omitempty"` + Reason *ToolCallCancellationReason `json:"reason,omitempty"` + EditedToolInput *string `json:"editedToolInput,omitempty"` + UserSuggestion *Message `json:"userSuggestion,omitempty"` + ReasonMessage *StringOrMarkdown `json:"reasonMessage,omitempty"` + SelectedOptionId *string `json:"selectedOptionId,omitempty"` } // Tool execution finished. Transitions to `completed` or `pending-result-confirmation` @@ -308,7 +313,7 @@ type SessionToolCallCompleteAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Execution result Result ToolCallResult `json:"result"` // If true, the result requires client approval before finalizing @@ -330,7 +335,7 @@ type SessionToolCallResultConfirmedAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Whether the result was approved Approved bool `json:"approved"` } @@ -600,10 +605,10 @@ type SessionCustomizationToggledAction struct { // // The reducer locates the existing entry by `customization.id`: // -// - If found, the entry is replaced entirely with `customization`, -// including its `children` array. To preserve existing children, the -// host must include them on the payload. -// - If not found, the entry is appended. +// - If found, the entry is replaced entirely with `customization`, +// including its `children` array. To preserve existing children, the +// host must include them on the payload. +// - If not found, the entry is appended. type SessionCustomizationUpdatedAction struct { Type ActionType `json:"type"` // The customization to upsert (matched by `customization.id`). @@ -680,7 +685,7 @@ type SessionToolCallContentChangedAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // The current partial content for the running tool call Content []ToolResultContent `json:"content"` } @@ -746,12 +751,12 @@ type ChangesetOperationStatusChangedAction struct { // Drop every file from the changeset. // // Two cases use this: -// 1. The underlying source moved (branch switched, fork point invalidated, -// …) and the server is recomputing from scratch — subsequent -// {@link ChangesetFileSetAction} entries will repopulate it. -// 2. The owning session has ended and the URI is becoming -// un-subscribable — the server will unsubscribe all clients shortly -// after dispatching this action. +// 1. The underlying source moved (branch switched, fork point invalidated, +// …) and the server is recomputing from scratch — subsequent +// {@link ChangesetFileSetAction} entries will repopulate it. +// 2. The owning session has ended and the URI is becoming +// un-subscribable — the server will unsubscribe all clients shortly +// after dispatching this action. // // Clients SHOULD release any references on receipt and SHOULD NOT // distinguish the two cases from the action alone — instead, react to @@ -761,6 +766,69 @@ type ChangesetClearedAction struct { Type ActionType `json:"type"` } +// Upsert a {@link CommentThread} in the comments channel — adds a new +// thread, or replaces an existing one identified by +// {@link CommentThread.id}. When replacing, the full thread payload +// (including its {@link CommentThread.comments | comments} list) is +// substituted; producers SHOULD prefer {@link CommentsCommentSetAction} +// for per-comment edits to keep wire updates small. +type CommentsThreadSetAction struct { + Type ActionType `json:"type"` + // The new or replacement thread. MUST contain at least one comment. + Thread CommentThread `json:"thread"` +} + +// Remove a {@link CommentThread} from the channel by its id. +// +// The server emits this in two cases: +// 1. The client explicitly invoked +// {@link DeleteCommentThreadParams | `deleteCommentThread`}. +// 2. The client invoked {@link DeleteCommentParams | `deleteComment`} on +// the last remaining comment in the thread — the protocol collapses +// the thread rather than leaving an empty one behind. +type CommentsThreadRemovedAction struct { + Type ActionType `json:"type"` + // The {@link CommentThread.id} of the thread to remove. + ThreadId string `json:"threadId"` +} + +// Upsert a {@link Comment} within an existing thread — adds a new +// comment, or replaces one identified by {@link Comment.id}. If +// {@link threadId} does not match any current thread the action is a +// no-op. +type CommentsCommentSetAction struct { + Type ActionType `json:"type"` + // The {@link CommentThread.id} the comment belongs to. + ThreadId string `json:"threadId"` + // The new or replacement comment. + Comment Comment `json:"comment"` +} + +// Remove a single {@link Comment} from a thread without collapsing the +// thread itself. Used when more than one comment remains — the server +// MUST dispatch {@link CommentsThreadRemovedAction} instead when removing +// the last comment would otherwise leave the thread empty. +// +// If either {@link threadId} or {@link commentId} does not match the +// current state the action is a no-op. +type CommentsCommentRemovedAction struct { + Type ActionType `json:"type"` + // The {@link CommentThread.id} the comment belongs to. + ThreadId string `json:"threadId"` + // The {@link Comment.id} to remove. + CommentId string `json:"commentId"` +} + +// Drop every thread from the comments channel. +// +// Dispatched when the owning session is going away and the channel is +// about to become un-subscribable. Clients SHOULD release references on +// receipt and react to the corresponding session-level lifecycle signal +// (e.g. `root/sessionRemoved`) to fully tear down UI. +type CommentsClearedAction struct { + Type ActionType `json:"type"` +} + // Fired when the list of known terminals changes. // // Full-replacement semantics: the `terminals` array replaces the previous @@ -916,68 +984,73 @@ type StateAction struct { // concrete variant of StateAction. type isStateAction interface{ isStateAction() } -func (*RootAgentsChangedAction) isStateAction() {} -func (*RootActiveSessionsChangedAction) isStateAction() {} -func (*RootConfigChangedAction) isStateAction() {} -func (*SessionReadyAction) isStateAction() {} -func (*SessionCreationFailedAction) isStateAction() {} -func (*SessionTurnStartedAction) isStateAction() {} -func (*SessionDeltaAction) isStateAction() {} -func (*SessionResponsePartAction) isStateAction() {} -func (*SessionToolCallStartAction) isStateAction() {} -func (*SessionToolCallDeltaAction) isStateAction() {} -func (*SessionToolCallReadyAction) isStateAction() {} -func (*SessionToolCallConfirmedAction) isStateAction() {} -func (*SessionToolCallCompleteAction) isStateAction() {} -func (*SessionToolCallResultConfirmedAction) isStateAction() {} -func (*SessionTurnCompleteAction) isStateAction() {} -func (*SessionTurnCancelledAction) isStateAction() {} -func (*SessionErrorAction) isStateAction() {} -func (*SessionTitleChangedAction) isStateAction() {} -func (*SessionUsageAction) isStateAction() {} -func (*SessionReasoningAction) isStateAction() {} -func (*SessionModelChangedAction) isStateAction() {} -func (*SessionAgentChangedAction) isStateAction() {} -func (*SessionIsReadChangedAction) isStateAction() {} -func (*SessionIsArchivedChangedAction) isStateAction() {} -func (*SessionActivityChangedAction) isStateAction() {} -func (*SessionChangesetsChangedAction) isStateAction() {} -func (*SessionServerToolsChangedAction) isStateAction() {} -func (*SessionActiveClientChangedAction) isStateAction() {} -func (*SessionActiveClientToolsChangedAction) isStateAction() {} -func (*SessionPendingMessageSetAction) isStateAction() {} -func (*SessionPendingMessageRemovedAction) isStateAction() {} -func (*SessionQueuedMessagesReorderedAction) isStateAction() {} -func (*SessionInputRequestedAction) isStateAction() {} -func (*SessionInputAnswerChangedAction) isStateAction() {} -func (*SessionInputCompletedAction) isStateAction() {} -func (*SessionCustomizationsChangedAction) isStateAction() {} -func (*SessionCustomizationToggledAction) isStateAction() {} -func (*SessionCustomizationUpdatedAction) isStateAction() {} -func (*SessionCustomizationRemovedAction) isStateAction() {} -func (*SessionTruncatedAction) isStateAction() {} -func (*SessionConfigChangedAction) isStateAction() {} -func (*SessionMetaChangedAction) isStateAction() {} -func (*SessionToolCallContentChangedAction) isStateAction() {} -func (*ChangesetStatusChangedAction) isStateAction() {} -func (*ChangesetFileSetAction) isStateAction() {} -func (*ChangesetFileRemovedAction) isStateAction() {} -func (*ChangesetOperationsChangedAction) isStateAction() {} -func (*ChangesetOperationStatusChangedAction) isStateAction() {} -func (*ChangesetClearedAction) isStateAction() {} -func (*RootTerminalsChangedAction) isStateAction() {} -func (*TerminalDataAction) isStateAction() {} -func (*TerminalInputAction) isStateAction() {} -func (*TerminalResizedAction) isStateAction() {} -func (*TerminalClaimedAction) isStateAction() {} -func (*TerminalTitleChangedAction) isStateAction() {} -func (*TerminalCwdChangedAction) isStateAction() {} -func (*TerminalExitedAction) isStateAction() {} -func (*TerminalClearedAction) isStateAction() {} +func (*RootAgentsChangedAction) isStateAction() {} +func (*RootActiveSessionsChangedAction) isStateAction() {} +func (*RootConfigChangedAction) isStateAction() {} +func (*SessionReadyAction) isStateAction() {} +func (*SessionCreationFailedAction) isStateAction() {} +func (*SessionTurnStartedAction) isStateAction() {} +func (*SessionDeltaAction) isStateAction() {} +func (*SessionResponsePartAction) isStateAction() {} +func (*SessionToolCallStartAction) isStateAction() {} +func (*SessionToolCallDeltaAction) isStateAction() {} +func (*SessionToolCallReadyAction) isStateAction() {} +func (*SessionToolCallConfirmedAction) isStateAction() {} +func (*SessionToolCallCompleteAction) isStateAction() {} +func (*SessionToolCallResultConfirmedAction) isStateAction() {} +func (*SessionTurnCompleteAction) isStateAction() {} +func (*SessionTurnCancelledAction) isStateAction() {} +func (*SessionErrorAction) isStateAction() {} +func (*SessionTitleChangedAction) isStateAction() {} +func (*SessionUsageAction) isStateAction() {} +func (*SessionReasoningAction) isStateAction() {} +func (*SessionModelChangedAction) isStateAction() {} +func (*SessionAgentChangedAction) isStateAction() {} +func (*SessionIsReadChangedAction) isStateAction() {} +func (*SessionIsArchivedChangedAction) isStateAction() {} +func (*SessionActivityChangedAction) isStateAction() {} +func (*SessionChangesetsChangedAction) isStateAction() {} +func (*SessionServerToolsChangedAction) isStateAction() {} +func (*SessionActiveClientChangedAction) isStateAction() {} +func (*SessionActiveClientToolsChangedAction) isStateAction() {} +func (*SessionPendingMessageSetAction) isStateAction() {} +func (*SessionPendingMessageRemovedAction) isStateAction() {} +func (*SessionQueuedMessagesReorderedAction) isStateAction() {} +func (*SessionInputRequestedAction) isStateAction() {} +func (*SessionInputAnswerChangedAction) isStateAction() {} +func (*SessionInputCompletedAction) isStateAction() {} +func (*SessionCustomizationsChangedAction) isStateAction() {} +func (*SessionCustomizationToggledAction) isStateAction() {} +func (*SessionCustomizationUpdatedAction) isStateAction() {} +func (*SessionCustomizationRemovedAction) isStateAction() {} +func (*SessionTruncatedAction) isStateAction() {} +func (*SessionConfigChangedAction) isStateAction() {} +func (*SessionMetaChangedAction) isStateAction() {} +func (*SessionToolCallContentChangedAction) isStateAction() {} +func (*ChangesetStatusChangedAction) isStateAction() {} +func (*ChangesetFileSetAction) isStateAction() {} +func (*ChangesetFileRemovedAction) isStateAction() {} +func (*ChangesetOperationsChangedAction) isStateAction() {} +func (*ChangesetOperationStatusChangedAction) isStateAction() {} +func (*ChangesetClearedAction) isStateAction() {} +func (*CommentsThreadSetAction) isStateAction() {} +func (*CommentsThreadRemovedAction) isStateAction() {} +func (*CommentsCommentSetAction) isStateAction() {} +func (*CommentsCommentRemovedAction) isStateAction() {} +func (*CommentsClearedAction) isStateAction() {} +func (*RootTerminalsChangedAction) isStateAction() {} +func (*TerminalDataAction) isStateAction() {} +func (*TerminalInputAction) isStateAction() {} +func (*TerminalResizedAction) isStateAction() {} +func (*TerminalClaimedAction) isStateAction() {} +func (*TerminalTitleChangedAction) isStateAction() {} +func (*TerminalCwdChangedAction) isStateAction() {} +func (*TerminalExitedAction) isStateAction() {} +func (*TerminalClearedAction) isStateAction() {} func (*TerminalCommandDetectionAvailableAction) isStateAction() {} -func (*TerminalCommandExecutedAction) isStateAction() {} -func (*TerminalCommandFinishedAction) isStateAction() {} -func (*ResourceWatchChangedAction) isStateAction() {} +func (*TerminalCommandExecutedAction) isStateAction() {} +func (*TerminalCommandFinishedAction) isStateAction() {} +func (*ResourceWatchChangedAction) isStateAction() {} // StateActionUnknown carries an unrecognized StateAction variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type StateActionUnknown struct { @@ -1287,6 +1360,36 @@ func (u *StateAction) UnmarshalJSON(data []byte) error { return err } u.Value = &value + case "comments/threadSet": + var value CommentsThreadSetAction + if err := json.Unmarshal(data, &value); err != nil { + return err + } + u.Value = &value + case "comments/threadRemoved": + var value CommentsThreadRemovedAction + if err := json.Unmarshal(data, &value); err != nil { + return err + } + u.Value = &value + case "comments/commentSet": + var value CommentsCommentSetAction + if err := json.Unmarshal(data, &value); err != nil { + return err + } + u.Value = &value + case "comments/commentRemoved": + var value CommentsCommentRemovedAction + if err := json.Unmarshal(data, &value); err != nil { + return err + } + u.Value = &value + case "comments/cleared": + var value CommentsClearedAction + if err := json.Unmarshal(data, &value); err != nil { + return err + } + u.Value = &value case "root/terminalsChanged": var value RootTerminalsChangedAction if err := json.Unmarshal(data, &value); err != nil { diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go index 49a49c6c..8cfd8677 100644 --- a/clients/go/ahptypes/commands.generated.go +++ b/clients/go/ahptypes/commands.generated.go @@ -19,7 +19,7 @@ var _ = json.RawMessage(nil) type ReconnectResultType string const ( - ReconnectResultTypeReplay ReconnectResultType = "replay" + ReconnectResultTypeReplay ReconnectResultType = "replay" ReconnectResultTypeSnapshot ReconnectResultType = "snapshot" ) @@ -28,7 +28,7 @@ type ContentEncoding string const ( ContentEncodingBase64 ContentEncoding = "base64" - ContentEncodingUtf8 ContentEncoding = "utf-8" + ContentEncodingUtf8 ContentEncoding = "utf-8" ) // The kind of completion items being requested. @@ -45,36 +45,36 @@ const ( type ResourceType string const ( - ResourceTypeFile ResourceType = "file" + ResourceTypeFile ResourceType = "file" ResourceTypeDirectory ResourceType = "directory" - ResourceTypeSymlink ResourceType = "symlink" + ResourceTypeSymlink ResourceType = "symlink" ) // How {@link ResourceWriteParams.data} is placed within the target file. // // Each mode interprets {@link ResourceWriteParams.position} differently: // -// - `truncate` (default): rooted at the **start** of the file. The file is -// truncated at `position` (0 by default) and `data` is written from that -// offset, so the resulting file is `existing[0..position] + data`. With -// `position` omitted this is a full overwrite. -// - `append`: rooted at the **end** of the file. `position` counts bytes -// backwards from EOF, so `position: 0` (the default) writes at EOF — -// POSIX append — and `position: 5` inserts `data` 5 bytes before the -// current EOF, shifting those trailing 5 bytes after the inserted region. -// The server MUST evaluate the effective EOF and write atomically with -// respect to other appenders so concurrent `append` writes do not -// clobber each other. -// - `insert`: rooted at the **start** of the file. `position` (0 by default) -// is the byte offset at which `data` is spliced in; bytes at or after -// `position` are shifted right by `data.length`. `insert` always grows -// the file — use `truncate` to overwrite bytes in place. +// - `truncate` (default): rooted at the **start** of the file. The file is +// truncated at `position` (0 by default) and `data` is written from that +// offset, so the resulting file is `existing[0..position] + data`. With +// `position` omitted this is a full overwrite. +// - `append`: rooted at the **end** of the file. `position` counts bytes +// backwards from EOF, so `position: 0` (the default) writes at EOF — +// POSIX append — and `position: 5` inserts `data` 5 bytes before the +// current EOF, shifting those trailing 5 bytes after the inserted region. +// The server MUST evaluate the effective EOF and write atomically with +// respect to other appenders so concurrent `append` writes do not +// clobber each other. +// - `insert`: rooted at the **start** of the file. `position` (0 by default) +// is the byte offset at which `data` is spliced in; bytes at or after +// `position` are shifted right by `data.length`. `insert` always grows +// the file — use `truncate` to overwrite bytes in place. type ResourceWriteMode string const ( ResourceWriteModeTruncate ResourceWriteMode = "truncate" - ResourceWriteModeAppend ResourceWriteMode = "append" - ResourceWriteModeInsert ResourceWriteMode = "insert" + ResourceWriteModeAppend ResourceWriteMode = "append" + ResourceWriteModeInsert ResourceWriteMode = "insert" ) // ─── Command Payloads ───────────────────────────────────────────────── @@ -775,9 +775,9 @@ type CompletionsParams struct { // A single completion item returned by the `completions` command. // // When the user accepts an item, the client SHOULD: -// 1. Replace the range `[rangeStart, rangeEnd)` in the input with `insertText` -// (or insert `insertText` at the cursor when the range is omitted). -// 2. Associate the item's `attachment` with the resulting {@link Message}. +// 1. Replace the range `[rangeStart, rangeEnd)` in the input with `insertText` +// (or insert `insertText` at the cursor when the range is omitted). +// 2. Associate the item's `attachment` with the resulting {@link Message}. type CompletionItem struct { // The text inserted into the input when this item is accepted. InsertText string `json:"insertText"` @@ -855,6 +855,117 @@ type ChangesetOperationFollowUp struct { External *bool `json:"external,omitempty"` } +// Create a new {@link CommentThread} anchored to a file range from a +// specific turn. +// +// The initial comment is required — the protocol forbids empty threads, +// so thread creation and first-comment creation are fused into one +// command. The server assigns both {@link CreateCommentThreadResult.threadId} +// and {@link CreateCommentThreadResult.commentId}, then broadcasts a +// {@link CommentsThreadSetAction} on the channel. +type CreateCommentThreadParams struct { + // Channel URI this command targets. + Channel URI `json:"channel"` + // Turn whose file versions {@link resource} + {@link range} address. + TurnId string `json:"turnId"` + // Anchored file URI. + Resource URI `json:"resource"` + // Anchored range within {@link resource}. + Range TextRange `json:"range"` + // First comment in the thread. The server assigns its {@link Comment.id}. + Comment NewComment `json:"comment"` +} + +// Result of {@link CreateCommentThreadParams | `createCommentThread`}. +type CreateCommentThreadResult struct { + // Server-assigned {@link CommentThread.id}. + ThreadId string `json:"threadId"` + // Server-assigned {@link Comment.id} of the initial comment. + CommentId string `json:"commentId"` +} + +// Re-anchor an existing {@link CommentThread} — typically used to re-pin +// a thread to a different range or a newer turn after an edit. Comments +// themselves are not modified by this command; use +// {@link AddCommentParams | `addComment`}, +// {@link EditCommentParams | `editComment`}, or +// {@link DeleteCommentParams | `deleteComment`} for that. +// +// Omitted optional fields preserve their current value. The server +// echoes the resulting thread state as a {@link CommentsThreadSetAction}. +type UpdateCommentThreadParams struct { + // Channel URI this command targets. + Channel URI `json:"channel"` + // The {@link CommentThread.id} to update. + ThreadId string `json:"threadId"` + // New {@link CommentThread.turnId}, if changing. + TurnId *string `json:"turnId,omitempty"` + // New anchored file URI, if changing. + Resource *URI `json:"resource,omitempty"` + // New anchored range, if changing. + Range *TextRange `json:"range,omitempty"` +} + +// Delete an entire comment thread (and every comment it contains). The +// server echoes a {@link CommentsThreadRemovedAction} on the channel. +type DeleteCommentThreadParams struct { + // Channel URI this command targets. + Channel URI `json:"channel"` + // The {@link CommentThread.id} to delete. + ThreadId string `json:"threadId"` +} + +// Append a new {@link Comment} to an existing thread. The server assigns +// the resulting {@link Comment.id} and echoes a +// {@link CommentsCommentSetAction}. +type AddCommentParams struct { + // Channel URI this command targets. + Channel URI `json:"channel"` + // Thread that receives the new comment. + ThreadId string `json:"threadId"` + // Comment payload — the server assigns the id. + Comment NewComment `json:"comment"` +} + +// Result of {@link AddCommentParams | `addComment`}. +type AddCommentResult struct { + // Server-assigned {@link Comment.id} of the new comment. + CommentId string `json:"commentId"` +} + +// Edit the body of an existing comment in place. The server echoes a +// {@link CommentsCommentSetAction} carrying the updated comment. +// +// Only the body is mutable through this command; to change +// {@link Comment.source} or {@link Comment._meta} delete and re-create +// the comment. +type EditCommentParams struct { + // Channel URI this command targets. + Channel URI `json:"channel"` + // Enclosing thread. + ThreadId string `json:"threadId"` + // {@link Comment.id} to edit. + CommentId string `json:"commentId"` + // New comment body. + Text string `json:"text"` +} + +// Remove a single comment from a thread. +// +// If the removal would leave the thread empty (i.e. the targeted comment +// is the only one remaining), the server collapses the thread instead +// — it dispatches a {@link CommentsThreadRemovedAction} and the thread +// disappears from {@link CommentsState.threads}. Otherwise the server +// echoes a {@link CommentsCommentRemovedAction}. +type DeleteCommentParams struct { + // Channel URI this command targets. + Channel URI `json:"channel"` + // Enclosing thread. + ThreadId string `json:"threadId"` + // {@link Comment.id} to remove. + CommentId string `json:"commentId"` +} + // ─── ReconnectResult Union ──────────────────────────────────────────── // ReconnectResult is the result of the `reconnect` command. @@ -866,7 +977,7 @@ type ReconnectResult struct { // concrete variant of ReconnectResult. type isReconnectResult interface{ isReconnectResult() } -func (*ReconnectReplayResult) isReconnectResult() {} +func (*ReconnectReplayResult) isReconnectResult() {} func (*ReconnectSnapshotResult) isReconnectResult() {} // UnmarshalJSON decodes the variant indicated by the "type" discriminator. @@ -924,10 +1035,10 @@ func (*ChangesetOperationResourceTarget) isChangesetOperationTarget() {} // ChangesetOperationRangeTarget targets a range within a resource. type ChangesetOperationRangeTarget struct { - Kind string `json:"kind"` - Resource URI `json:"resource"` - Side *string `json:"side,omitempty"` - Range ChangesetOperationTargetRange `json:"range"` + Kind string `json:"kind"` + Resource URI `json:"resource"` + Side *string `json:"side,omitempty"` + Range ChangesetOperationTargetRange `json:"range"` } func (*ChangesetOperationRangeTarget) isChangesetOperationTarget() {} diff --git a/clients/go/ahptypes/errors.generated.go b/clients/go/ahptypes/errors.generated.go index 3b3e8f82..b5a22f4b 100644 --- a/clients/go/ahptypes/errors.generated.go +++ b/clients/go/ahptypes/errors.generated.go @@ -26,16 +26,16 @@ const ( // AHP application-specific error codes (above the JSON-RPC reserved // range). const ( - ErrorCodeSessionNotFound int32 = -32001 - ErrorCodeProviderNotFound int32 = -32002 - ErrorCodeSessionAlreadyExists int32 = -32003 - ErrorCodeTurnInProgress int32 = -32004 - ErrorCodeUnsupportedProtocolVersion int32 = -32005 - ErrorCodeContentNotFound int32 = -32006 - ErrorCodeAuthRequired int32 = -32007 - ErrorCodeNotFound int32 = -32008 - ErrorCodePermissionDenied int32 = -32009 - ErrorCodeAlreadyExists int32 = -32010 + ErrorCodeSessionNotFound int32 = -32001 + ErrorCodeProviderNotFound int32 = -32002 + ErrorCodeSessionAlreadyExists int32 = -32003 + ErrorCodeTurnInProgress int32 = -32004 + ErrorCodeUnsupportedProtocolVersion int32 = -32005 + ErrorCodeContentNotFound int32 = -32006 + ErrorCodeAuthRequired int32 = -32007 + ErrorCodeNotFound int32 = -32008 + ErrorCodePermissionDenied int32 = -32009 + ErrorCodeAlreadyExists int32 = -32010 ) // AhpErrorCode is the type alias used by AHP application error codes. diff --git a/clients/go/ahptypes/notifications.generated.go b/clients/go/ahptypes/notifications.generated.go index 3d37cdc7..7457347b 100644 --- a/clients/go/ahptypes/notifications.generated.go +++ b/clients/go/ahptypes/notifications.generated.go @@ -59,20 +59,20 @@ type SessionRemovedParams struct { // // Semantics: // -// - Only fields present in `changes` have new values; omitted fields are -// unchanged on the client's cached summary. -// - Identity fields (`resource`, `provider`, `createdAt`) never change and -// are not carried. -// - Like all protocol notifications, this is ephemeral: it is **not** -// replayed on reconnect. On reconnect, clients should re-fetch the full -// catalog via `listSessions()` as usual. -// - The server SHOULD emit this notification whenever any mutable field on -// {@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) at their discretion. -// - Clients that have no cached entry for `session` MAY ignore the -// notification; it is not a substitute for `root/sessionAdded`. +// - Only fields present in `changes` have new values; omitted fields are +// unchanged on the client's cached summary. +// - Identity fields (`resource`, `provider`, `createdAt`) never change and +// are not carried. +// - Like all protocol notifications, this is ephemeral: it is **not** +// replayed on reconnect. On reconnect, clients should re-fetch the full +// catalog via `listSessions()` as usual. +// - The server SHOULD emit this notification whenever any mutable field on +// {@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) 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 { // Channel URI this notification belongs to (the root channel) Channel URI `json:"channel"` @@ -189,4 +189,9 @@ type PartialSessionSummary struct { // session's footprint (e.g., for list rendering) without requiring the // client to subscribe to a changeset. Changes *ChangesSummary `json:"changes,omitempty"` + // Lightweight summary of this session's inline comments channel + // (`ahp-session://comments`). Surfaced so badge UI can render + // thread / comment counts without subscribing. Absent when the session + // does not expose a comments channel. + Comments *CommentsSummary `json:"comments,omitempty"` } diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 9e8a8354..ca8c7957 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -19,8 +19,8 @@ var _ = json.RawMessage(nil) type PolicyState string const ( - PolicyStateEnabled PolicyState = "enabled" - PolicyStateDisabled PolicyState = "disabled" + PolicyStateEnabled PolicyState = "enabled" + PolicyStateDisabled PolicyState = "disabled" PolicyStateUnconfigured PolicyState = "unconfigured" ) @@ -38,8 +38,8 @@ const ( type SessionLifecycle string const ( - SessionLifecycleCreating SessionLifecycle = "creating" - SessionLifecycleReady SessionLifecycle = "ready" + SessionLifecycleCreating SessionLifecycle = "creating" + SessionLifecycleReady SessionLifecycle = "ready" SessionLifecycleCreationFailed SessionLifecycle = "creationFailed" ) @@ -75,19 +75,19 @@ func (s SessionStatus) Or(other SessionStatus) SessionStatus { return s | other type SessionInputAnswerState string const ( - SessionInputAnswerStateDraft SessionInputAnswerState = "draft" + SessionInputAnswerStateDraft SessionInputAnswerState = "draft" SessionInputAnswerStateSubmitted SessionInputAnswerState = "submitted" - SessionInputAnswerStateSkipped SessionInputAnswerState = "skipped" + SessionInputAnswerStateSkipped SessionInputAnswerState = "skipped" ) // Answer value kind. type SessionInputAnswerValueKind string const ( - SessionInputAnswerValueKindText SessionInputAnswerValueKind = "text" - SessionInputAnswerValueKindNumber SessionInputAnswerValueKind = "number" - SessionInputAnswerValueKindBoolean SessionInputAnswerValueKind = "boolean" - SessionInputAnswerValueKindSelected SessionInputAnswerValueKind = "selected" + SessionInputAnswerValueKindText SessionInputAnswerValueKind = "text" + SessionInputAnswerValueKindNumber SessionInputAnswerValueKind = "number" + SessionInputAnswerValueKindBoolean SessionInputAnswerValueKind = "boolean" + SessionInputAnswerValueKindSelected SessionInputAnswerValueKind = "selected" SessionInputAnswerValueKindSelectedMany SessionInputAnswerValueKind = "selected-many" ) @@ -95,30 +95,30 @@ const ( type SessionInputQuestionKind string const ( - SessionInputQuestionKindText SessionInputQuestionKind = "text" - SessionInputQuestionKindNumber SessionInputQuestionKind = "number" - SessionInputQuestionKindInteger SessionInputQuestionKind = "integer" - SessionInputQuestionKindBoolean SessionInputQuestionKind = "boolean" + SessionInputQuestionKindText SessionInputQuestionKind = "text" + SessionInputQuestionKindNumber SessionInputQuestionKind = "number" + SessionInputQuestionKindInteger SessionInputQuestionKind = "integer" + SessionInputQuestionKindBoolean SessionInputQuestionKind = "boolean" SessionInputQuestionKindSingleSelect SessionInputQuestionKind = "single-select" - SessionInputQuestionKindMultiSelect SessionInputQuestionKind = "multi-select" + SessionInputQuestionKindMultiSelect SessionInputQuestionKind = "multi-select" ) // How a client completed an input request. type SessionInputResponseKind string const ( - SessionInputResponseKindAccept SessionInputResponseKind = "accept" + SessionInputResponseKindAccept SessionInputResponseKind = "accept" SessionInputResponseKindDecline SessionInputResponseKind = "decline" - SessionInputResponseKindCancel SessionInputResponseKind = "cancel" + SessionInputResponseKindCancel SessionInputResponseKind = "cancel" ) // How a turn ended. type TurnState string const ( - TurnStateComplete TurnState = "complete" + TurnStateComplete TurnState = "complete" TurnStateCancelled TurnState = "cancelled" - TurnStateError TurnState = "error" + TurnStateError TurnState = "error" ) // Discriminant for {@link MessageAttachment} variants. @@ -137,10 +137,10 @@ const ( type ResponsePartKind string const ( - ResponsePartKindMarkdown ResponsePartKind = "markdown" - ResponsePartKindContentRef ResponsePartKind = "contentRef" - ResponsePartKindToolCall ResponsePartKind = "toolCall" - ResponsePartKindReasoning ResponsePartKind = "reasoning" + ResponsePartKindMarkdown ResponsePartKind = "markdown" + ResponsePartKindContentRef ResponsePartKind = "contentRef" + ResponsePartKindToolCall ResponsePartKind = "toolCall" + ResponsePartKindReasoning ResponsePartKind = "reasoning" ResponsePartKindSystemNotification ResponsePartKind = "systemNotification" ) @@ -148,12 +148,12 @@ const ( type ToolCallStatus string const ( - ToolCallStatusStreaming ToolCallStatus = "streaming" - ToolCallStatusPendingConfirmation ToolCallStatus = "pending-confirmation" - ToolCallStatusRunning ToolCallStatus = "running" + ToolCallStatusStreaming ToolCallStatus = "streaming" + ToolCallStatusPendingConfirmation ToolCallStatus = "pending-confirmation" + ToolCallStatusRunning ToolCallStatus = "running" ToolCallStatusPendingResultConfirmation ToolCallStatus = "pending-result-confirmation" - ToolCallStatusCompleted ToolCallStatus = "completed" - ToolCallStatusCancelled ToolCallStatus = "cancelled" + ToolCallStatusCompleted ToolCallStatus = "completed" + ToolCallStatusCancelled ToolCallStatus = "cancelled" ) // How a tool call was confirmed for execution. @@ -164,17 +164,17 @@ const ( type ToolCallConfirmationReason string const ( - ToolCallConfirmationReasonNotNeeded ToolCallConfirmationReason = "not-needed" + ToolCallConfirmationReasonNotNeeded ToolCallConfirmationReason = "not-needed" ToolCallConfirmationReasonUserAction ToolCallConfirmationReason = "user-action" - ToolCallConfirmationReasonSetting ToolCallConfirmationReason = "setting" + ToolCallConfirmationReasonSetting ToolCallConfirmationReason = "setting" ) // Why a tool call was cancelled. type ToolCallCancellationReason string const ( - ToolCallCancellationReasonDenied ToolCallCancellationReason = "denied" - ToolCallCancellationReasonSkipped ToolCallCancellationReason = "skipped" + ToolCallCancellationReasonDenied ToolCallCancellationReason = "denied" + ToolCallCancellationReasonSkipped ToolCallCancellationReason = "skipped" ToolCallCancellationReasonResultDenied ToolCallCancellationReason = "result-denied" ) @@ -183,19 +183,19 @@ type ConfirmationOptionKind string const ( ConfirmationOptionKindApprove ConfirmationOptionKind = "approve" - ConfirmationOptionKindDeny ConfirmationOptionKind = "deny" + ConfirmationOptionKindDeny ConfirmationOptionKind = "deny" ) // Discriminant for tool result content types. type ToolResultContentType string const ( - ToolResultContentTypeText ToolResultContentType = "text" + ToolResultContentTypeText ToolResultContentType = "text" ToolResultContentTypeEmbeddedResource ToolResultContentType = "embeddedResource" - ToolResultContentTypeResource ToolResultContentType = "resource" - ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" - ToolResultContentTypeTerminal ToolResultContentType = "terminal" - ToolResultContentTypeSubagent ToolResultContentType = "subagent" + ToolResultContentTypeResource ToolResultContentType = "resource" + ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" + ToolResultContentTypeTerminal ToolResultContentType = "terminal" + ToolResultContentTypeSubagent ToolResultContentType = "subagent" ) // Discriminant for the kind of customization. @@ -208,13 +208,13 @@ const ( type CustomizationType string const ( - CustomizationTypePlugin CustomizationType = "plugin" + CustomizationTypePlugin CustomizationType = "plugin" CustomizationTypeDirectory CustomizationType = "directory" - CustomizationTypeAgent CustomizationType = "agent" - CustomizationTypeSkill CustomizationType = "skill" - CustomizationTypePrompt CustomizationType = "prompt" - CustomizationTypeRule CustomizationType = "rule" - CustomizationTypeHook CustomizationType = "hook" + CustomizationTypeAgent CustomizationType = "agent" + CustomizationTypeSkill CustomizationType = "skill" + CustomizationTypePrompt CustomizationType = "prompt" + CustomizationTypeRule CustomizationType = "rule" + CustomizationTypeHook CustomizationType = "hook" CustomizationTypeMcpServer CustomizationType = "mcpServer" ) @@ -222,17 +222,17 @@ const ( type CustomizationLoadStatus string const ( - CustomizationLoadStatusLoading CustomizationLoadStatus = "loading" - CustomizationLoadStatusLoaded CustomizationLoadStatus = "loaded" + CustomizationLoadStatusLoading CustomizationLoadStatus = "loading" + CustomizationLoadStatusLoaded CustomizationLoadStatus = "loaded" CustomizationLoadStatusDegraded CustomizationLoadStatus = "degraded" - CustomizationLoadStatusError CustomizationLoadStatus = "error" + CustomizationLoadStatusError CustomizationLoadStatus = "error" ) // Discriminant for terminal claim kinds. type TerminalClaimKind string const ( - TerminalClaimKindClient TerminalClaimKind = "client" + TerminalClaimKindClient TerminalClaimKind = "client" TerminalClaimKindSession TerminalClaimKind = "session" ) @@ -284,7 +284,7 @@ const ( type ResourceChangeType string const ( - ResourceChangeTypeAdded ResourceChangeType = "added" + ResourceChangeTypeAdded ResourceChangeType = "added" ResourceChangeTypeUpdated ResourceChangeType = "updated" ResourceChangeTypeDeleted ResourceChangeType = "deleted" ) @@ -620,6 +620,11 @@ type SessionSummary struct { // session's footprint (e.g., for list rendering) without requiring the // client to subscribe to a changeset. Changes *ChangesSummary `json:"changes,omitempty"` + // Lightweight summary of this session's inline comments channel + // (`ahp-session://comments`). Surfaced so badge UI can render + // thread / comment counts without subscribing. Absent when the session + // does not expose a comments channel. + Comments *CommentsSummary `json:"comments,omitempty"` } // Aggregate counts describing the file changes associated with a session. @@ -769,30 +774,30 @@ type SessionInputOption struct { // Value captured for one answer. type SessionInputTextAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` } type SessionInputNumberAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value float64 `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value float64 `json:"value"` } type SessionInputBooleanAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value bool `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value bool `json:"value"` } type SessionInputSelectedAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` // Free-form text entered instead of selecting an option FreeformValues []string `json:"freeformValues,omitempty"` } type SessionInputSelectedManyAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value []string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value []string `json:"value"` // Free-form text entered in addition to selected options FreeformValues []string `json:"freeformValues,omitempty"` } @@ -820,8 +825,8 @@ type SessionInputTextQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Format hint for text questions, such as `email`, `uri`, `date`, or `date-time` Format *string `json:"format,omitempty"` // Minimum string length @@ -841,8 +846,8 @@ type SessionInputNumberQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Minimum value Min *float64 `json:"min,omitempty"` // Maximum value @@ -860,8 +865,8 @@ type SessionInputBooleanQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Default boolean value DefaultValue *bool `json:"defaultValue,omitempty"` } @@ -875,8 +880,8 @@ type SessionInputSingleSelectQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Options the user may select from Options []SessionInputOption `json:"options"` // Whether the user may enter text instead of selecting an option @@ -892,8 +897,8 @@ type SessionInputMultiSelectQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Options the user may select from Options []SessionInputOption `json:"options"` // Whether the user may enter text in addition to selecting options @@ -1190,8 +1195,8 @@ type ToolCallStreamingState struct { // For example, a `ptyTerminal` key with `{ input: string; output: string }` // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). - Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Status ToolCallStatus `json:"status"` + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Status ToolCallStatus `json:"status"` // Partial parameters accumulated so far PartialInput *string `json:"partialInput,omitempty"` // Progress message shown while parameters are streaming @@ -1223,8 +1228,8 @@ type ToolCallPendingConfirmationState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // Short title for the confirmation prompt (e.g. `"Run in terminal"`, `"Write file"`) ConfirmationTitle *StringOrMarkdown `json:"confirmationTitle,omitempty"` // File edits that this tool call will perform, for preview before confirmation @@ -1262,8 +1267,8 @@ type ToolCallRunningState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1313,8 +1318,8 @@ type ToolCallPendingResultConfirmationState struct { // This mirrors the `structuredContent` field of MCP `CallToolResult`. StructuredContent map[string]json.RawMessage `json:"structuredContent,omitempty"` // Error details if the tool failed - Error *json.RawMessage `json:"error,omitempty"` - Status ToolCallStatus `json:"status"` + Error *json.RawMessage `json:"error,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1359,8 +1364,8 @@ type ToolCallCompletedState struct { // This mirrors the `structuredContent` field of MCP `CallToolResult`. StructuredContent map[string]json.RawMessage `json:"structuredContent,omitempty"` // Error details if the tool failed - Error *json.RawMessage `json:"error,omitempty"` - Status ToolCallStatus `json:"status"` + Error *json.RawMessage `json:"error,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1391,8 +1396,8 @@ type ToolCallCancelledState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // Why the tool was cancelled Reason ToolCallCancellationReason `json:"reason"` // Optional message explaining the cancellation @@ -1474,8 +1479,8 @@ type ToolResultResourceContent struct { // Approximate size in bytes SizeHint *int64 `json:"sizeHint,omitempty"` // Content MIME type - ContentType *string `json:"contentType,omitempty"` - Type ToolResultContentType `json:"type"` + ContentType *string `json:"contentType,omitempty"` + Type ToolResultContentType `json:"type"` } // Describes a file modification performed by a tool. @@ -1485,7 +1490,7 @@ type ToolResultFileEditContent struct { // The file state after the edit. Absent for file deletions. After *json.RawMessage `json:"after,omitempty"` // Optional diff display metadata - Diff *json.RawMessage `json:"diff,omitempty"` + Diff *json.RawMessage `json:"diff,omitempty"` Type ToolResultContentType `json:"type"` } @@ -1578,7 +1583,7 @@ type PluginCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` } // A {@link PluginCustomization} as published by a client. Extends the @@ -1626,7 +1631,7 @@ type ClientPluginCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` // Opaque version token used by the host to detect changes. Nonce *string `json:"nonce,omitempty"` } @@ -1676,7 +1681,7 @@ type DirectoryCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` // Which child customization type this directory holds. Contents CustomizationType `json:"contents"` // Whether clients may write into this directory. @@ -1709,8 +1714,8 @@ type AgentCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description of what the agent specializes in and when to // invoke it. Sourced from the agent file's frontmatter `description`. Description *string `json:"description,omitempty"` @@ -1747,8 +1752,8 @@ type SkillCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description used for help text and auto-invocation matching. // Sourced from the skill's frontmatter `description`. Description *string `json:"description,omitempty"` @@ -1780,8 +1785,8 @@ type PromptCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description of what the prompt does. Description *string `json:"description,omitempty"` } @@ -1816,8 +1821,8 @@ type RuleCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Description of what the rule enforces. Description *string `json:"description,omitempty"` // When `true`, the rule is always active (subject to `globs` if any). @@ -1851,8 +1856,8 @@ type HookCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` } // An MCP manifest contributed by a plugin or directory. @@ -1882,8 +1887,8 @@ type McpServerCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` } // Describes a file modification with before/after state and diff metadata. @@ -2129,6 +2134,82 @@ type ChangesetOperation struct { Error *ErrorInfo `json:"error,omitempty"` } +// Lightweight per-session summary of the comments channel, surfaced on +// {@link SessionSummary.comments} so badge UI can render thread / comment +// counts without subscribing to the channel itself. +type CommentsSummary struct { + // The subscribable comments channel URI for the owning session + // (typically `ahp-session://comments`). Surfaced explicitly even + // though it is derivable from the session URI so badge UI does not need + // to know the derivation rule. + Resource URI `json:"resource"` + // Total number of {@link CommentThread} entries in the channel. + ThreadCount int64 `json:"threadCount"` + // Total number of {@link Comment} entries across every thread. + CommentCount int64 `json:"commentCount"` +} + +// Full state for a session's comments channel, returned when a client +// subscribes to an `ahp-session://comments` URI. +type CommentsState struct { + // Comment threads in this channel, keyed by {@link CommentThread.id}. + Threads []CommentThread `json:"threads"` +} + +// A conversation anchored to a specific range in a specific file produced +// by a specific turn. +// +// {@link turnId} anchors the thread to the file versions that turn +// produced, so a later turn that rewrites the same file does not silently +// invalidate the comment's anchor — clients can resolve {@link resource} +// and {@link range} against the turn's changeset. +// +// Every thread MUST contain at least one {@link Comment}. The server +// enforces this invariant: {@link CreateCommentThreadParams | +// `createCommentThread`} requires an initial comment, and deleting the +// last remaining comment collapses the thread into a +// {@link CommentsThreadRemovedAction} rather than leaving an empty thread +// behind. +type CommentThread struct { + // Stable identifier within the comments channel. Server-assigned. + Id string `json:"id"` + // Turn that produced the file versions this thread is anchored to. + // Matches a {@link Turn.id} on the owning session. + TurnId string `json:"turnId"` + // The file the thread is anchored to. + Resource URI `json:"resource"` + // Range within {@link resource} the thread is anchored to. + Range TextRange `json:"range"` + // Comments in this thread, in dispatch order (oldest first). MUST + // contain at least one entry. + Comments []Comment `json:"comments"` + // Server-defined opaque metadata, surfaced to tooling but not + // interpreted by the protocol. + Meta map[string]json.RawMessage `json:"_meta,omitempty"` +} + +// A single comment within a {@link CommentThread}. +type Comment struct { + // Stable identifier within the enclosing thread. Server-assigned. + Id string `json:"id"` + // Comment body. Rendered as plain text unless the client opts into Markdown. + Text string `json:"text"` + // Server-defined opaque metadata, surfaced to tooling but not + // interpreted by the protocol. + Meta map[string]json.RawMessage `json:"_meta,omitempty"` +} + +// Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`} +// and {@link AddCommentParams | `addComment`}. The server assigns the +// resulting {@link Comment.id}. +type NewComment struct { + // Comment body. + Text string `json:"text"` + // Server-defined opaque metadata, forwarded onto the resulting + // {@link Comment._meta}. + Meta map[string]json.RawMessage `json:"_meta,omitempty"` +} + // OTLP telemetry channels the agent host emits. // // Each field, when present, is either a literal channel URI or an @@ -2220,10 +2301,10 @@ type ResponsePart struct { // concrete variant of ResponsePart. type isResponsePart interface{ isResponsePart() } -func (*MarkdownResponsePart) isResponsePart() {} -func (*ResourceResponsePart) isResponsePart() {} -func (*ToolCallResponsePart) isResponsePart() {} -func (*ReasoningResponsePart) isResponsePart() {} +func (*MarkdownResponsePart) isResponsePart() {} +func (*ResourceResponsePart) isResponsePart() {} +func (*ToolCallResponsePart) isResponsePart() {} +func (*ReasoningResponsePart) isResponsePart() {} func (*SystemNotificationResponsePart) isResponsePart() {} // ResponsePartUnknown carries an unrecognized ResponsePart variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2301,12 +2382,12 @@ type ToolCallState struct { // concrete variant of ToolCallState. type isToolCallState interface{ isToolCallState() } -func (*ToolCallStreamingState) isToolCallState() {} -func (*ToolCallPendingConfirmationState) isToolCallState() {} -func (*ToolCallRunningState) isToolCallState() {} +func (*ToolCallStreamingState) isToolCallState() {} +func (*ToolCallPendingConfirmationState) isToolCallState() {} +func (*ToolCallRunningState) isToolCallState() {} func (*ToolCallPendingResultConfirmationState) isToolCallState() {} -func (*ToolCallCompletedState) isToolCallState() {} -func (*ToolCallCancelledState) isToolCallState() {} +func (*ToolCallCompletedState) isToolCallState() {} +func (*ToolCallCancelledState) isToolCallState() {} // ToolCallStateUnknown carries an unrecognized ToolCallState variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type ToolCallStateUnknown struct { @@ -2389,7 +2470,7 @@ type TerminalClaim struct { // concrete variant of TerminalClaim. type isTerminalClaim interface{ isTerminalClaim() } -func (*TerminalClientClaim) isTerminalClaim() {} +func (*TerminalClientClaim) isTerminalClaim() {} func (*TerminalSessionClaim) isTerminalClaim() {} // TerminalClaimUnknown carries an unrecognized TerminalClaim variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2450,7 +2531,7 @@ type TerminalContentPart struct { type isTerminalContentPart interface{ isTerminalContentPart() } func (*TerminalUnclassifiedPart) isTerminalContentPart() {} -func (*TerminalCommandPart) isTerminalContentPart() {} +func (*TerminalCommandPart) isTerminalContentPart() {} // TerminalContentPartUnknown carries an unrecognized TerminalContentPart variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type TerminalContentPartUnknown struct { @@ -2509,11 +2590,11 @@ type SessionInputQuestion struct { // concrete variant of SessionInputQuestion. type isSessionInputQuestion interface{ isSessionInputQuestion() } -func (*SessionInputTextQuestion) isSessionInputQuestion() {} -func (*SessionInputNumberQuestion) isSessionInputQuestion() {} -func (*SessionInputBooleanQuestion) isSessionInputQuestion() {} +func (*SessionInputTextQuestion) isSessionInputQuestion() {} +func (*SessionInputNumberQuestion) isSessionInputQuestion() {} +func (*SessionInputBooleanQuestion) isSessionInputQuestion() {} func (*SessionInputSingleSelectQuestion) isSessionInputQuestion() {} -func (*SessionInputMultiSelectQuestion) isSessionInputQuestion() {} +func (*SessionInputMultiSelectQuestion) isSessionInputQuestion() {} // SessionInputQuestionUnknown carries an unrecognized SessionInputQuestion variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type SessionInputQuestionUnknown struct { @@ -2596,10 +2677,10 @@ type SessionInputAnswerValue struct { // concrete variant of SessionInputAnswerValue. type isSessionInputAnswerValue interface{ isSessionInputAnswerValue() } -func (*SessionInputTextAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputNumberAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputBooleanAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputSelectedAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputTextAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputNumberAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputBooleanAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputSelectedAnswerValue) isSessionInputAnswerValue() {} func (*SessionInputSelectedManyAnswerValue) isSessionInputAnswerValue() {} // SessionInputAnswerValueUnknown carries an unrecognized SessionInputAnswerValue variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2678,7 +2759,7 @@ type SessionInputAnswer struct { type isSessionInputAnswer interface{ isSessionInputAnswer() } func (*SessionInputAnswered) isSessionInputAnswer() {} -func (*SessionInputSkipped) isSessionInputAnswer() {} +func (*SessionInputSkipped) isSessionInputAnswer() {} // SessionInputAnswerUnknown carries an unrecognized SessionInputAnswer variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type SessionInputAnswerUnknown struct { @@ -2743,12 +2824,12 @@ type ToolResultContent struct { // concrete variant of ToolResultContent. type isToolResultContent interface{ isToolResultContent() } -func (*ToolResultTextContent) isToolResultContent() {} +func (*ToolResultTextContent) isToolResultContent() {} func (*ToolResultEmbeddedResourceContent) isToolResultContent() {} -func (*ToolResultResourceContent) isToolResultContent() {} -func (*ToolResultFileEditContent) isToolResultContent() {} -func (*ToolResultTerminalContent) isToolResultContent() {} -func (*ToolResultSubagentContent) isToolResultContent() {} +func (*ToolResultResourceContent) isToolResultContent() {} +func (*ToolResultFileEditContent) isToolResultContent() {} +func (*ToolResultTerminalContent) isToolResultContent() {} +func (*ToolResultSubagentContent) isToolResultContent() {} // ToolResultContentUnknown carries an unrecognized ToolResultContent variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type ToolResultContentUnknown struct { @@ -2831,9 +2912,9 @@ type MessageAttachment struct { // concrete variant of MessageAttachment. type isMessageAttachment interface{ isMessageAttachment() } -func (*SimpleMessageAttachment) isMessageAttachment() {} +func (*SimpleMessageAttachment) isMessageAttachment() {} func (*MessageEmbeddedResourceAttachment) isMessageAttachment() {} -func (*MessageResourceAttachment) isMessageAttachment() {} +func (*MessageResourceAttachment) isMessageAttachment() {} // MessageAttachmentUnknown carries an unrecognized MessageAttachment variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type MessageAttachmentUnknown struct { @@ -2898,7 +2979,7 @@ type Customization struct { // concrete variant of Customization. type isCustomization interface{ isCustomization() } -func (*PluginCustomization) isCustomization() {} +func (*PluginCustomization) isCustomization() {} func (*DirectoryCustomization) isCustomization() {} // CustomizationUnknown carries an unrecognized Customization variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2958,11 +3039,11 @@ type ChildCustomization struct { // concrete variant of ChildCustomization. type isChildCustomization interface{ isChildCustomization() } -func (*AgentCustomization) isChildCustomization() {} -func (*SkillCustomization) isChildCustomization() {} -func (*PromptCustomization) isChildCustomization() {} -func (*RuleCustomization) isChildCustomization() {} -func (*HookCustomization) isChildCustomization() {} +func (*AgentCustomization) isChildCustomization() {} +func (*SkillCustomization) isChildCustomization() {} +func (*PromptCustomization) isChildCustomization() {} +func (*RuleCustomization) isChildCustomization() {} +func (*HookCustomization) isChildCustomization() {} func (*McpServerCustomization) isChildCustomization() {} // ChildCustomizationUnknown carries an unrecognized ChildCustomization variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -3046,10 +3127,10 @@ type CustomizationLoadState struct { // concrete variant of CustomizationLoadState. type isCustomizationLoadState interface{ isCustomizationLoadState() } -func (*CustomizationLoadingState) isCustomizationLoadState() {} -func (*CustomizationLoadedState) isCustomizationLoadState() {} +func (*CustomizationLoadingState) isCustomizationLoadState() {} +func (*CustomizationLoadedState) isCustomizationLoadState() {} func (*CustomizationDegradedState) isCustomizationLoadState() {} -func (*CustomizationErrorState) isCustomizationLoadState() {} +func (*CustomizationErrorState) isCustomizationLoadState() {} // CustomizationLoadStateUnknown carries an unrecognized CustomizationLoadState variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type CustomizationLoadStateUnknown struct { @@ -3112,14 +3193,16 @@ func (u CustomizationLoadState) MarshalJSON() ([]byte, error) { } // SnapshotState is the state payload of a snapshot — root, session, -// terminal, or changeset state. The active variant is chosen by which -// pointer field is non-nil; UnmarshalJSON probes for required fields in -// the canonical order (session → terminal → changeset → root). +// terminal, changeset, or comments state. The active variant is chosen +// by which pointer field is non-nil; UnmarshalJSON probes for required +// fields in the canonical order (session → terminal → changeset → +// comments → root). type SnapshotState struct { Root *RootState `json:"-"` Session *SessionState `json:"-"` Terminal *TerminalState `json:"-"` Changeset *ChangesetState `json:"-"` + Comments *CommentsState `json:"-"` } // MarshalJSON encodes whichever variant is currently populated. @@ -3131,6 +3214,8 @@ func (s SnapshotState) MarshalJSON() ([]byte, error) { return json.Marshal(s.Terminal) case s.Changeset != nil: return json.Marshal(s.Changeset) + case s.Comments != nil: + return json.Marshal(s.Comments) case s.Root != nil: return json.Marshal(s.Root) default: @@ -3165,6 +3250,12 @@ func (s *SnapshotState) UnmarshalJSON(data []byte) error { return err } s.Changeset = &v + case containsAll(probe, "threads"): + var v CommentsState + if err := json.Unmarshal(data, &v); err != nil { + return err + } + s.Comments = &v default: var v RootState if err := json.Unmarshal(data, &v); err != nil { diff --git a/clients/kotlin/CHANGELOG.md b/clients/kotlin/CHANGELOG.md index 365f76eb..530e26ea 100644 --- a/clients/kotlin/CHANGELOG.md +++ b/clients/kotlin/CHANGELOG.md @@ -22,6 +22,17 @@ versions (`*-SNAPSHOT`) are explicitly rejected by the publish pipeline; bump `idle → running → error` lifecycle of a changeset operation. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. +- New comments channel (`ahp-session://comments`): `CommentsState`, + `CommentThread`, `Comment`, `NewComment`, + `CommentsSummary`; the `commentsReducer` top-level function and + `CommentsReducer` object; the `comments/threadSet`, + `comments/threadRemoved`, `comments/commentSet`, `comments/commentRemoved`, + `comments/cleared` action variants; `createCommentThread`, + `updateCommentThread`, `deleteCommentThread`, `addComment`, + `editComment`, and `deleteComment` request factories on + `AhpClientRequests`; and `SnapshotState.Comments`. + `SessionSummary.comments` surfaces the per-session `CommentsSummary`. + `SessionSummary.comments` surfaces the per-session `CommentsSummary`. ### Changed 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 5c58e74d..ffa86123 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt @@ -143,9 +143,9 @@ import kotlinx.serialization.json.JsonElement * no mutation of [state] and no side effects. * * The companion top-level functions ([rootReducer], [sessionReducer], - * [terminalReducer], [changesetReducer], [resourceWatchReducer]) are the canonical implementations. + * [terminalReducer], [changesetReducer], [commentsReducer], [resourceWatchReducer]) are the canonical implementations. * The object instances on this interface ([RootReducer], [SessionReducer], - * [TerminalReducer], [ChangesetReducer]) wrap them for use as values where + * [TerminalReducer], [ChangesetReducer], [CommentsReducer]) wrap them for use as values where * an instance is needed. */ public fun interface Reducer { @@ -176,6 +176,12 @@ public object ChangesetReducer : Reducer { changesetReducer(state, action) } +/** Pure comments reducer as a [Reducer] instance. Delegates to [commentsReducer]. */ +public object CommentsReducer : Reducer { + override fun reduce(state: CommentsState, action: StateAction): CommentsState = + commentsReducer(state, action) +} + /** Pure resource-watch reducer as a [Reducer] instance. Delegates to [resourceWatchReducer]. */ public object ResourceWatchReducer : Reducer { override fun reduce(state: ResourceWatchState, action: StateAction): ResourceWatchState = @@ -1331,6 +1337,84 @@ public fun changesetReducer(state: ChangesetState, action: StateAction): Changes else -> state } +// ─── Comments Reducer ────────────────────────────────────────── + +/** + * Pure reducer for [CommentsState]. Handles all comments-channel action + * variants; actions belonging to other channels (or unknown variants) are + * no-ops that return [state] unchanged. + * + * The single-comment-minimum invariant is enforced by the server, not the + * reducer — a malformed server that removes a thread's last comment via + * `comments/commentRemoved` would leave an empty thread, which is + * observable but not catastrophic. + */ +public fun commentsReducer(state: CommentsState, action: StateAction): CommentsState = when (action) { + is StateActionCommentsThreadSet -> { + val thread = action.value.thread + val idx = state.threads.indexOfFirst { it.id == thread.id } + if (idx < 0) { + state.copy(threads = state.threads + thread) + } else { + val next = state.threads.toMutableList().also { it[idx] = thread } + state.copy(threads = next) + } + } + + is StateActionCommentsThreadRemoved -> { + val idx = state.threads.indexOfFirst { it.id == action.value.threadId } + if (idx < 0) { + state + } else { + val next: List = state.threads.toMutableList().also { it.removeAt(idx) } + state.copy(threads = next) + } + } + + is StateActionCommentsCommentSet -> { + val tIdx = state.threads.indexOfFirst { it.id == action.value.threadId } + if (tIdx < 0) { + state + } else { + val thread = state.threads[tIdx] + val comment = action.value.comment + val cIdx = thread.comments.indexOfFirst { it.id == comment.id } + val nextComments = if (cIdx < 0) { + thread.comments + comment + } else { + thread.comments.toMutableList().also { it[cIdx] = comment } + } + val nextThreads = state.threads.toMutableList() + .also { it[tIdx] = thread.copy(comments = nextComments) } + state.copy(threads = nextThreads) + } + } + + is StateActionCommentsCommentRemoved -> { + val tIdx = state.threads.indexOfFirst { it.id == action.value.threadId } + if (tIdx < 0) { + state + } else { + val thread = state.threads[tIdx] + val cIdx = thread.comments.indexOfFirst { it.id == action.value.commentId } + if (cIdx < 0) { + state + } else { + val nextComments: List = thread.comments.toMutableList() + .also { it.removeAt(cIdx) } + val nextThreads = state.threads.toMutableList() + .also { it[tIdx] = thread.copy(comments = nextComments) } + state.copy(threads = nextThreads) + } + } + } + + is StateActionCommentsCleared -> + if (state.threads.isEmpty()) state else state.copy(threads = emptyList()) + + else -> state +} + /** * Pure reducer for an [ResourceWatchState]. Pattern-matches on the * `resourceWatch/changed` action; actions belonging to other channels 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..f924b6a4 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 @@ -122,6 +122,16 @@ enum class ActionType { CHANGESET_OPERATION_STATUS_CHANGED, @SerialName("changeset/cleared") CHANGESET_CLEARED, + @SerialName("comments/threadSet") + COMMENTS_THREAD_SET, + @SerialName("comments/threadRemoved") + COMMENTS_THREAD_REMOVED, + @SerialName("comments/commentSet") + COMMENTS_COMMENT_SET, + @SerialName("comments/commentRemoved") + COMMENTS_COMMENT_REMOVED, + @SerialName("comments/cleared") + COMMENTS_CLEARED, @SerialName("root/terminalsChanged") ROOT_TERMINALS_CHANGED, @SerialName("root/configChanged") @@ -856,6 +866,55 @@ data class ChangesetClearedAction( val type: ActionType ) +@Serializable +data class CommentsThreadSetAction( + val type: ActionType, + /** + * The new or replacement thread. MUST contain at least one comment. + */ + val thread: CommentThread +) + +@Serializable +data class CommentsThreadRemovedAction( + val type: ActionType, + /** + * The {@link CommentThread.id} of the thread to remove. + */ + val threadId: String +) + +@Serializable +data class CommentsCommentSetAction( + val type: ActionType, + /** + * The {@link CommentThread.id} the comment belongs to. + */ + val threadId: String, + /** + * The new or replacement comment. + */ + val comment: Comment +) + +@Serializable +data class CommentsCommentRemovedAction( + val type: ActionType, + /** + * The {@link CommentThread.id} the comment belongs to. + */ + val threadId: String, + /** + * The {@link Comment.id} to remove. + */ + val commentId: String +) + +@Serializable +data class CommentsClearedAction( + val type: ActionType +) + @Serializable data class RootTerminalsChangedAction( val type: ActionType, @@ -1063,6 +1122,11 @@ sealed interface StateAction @JvmInline value class StateActionChangesetOperationsChanged(val value: ChangesetOperationsChangedAction) : StateAction @JvmInline value class StateActionChangesetOperationStatusChanged(val value: ChangesetOperationStatusChangedAction) : StateAction @JvmInline value class StateActionChangesetCleared(val value: ChangesetClearedAction) : StateAction +@JvmInline value class StateActionCommentsThreadSet(val value: CommentsThreadSetAction) : StateAction +@JvmInline value class StateActionCommentsThreadRemoved(val value: CommentsThreadRemovedAction) : StateAction +@JvmInline value class StateActionCommentsCommentSet(val value: CommentsCommentSetAction) : StateAction +@JvmInline value class StateActionCommentsCommentRemoved(val value: CommentsCommentRemovedAction) : StateAction +@JvmInline value class StateActionCommentsCleared(val value: CommentsClearedAction) : StateAction @JvmInline value class StateActionRootTerminalsChanged(val value: RootTerminalsChangedAction) : StateAction @JvmInline value class StateActionRootConfigChanged(val value: RootConfigChangedAction) : StateAction @JvmInline value class StateActionTerminalData(val value: TerminalDataAction) : StateAction @@ -1140,6 +1204,11 @@ internal object StateActionSerializer : KSerializer { "changeset/operationsChanged" -> StateActionChangesetOperationsChanged(input.json.decodeFromJsonElement(ChangesetOperationsChangedAction.serializer(), element)) "changeset/operationStatusChanged" -> StateActionChangesetOperationStatusChanged(input.json.decodeFromJsonElement(ChangesetOperationStatusChangedAction.serializer(), element)) "changeset/cleared" -> StateActionChangesetCleared(input.json.decodeFromJsonElement(ChangesetClearedAction.serializer(), element)) + "comments/threadSet" -> StateActionCommentsThreadSet(input.json.decodeFromJsonElement(CommentsThreadSetAction.serializer(), element)) + "comments/threadRemoved" -> StateActionCommentsThreadRemoved(input.json.decodeFromJsonElement(CommentsThreadRemovedAction.serializer(), element)) + "comments/commentSet" -> StateActionCommentsCommentSet(input.json.decodeFromJsonElement(CommentsCommentSetAction.serializer(), element)) + "comments/commentRemoved" -> StateActionCommentsCommentRemoved(input.json.decodeFromJsonElement(CommentsCommentRemovedAction.serializer(), element)) + "comments/cleared" -> StateActionCommentsCleared(input.json.decodeFromJsonElement(CommentsClearedAction.serializer(), element)) "root/terminalsChanged" -> StateActionRootTerminalsChanged(input.json.decodeFromJsonElement(RootTerminalsChangedAction.serializer(), element)) "root/configChanged" -> StateActionRootConfigChanged(input.json.decodeFromJsonElement(RootConfigChangedAction.serializer(), element)) "terminal/data" -> StateActionTerminalData(input.json.decodeFromJsonElement(TerminalDataAction.serializer(), element)) @@ -1210,6 +1279,11 @@ internal object StateActionSerializer : KSerializer { is StateActionChangesetOperationsChanged -> output.json.encodeToJsonElement(ChangesetOperationsChangedAction.serializer(), value.value) is StateActionChangesetOperationStatusChanged -> output.json.encodeToJsonElement(ChangesetOperationStatusChangedAction.serializer(), value.value) is StateActionChangesetCleared -> output.json.encodeToJsonElement(ChangesetClearedAction.serializer(), value.value) + is StateActionCommentsThreadSet -> output.json.encodeToJsonElement(CommentsThreadSetAction.serializer(), value.value) + is StateActionCommentsThreadRemoved -> output.json.encodeToJsonElement(CommentsThreadRemovedAction.serializer(), value.value) + is StateActionCommentsCommentSet -> output.json.encodeToJsonElement(CommentsCommentSetAction.serializer(), value.value) + is StateActionCommentsCommentRemoved -> output.json.encodeToJsonElement(CommentsCommentRemovedAction.serializer(), value.value) + is StateActionCommentsCleared -> output.json.encodeToJsonElement(CommentsClearedAction.serializer(), value.value) is StateActionRootTerminalsChanged -> output.json.encodeToJsonElement(RootTerminalsChangedAction.serializer(), value.value) is StateActionRootConfigChanged -> output.json.encodeToJsonElement(RootConfigChangedAction.serializer(), value.value) is StateActionTerminalData -> output.json.encodeToJsonElement(TerminalDataAction.serializer(), value.value) diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt index b71f2527..bc4f7a9a 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt @@ -1010,6 +1010,138 @@ data class ChangesetOperationFollowUp( val external: Boolean? = null ) +@Serializable +data class CreateCommentThreadParams( + /** + * Channel URI this command targets. + */ + val channel: String, + /** + * Turn whose file versions {@link resource} + {@link range} address. + */ + val turnId: String, + /** + * Anchored file URI. + */ + val resource: String, + /** + * Anchored range within {@link resource}. + */ + val range: TextRange, + /** + * First comment in the thread. The server assigns its {@link Comment.id}. + */ + val comment: NewComment +) + +@Serializable +data class CreateCommentThreadResult( + /** + * Server-assigned {@link CommentThread.id}. + */ + val threadId: String, + /** + * Server-assigned {@link Comment.id} of the initial comment. + */ + val commentId: String +) + +@Serializable +data class UpdateCommentThreadParams( + /** + * Channel URI this command targets. + */ + val channel: String, + /** + * The {@link CommentThread.id} to update. + */ + val threadId: String, + /** + * New {@link CommentThread.turnId}, if changing. + */ + val turnId: String? = null, + /** + * New anchored file URI, if changing. + */ + val resource: String? = null, + /** + * New anchored range, if changing. + */ + val range: TextRange? = null +) + +@Serializable +data class DeleteCommentThreadParams( + /** + * Channel URI this command targets. + */ + val channel: String, + /** + * The {@link CommentThread.id} to delete. + */ + val threadId: String +) + +@Serializable +data class AddCommentParams( + /** + * Channel URI this command targets. + */ + val channel: String, + /** + * Thread that receives the new comment. + */ + val threadId: String, + /** + * Comment payload — the server assigns the id. + */ + val comment: NewComment +) + +@Serializable +data class AddCommentResult( + /** + * Server-assigned {@link Comment.id} of the new comment. + */ + val commentId: String +) + +@Serializable +data class EditCommentParams( + /** + * Channel URI this command targets. + */ + val channel: String, + /** + * Enclosing thread. + */ + val threadId: String, + /** + * {@link Comment.id} to edit. + */ + val commentId: String, + /** + * New comment body. + */ + val text: String +) + +@Serializable +data class DeleteCommentParams( + /** + * Channel URI this command targets. + */ + val channel: String, + /** + * Enclosing thread. + */ + val threadId: String, + /** + * {@link Comment.id} to remove. + */ + val commentId: String +) + // ─── ReconnectResult Union ────────────────────────────────────────────────── @Serializable(with = ReconnectResultSerializer::class) diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt index 07ed4755..e95b3102 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt @@ -145,6 +145,24 @@ object AhpCommands { fun invokeChangesetOperation(id: Long, params: InvokeChangesetOperationParams): JsonRpcRequest = JsonRpcRequest(id = id, method = "invokeChangesetOperation", params = params) + + fun createCommentThread(id: Long, params: CreateCommentThreadParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "createCommentThread", params = params) + + fun updateCommentThread(id: Long, params: UpdateCommentThreadParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "updateCommentThread", params = params) + + fun deleteCommentThread(id: Long, params: DeleteCommentThreadParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "deleteCommentThread", params = params) + + fun addComment(id: Long, params: AddCommentParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "addComment", params = params) + + fun editComment(id: Long, params: EditCommentParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "editComment", params = params) + + fun deleteComment(id: Long, params: DeleteCommentParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "deleteComment", params = params) } /** 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 425ef812..ce48428f 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 @@ -198,5 +198,12 @@ data class PartialSessionSummary( * session's footprint (e.g., for list rendering) without requiring the * client to subscribe to a changeset. */ - val changes: ChangesSummary? = null + val changes: ChangesSummary? = null, + /** + * Lightweight summary of this session's inline comments channel + * (`ahp-session://comments`). Surfaced so badge UI can render + * thread / comment counts without subscribing. Absent when the session + * does not expose a comments channel. + */ + val comments: CommentsSummary? = null ) 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 0ebd79d8..5b555267 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 @@ -998,7 +998,14 @@ data class SessionSummary( * session's footprint (e.g., for list rendering) without requiring the * client to subscribe to a changeset. */ - val changes: ChangesSummary? = null + val changes: ChangesSummary? = null, + /** + * Lightweight summary of this session's inline comments channel + * (`ahp-session://comments`). Surfaced so badge UI can render + * thread / comment counts without subscribing. Absent when the session + * does not expose a comments channel. + */ + val comments: CommentsSummary? = null ) @Serializable @@ -3061,6 +3068,97 @@ data class ChangesetOperation( val error: ErrorInfo? = null ) +@Serializable +data class CommentsSummary( + /** + * The subscribable comments channel URI for the owning session + * (typically `ahp-session://comments`). Surfaced explicitly even + * though it is derivable from the session URI so badge UI does not need + * to know the derivation rule. + */ + val resource: String, + /** + * Total number of {@link CommentThread} entries in the channel. + */ + val threadCount: Long, + /** + * Total number of {@link Comment} entries across every thread. + */ + val commentCount: Long +) + +@Serializable +data class CommentsState( + /** + * Comment threads in this channel, keyed by {@link CommentThread.id}. + */ + val threads: List +) + +@Serializable +data class CommentThread( + /** + * Stable identifier within the comments channel. Server-assigned. + */ + val id: String, + /** + * Turn that produced the file versions this thread is anchored to. + * Matches a {@link Turn.id} on the owning session. + */ + val turnId: String, + /** + * The file the thread is anchored to. + */ + val resource: String, + /** + * Range within {@link resource} the thread is anchored to. + */ + val range: TextRange, + /** + * Comments in this thread, in dispatch order (oldest first). MUST + * contain at least one entry. + */ + val comments: List, + /** + * Server-defined opaque metadata, surfaced to tooling but not + * interpreted by the protocol. + */ + @SerialName("_meta") + val meta: Map? = null +) + +@Serializable +data class Comment( + /** + * Stable identifier within the enclosing thread. Server-assigned. + */ + val id: String, + /** + * Comment body. Rendered as plain text unless the client opts into Markdown. + */ + val text: String, + /** + * Server-defined opaque metadata, surfaced to tooling but not + * interpreted by the protocol. + */ + @SerialName("_meta") + val meta: Map? = null +) + +@Serializable +data class NewComment( + /** + * Comment body. + */ + val text: String, + /** + * Server-defined opaque metadata, forwarded onto the resulting + * {@link Comment._meta}. + */ + @SerialName("_meta") + val meta: Map? = null +) + @Serializable data class TelemetryCapabilities( /** @@ -3817,7 +3915,7 @@ internal object ToolResultContentSerializer : KSerializer { } /** - * The state payload of a snapshot — root, session, terminal, or changeset state. + * The state payload of a snapshot — root, session, terminal, changeset, or comments state. */ @Serializable(with = SnapshotStateSerializer::class) sealed interface SnapshotState { @@ -3825,6 +3923,7 @@ sealed interface SnapshotState { @JvmInline value class Session(val value: SessionState) : SnapshotState @JvmInline value class Terminal(val value: TerminalState) : SnapshotState @JvmInline value class Changeset(val value: ChangesetState) : SnapshotState + @JvmInline value class Comments(val value: CommentsState) : SnapshotState } internal object SnapshotStateSerializer : KSerializer { @@ -3839,12 +3938,14 @@ internal object SnapshotStateSerializer : KSerializer { ?: error("Expected JsonObject for SnapshotState") // Try the most distinctive shape first. SessionState has required // `summary`; ChangesetState has required `status` + `files`; - // TerminalState has `uri` / `size` / `buffer`; RootState is the - // catch-all. + // CommentsState has required `threads`; TerminalState has + // `uri` / `size` / `buffer`; RootState is the catch-all. return when { obj.containsKey("summary") -> SnapshotState.Session(input.json.decodeFromJsonElement(SessionState.serializer(), element)) obj.containsKey("status") && obj.containsKey("files") -> SnapshotState.Changeset(input.json.decodeFromJsonElement(ChangesetState.serializer(), element)) + obj.containsKey("threads") -> + SnapshotState.Comments(input.json.decodeFromJsonElement(CommentsState.serializer(), element)) obj.containsKey("size") || obj.containsKey("uri") || obj.containsKey("buffer") -> SnapshotState.Terminal(input.json.decodeFromJsonElement(TerminalState.serializer(), element)) else -> SnapshotState.Root(input.json.decodeFromJsonElement(RootState.serializer(), element)) @@ -3859,6 +3960,7 @@ internal object SnapshotStateSerializer : KSerializer { is SnapshotState.Session -> output.json.encodeToJsonElement(SessionState.serializer(), value.value) is SnapshotState.Terminal -> output.json.encodeToJsonElement(TerminalState.serializer(), value.value) is SnapshotState.Changeset -> output.json.encodeToJsonElement(ChangesetState.serializer(), value.value) + is SnapshotState.Comments -> output.json.encodeToJsonElement(CommentsState.serializer(), value.value) } output.encodeJsonElement(element) } diff --git a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt index d426b79c..e8d19632 100644 --- a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt +++ b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt @@ -179,6 +179,18 @@ class FixtureDrivenReducerTest { }, ) + "comments" -> compareFixture( + file = file, + initial = initial, + expected = expected, + serializer = CommentsState.serializer(), + run = { state -> + var s = state + for (action in actions) s = commentsReducer(s, action) + s + }, + ) + "resourceWatch" -> compareFixture( file = file, initial = initial, diff --git a/clients/rust/CHANGELOG.md b/clients/rust/CHANGELOG.md index 399a16ec..68ee080a 100644 --- a/clients/rust/CHANGELOG.md +++ b/clients/rust/CHANGELOG.md @@ -22,6 +22,15 @@ matching `## [X.Y.Z]` heading is missing from this file. `idle → running → error` lifecycle of a changeset operation. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. +- New comments channel wire types (`ahp-session://comments`): + `CommentsState`, `CommentThread`, `Comment`, `NewComment`, + `CommentsSummary`; the + `comments/threadSet` / `comments/threadRemoved` / `comments/commentSet` + / `comments/commentRemoved` / `comments/cleared` action variants; + `createCommentThread`, `updateCommentThread`, `deleteCommentThread`, + `addComment`, `editComment`, and `deleteComment` command structs; + `MultiHostStateMirror.comments()` and `SnapshotState::Comments`. + Reducer logic is deferred (matches the changeset stub). ### Changed diff --git a/clients/rust/crates/ahp-types/src/actions.rs b/clients/rust/crates/ahp-types/src/actions.rs index 49fdd8e9..8f77aca2 100644 --- a/clients/rust/crates/ahp-types/src/actions.rs +++ b/clients/rust/crates/ahp-types/src/actions.rs @@ -13,11 +13,11 @@ 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, + ChangesetOperationStatus, ChangesetStatus, Comment, CommentThread, ConfirmationOption, + Customization, ErrorInfo, Message, ModelSelection, PendingMessageKind, ResponsePart, + SessionActiveClient, SessionInputAnswer, SessionInputRequest, SessionInputResponseKind, + TerminalClaim, TerminalInfo, ToolCallCancellationReason, ToolCallConfirmationReason, + ToolCallResult, ToolDefinition, ToolResultContent, UsageInfo, }; // ─── ActionType ────────────────────────────────────────────────────── @@ -121,6 +121,16 @@ pub enum ActionType { ChangesetOperationStatusChanged, #[serde(rename = "changeset/cleared")] ChangesetCleared, + #[serde(rename = "comments/threadSet")] + CommentsThreadSet, + #[serde(rename = "comments/threadRemoved")] + CommentsThreadRemoved, + #[serde(rename = "comments/commentSet")] + CommentsCommentSet, + #[serde(rename = "comments/commentRemoved")] + CommentsCommentRemoved, + #[serde(rename = "comments/cleared")] + CommentsCleared, #[serde(rename = "root/terminalsChanged")] RootTerminalsChanged, #[serde(rename = "root/configChanged")] @@ -929,6 +939,73 @@ pub struct ChangesetOperationStatusChangedAction { #[serde(rename_all = "camelCase")] pub struct ChangesetClearedAction {} +/// Upsert a {@link CommentThread} in the comments channel — adds a new +/// thread, or replaces an existing one identified by +/// {@link CommentThread.id}. When replacing, the full thread payload +/// (including its {@link CommentThread.comments | comments} list) is +/// substituted; producers SHOULD prefer {@link CommentsCommentSetAction} +/// for per-comment edits to keep wire updates small. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommentsThreadSetAction { + /// The new or replacement thread. MUST contain at least one comment. + pub thread: CommentThread, +} + +/// Remove a {@link CommentThread} from the channel by its id. +/// +/// The server emits this in two cases: +/// 1. The client explicitly invoked +/// {@link DeleteCommentThreadParams | `deleteCommentThread`}. +/// 2. The client invoked {@link DeleteCommentParams | `deleteComment`} on +/// the last remaining comment in the thread — the protocol collapses +/// the thread rather than leaving an empty one behind. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommentsThreadRemovedAction { + /// The {@link CommentThread.id} of the thread to remove. + pub thread_id: String, +} + +/// Upsert a {@link Comment} within an existing thread — adds a new +/// comment, or replaces one identified by {@link Comment.id}. If +/// {@link threadId} does not match any current thread the action is a +/// no-op. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommentsCommentSetAction { + /// The {@link CommentThread.id} the comment belongs to. + pub thread_id: String, + /// The new or replacement comment. + pub comment: Comment, +} + +/// Remove a single {@link Comment} from a thread without collapsing the +/// thread itself. Used when more than one comment remains — the server +/// MUST dispatch {@link CommentsThreadRemovedAction} instead when removing +/// the last comment would otherwise leave the thread empty. +/// +/// If either {@link threadId} or {@link commentId} does not match the +/// current state the action is a no-op. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommentsCommentRemovedAction { + /// The {@link CommentThread.id} the comment belongs to. + pub thread_id: String, + /// The {@link Comment.id} to remove. + pub comment_id: String, +} + +/// Drop every thread from the comments channel. +/// +/// Dispatched when the owning session is going away and the channel is +/// about to become un-subscribable. Clients SHOULD release references on +/// receipt and react to the corresponding session-level lifecycle signal +/// (e.g. `root/sessionRemoved`) to fully tear down UI. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommentsClearedAction {} + /// Fired when the list of known terminals changes. /// /// Full-replacement semantics: the `terminals` array replaces the previous @@ -1191,6 +1268,16 @@ pub enum StateAction { ChangesetOperationStatusChanged(ChangesetOperationStatusChangedAction), #[serde(rename = "changeset/cleared")] ChangesetCleared(ChangesetClearedAction), + #[serde(rename = "comments/threadSet")] + CommentsThreadSet(CommentsThreadSetAction), + #[serde(rename = "comments/threadRemoved")] + CommentsThreadRemoved(CommentsThreadRemovedAction), + #[serde(rename = "comments/commentSet")] + CommentsCommentSet(CommentsCommentSetAction), + #[serde(rename = "comments/commentRemoved")] + CommentsCommentRemoved(CommentsCommentRemovedAction), + #[serde(rename = "comments/cleared")] + CommentsCleared(CommentsClearedAction), #[serde(rename = "root/terminalsChanged")] RootTerminalsChanged(RootTerminalsChangedAction), #[serde(rename = "terminal/data")] diff --git a/clients/rust/crates/ahp-types/src/commands.rs b/clients/rust/crates/ahp-types/src/commands.rs index 4b18fa92..1d5f87c3 100644 --- a/clients/rust/crates/ahp-types/src/commands.rs +++ b/clients/rust/crates/ahp-types/src/commands.rs @@ -15,9 +15,9 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::actions::{ActionEnvelope, StateAction}; #[allow(unused_imports)] use crate::state::{ - AgentSelection, ContentRef, MessageAttachment, ModelSelection, SessionActiveClient, + AgentSelection, ContentRef, MessageAttachment, ModelSelection, NewComment, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, - TerminalClaim, Turn, + TerminalClaim, TextRange, Turn, }; // ─── Enums ──────────────────────────────────────────────────────────── @@ -1020,6 +1020,136 @@ pub struct ChangesetOperationFollowUp { pub external: Option, } +/// Create a new {@link CommentThread} anchored to a file range from a +/// specific turn. +/// +/// The initial comment is required — the protocol forbids empty threads, +/// so thread creation and first-comment creation are fused into one +/// command. The server assigns both {@link CreateCommentThreadResult.threadId} +/// and {@link CreateCommentThreadResult.commentId}, then broadcasts a +/// {@link CommentsThreadSetAction} on the channel. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateCommentThreadParams { + /// Channel URI this command targets. + pub channel: Uri, + /// Turn whose file versions {@link resource} + {@link range} address. + pub turn_id: String, + /// Anchored file URI. + pub resource: Uri, + /// Anchored range within {@link resource}. + pub range: TextRange, + /// First comment in the thread. The server assigns its {@link Comment.id}. + pub comment: NewComment, +} + +/// Result of {@link CreateCommentThreadParams | `createCommentThread`}. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateCommentThreadResult { + /// Server-assigned {@link CommentThread.id}. + pub thread_id: String, + /// Server-assigned {@link Comment.id} of the initial comment. + pub comment_id: String, +} + +/// Re-anchor an existing {@link CommentThread} — typically used to re-pin +/// a thread to a different range or a newer turn after an edit. Comments +/// themselves are not modified by this command; use +/// {@link AddCommentParams | `addComment`}, +/// {@link EditCommentParams | `editComment`}, or +/// {@link DeleteCommentParams | `deleteComment`} for that. +/// +/// Omitted optional fields preserve their current value. The server +/// echoes the resulting thread state as a {@link CommentsThreadSetAction}. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateCommentThreadParams { + /// Channel URI this command targets. + pub channel: Uri, + /// The {@link CommentThread.id} to update. + pub thread_id: String, + /// New {@link CommentThread.turnId}, if changing. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub turn_id: Option, + /// New anchored file URI, if changing. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resource: Option, + /// New anchored range, if changing. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub range: Option, +} + +/// Delete an entire comment thread (and every comment it contains). The +/// server echoes a {@link CommentsThreadRemovedAction} on the channel. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DeleteCommentThreadParams { + /// Channel URI this command targets. + pub channel: Uri, + /// The {@link CommentThread.id} to delete. + pub thread_id: String, +} + +/// Append a new {@link Comment} to an existing thread. The server assigns +/// the resulting {@link Comment.id} and echoes a +/// {@link CommentsCommentSetAction}. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AddCommentParams { + /// Channel URI this command targets. + pub channel: Uri, + /// Thread that receives the new comment. + pub thread_id: String, + /// Comment payload — the server assigns the id. + pub comment: NewComment, +} + +/// Result of {@link AddCommentParams | `addComment`}. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AddCommentResult { + /// Server-assigned {@link Comment.id} of the new comment. + pub comment_id: String, +} + +/// Edit the body of an existing comment in place. The server echoes a +/// {@link CommentsCommentSetAction} carrying the updated comment. +/// +/// Only the body is mutable through this command; to change +/// {@link Comment.source} or {@link Comment._meta} delete and re-create +/// the comment. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EditCommentParams { + /// Channel URI this command targets. + pub channel: Uri, + /// Enclosing thread. + pub thread_id: String, + /// {@link Comment.id} to edit. + pub comment_id: String, + /// New comment body. + pub text: String, +} + +/// Remove a single comment from a thread. +/// +/// If the removal would leave the thread empty (i.e. the targeted comment +/// is the only one remaining), the server collapses the thread instead +/// — it dispatches a {@link CommentsThreadRemovedAction} and the thread +/// disappears from {@link CommentsState.threads}. Otherwise the server +/// echoes a {@link CommentsCommentRemovedAction}. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DeleteCommentParams { + /// Channel URI this command targets. + pub channel: Uri, + /// Enclosing thread. + pub thread_id: String, + /// {@link Comment.id} to remove. + pub comment_id: String, +} + // ─── ReconnectResult Union ──────────────────────────────────────────── /// Result of the `reconnect` command. diff --git a/clients/rust/crates/ahp-types/src/notifications.rs b/clients/rust/crates/ahp-types/src/notifications.rs index f1a55a22..ad20f915 100644 --- a/clients/rust/crates/ahp-types/src/notifications.rs +++ b/clients/rust/crates/ahp-types/src/notifications.rs @@ -13,8 +13,8 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; #[allow(unused_imports)] use crate::state::{ - AgentSelection, ChangesSummary, Changeset, FileEdit, ModelSelection, ProjectInfo, - SessionStatus, SessionSummary, + AgentSelection, ChangesSummary, Changeset, CommentsSummary, FileEdit, ModelSelection, + ProjectInfo, SessionStatus, SessionSummary, }; // ─── Enums ──────────────────────────────────────────────────────────── @@ -223,4 +223,10 @@ pub struct PartialSessionSummary { /// client to subscribe to a changeset. #[serde(default, skip_serializing_if = "Option::is_none")] pub changes: Option, + /// Lightweight summary of this session's inline comments channel + /// (`ahp-session://comments`). Surfaced so badge UI can render + /// thread / comment counts without subscribing. Absent when the session + /// does not expose a comments channel. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub comments: Option, } diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 2770b915..fd9b7a93 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -795,6 +795,12 @@ pub struct SessionSummary { /// client to subscribe to a changeset. #[serde(default, skip_serializing_if = "Option::is_none")] pub changes: Option, + /// Lightweight summary of this session's inline comments channel + /// (`ahp-session://comments`). Surfaced so badge UI can render + /// thread / comment counts without subscribing. Absent when the session + /// does not expose a comments channel. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub comments: Option, } /// Aggregate counts describing the file changes associated with a session. @@ -2585,6 +2591,95 @@ pub struct ChangesetOperation { pub error: Option, } +/// Lightweight per-session summary of the comments channel, surfaced on +/// {@link SessionSummary.comments} so badge UI can render thread / comment +/// counts without subscribing to the channel itself. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommentsSummary { + /// The subscribable comments channel URI for the owning session + /// (typically `ahp-session://comments`). Surfaced explicitly even + /// though it is derivable from the session URI so badge UI does not need + /// to know the derivation rule. + pub resource: Uri, + /// Total number of {@link CommentThread} entries in the channel. + pub thread_count: i64, + /// Total number of {@link Comment} entries across every thread. + pub comment_count: i64, +} + +/// Full state for a session's comments channel, returned when a client +/// subscribes to an `ahp-session://comments` URI. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommentsState { + /// Comment threads in this channel, keyed by {@link CommentThread.id}. + pub threads: Vec, +} + +/// A conversation anchored to a specific range in a specific file produced +/// by a specific turn. +/// +/// {@link turnId} anchors the thread to the file versions that turn +/// produced, so a later turn that rewrites the same file does not silently +/// invalidate the comment's anchor — clients can resolve {@link resource} +/// and {@link range} against the turn's changeset. +/// +/// Every thread MUST contain at least one {@link Comment}. The server +/// enforces this invariant: {@link CreateCommentThreadParams | +/// `createCommentThread`} requires an initial comment, and deleting the +/// last remaining comment collapses the thread into a +/// {@link CommentsThreadRemovedAction} rather than leaving an empty thread +/// behind. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommentThread { + /// Stable identifier within the comments channel. Server-assigned. + pub id: String, + /// Turn that produced the file versions this thread is anchored to. + /// Matches a {@link Turn.id} on the owning session. + pub turn_id: String, + /// The file the thread is anchored to. + pub resource: Uri, + /// Range within {@link resource} the thread is anchored to. + pub range: TextRange, + /// Comments in this thread, in dispatch order (oldest first). MUST + /// contain at least one entry. + pub comments: Vec, + /// Server-defined opaque metadata, surfaced to tooling but not + /// interpreted by the protocol. + #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] + pub meta: Option, +} + +/// A single comment within a {@link CommentThread}. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Comment { + /// Stable identifier within the enclosing thread. Server-assigned. + pub id: String, + /// Comment body. Rendered as plain text unless the client opts into Markdown. + pub text: String, + /// Server-defined opaque metadata, surfaced to tooling but not + /// interpreted by the protocol. + #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] + pub meta: Option, +} + +/// Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`} +/// and {@link AddCommentParams | `addComment`}. The server assigns the +/// resulting {@link Comment.id}. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NewComment { + /// Comment body. + pub text: String, + /// Server-defined opaque metadata, forwarded onto the resulting + /// {@link Comment._meta}. + #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] + pub meta: Option, +} + /// OTLP telemetry channels the agent host emits. /// /// Each field, when present, is either a literal channel URI or an @@ -2898,17 +2993,19 @@ pub enum CustomizationLoadState { Unknown(serde_json::Value), } -/// The state payload of a snapshot — root, session, terminal, or -/// changeset state. +/// The state payload of a snapshot — root, session, terminal, +/// changeset, or comments state. /// /// Deserialized by trying session first (has required `summary`), then /// terminal (has required `content`), then changeset (has required -/// `status` and `files`), then root. +/// `status` and `files`), then comments (has required `threads`), +/// then root. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum SnapshotState { Session(Box), Terminal(Box), Changeset(Box), + Comments(Box), Root(Box), } diff --git a/clients/rust/crates/ahp/src/multi_host_state_mirror.rs b/clients/rust/crates/ahp/src/multi_host_state_mirror.rs index 3b1ecde4..89560091 100644 --- a/clients/rust/crates/ahp/src/multi_host_state_mirror.rs +++ b/clients/rust/crates/ahp/src/multi_host_state_mirror.rs @@ -36,7 +36,7 @@ use std::collections::HashMap; use ahp_types::actions::ActionEnvelope; use ahp_types::common::ROOT_RESOURCE_URI; -use ahp_types::state::{ChangesetState, RootState, SessionState, SnapshotState, TerminalState}; +use ahp_types::state::{ChangesetState, CommentsState, RootState, SessionState, SnapshotState, TerminalState}; use crate::hosts::{HostId, HostSubscriptionEvent}; use crate::reducers::{apply_action_to_root, apply_action_to_session, apply_action_to_terminal}; @@ -84,6 +84,7 @@ pub struct MultiHostStateMirror { sessions: HashMap, terminals: HashMap, changesets: HashMap, + comments: HashMap, } impl MultiHostStateMirror { @@ -112,6 +113,11 @@ impl MultiHostStateMirror { &self.changesets } + /// Borrow the comments states map keyed by `(host_id, uri)`. + pub fn comments(&self) -> &HashMap { + &self.comments + } + /// Convenience: apply a [`HostSubscriptionEvent`] produced by /// [`crate::hosts::MultiHostClient::events`]. Action envelopes are /// routed through the reducer; non-action events (session-summary @@ -173,16 +179,20 @@ impl MultiHostStateMirror { SnapshotState::Changeset(state) => { self.changesets.insert(key, state.as_ref().clone()); } + SnapshotState::Comments(state) => { + self.comments.insert(key, state.as_ref().clone()); + } } } /// Drop every slot keyed under `host` — root state, sessions, - /// terminals, and changesets. + /// terminals, changesets, and comments. pub fn reset_host(&mut self, host: &HostId) { self.root_states.remove(host); self.sessions.retain(|key, _| &key.host_id != host); self.terminals.retain(|key, _| &key.host_id != host); self.changesets.retain(|key, _| &key.host_id != host); + self.comments.retain(|key, _| &key.host_id != host); } /// Drop every host's state. @@ -191,5 +201,6 @@ impl MultiHostStateMirror { self.sessions.clear(); self.terminals.clear(); self.changesets.clear(); + self.comments.clear(); } } diff --git a/clients/rust/crates/ahp/src/reducers.rs b/clients/rust/crates/ahp/src/reducers.rs index 5599bb83..eb48e889 100644 --- a/clients/rust/crates/ahp/src/reducers.rs +++ b/clients/rust/crates/ahp/src/reducers.rs @@ -1220,6 +1220,7 @@ mod tests { agent: None, working_directory: None, changes: None, + comments: None, }, lifecycle: SessionLifecycle::Creating, creation_error: None, @@ -1519,6 +1520,11 @@ mod tests { skipped += 1; continue; } + "comments" => { + // comments reducer not yet implemented in Rust; skip. + skipped += 1; + continue; + } "resourceWatch" => { // resourceWatch reducer not yet implemented in Rust; skip. skipped += 1; diff --git a/clients/rust/crates/ahp/tests/hosts.rs b/clients/rust/crates/ahp/tests/hosts.rs index cf5e9da7..d2923a84 100644 --- a/clients/rust/crates/ahp/tests/hosts.rs +++ b/clients/rust/crates/ahp/tests/hosts.rs @@ -1033,6 +1033,7 @@ fn make_summary(uri: &str, title: &str, modified_at: i64) -> ahp_types::state::S agent: None, working_directory: None, changes: None, + comments: 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 5f830bd7..c1baa64f 100644 --- a/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs +++ b/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs @@ -57,6 +57,7 @@ fn session_state(title: &str, resource: &str) -> SessionState { agent: None, working_directory: None, changes: None, + comments: None, }, lifecycle: SessionLifecycle::Ready, creation_error: None, @@ -365,6 +366,7 @@ fn non_action_event_is_ignored() { agent: None, working_directory: None, changes: None, + comments: None, }, }), }; diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift index 30158295..c3f414a1 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift @@ -54,6 +54,11 @@ public enum ActionType: String, Codable, Sendable { case changesetOperationsChanged = "changeset/operationsChanged" case changesetOperationStatusChanged = "changeset/operationStatusChanged" case changesetCleared = "changeset/cleared" + case commentsThreadSet = "comments/threadSet" + case commentsThreadRemoved = "comments/threadRemoved" + case commentsCommentSet = "comments/commentSet" + case commentsCommentRemoved = "comments/commentRemoved" + case commentsCleared = "comments/cleared" case rootTerminalsChanged = "root/terminalsChanged" case rootConfigChanged = "root/configChanged" case terminalData = "terminal/data" @@ -1111,6 +1116,80 @@ public struct ChangesetClearedAction: Codable, Sendable { } } +public struct CommentsThreadSetAction: Codable, Sendable { + public var type: ActionType + /// The new or replacement thread. MUST contain at least one comment. + public var thread: CommentThread + + public init( + type: ActionType, + thread: CommentThread + ) { + self.type = type + self.thread = thread + } +} + +public struct CommentsThreadRemovedAction: Codable, Sendable { + public var type: ActionType + /// The {@link CommentThread.id} of the thread to remove. + public var threadId: String + + public init( + type: ActionType, + threadId: String + ) { + self.type = type + self.threadId = threadId + } +} + +public struct CommentsCommentSetAction: Codable, Sendable { + public var type: ActionType + /// The {@link CommentThread.id} the comment belongs to. + public var threadId: String + /// The new or replacement comment. + public var comment: Comment + + public init( + type: ActionType, + threadId: String, + comment: Comment + ) { + self.type = type + self.threadId = threadId + self.comment = comment + } +} + +public struct CommentsCommentRemovedAction: Codable, Sendable { + public var type: ActionType + /// The {@link CommentThread.id} the comment belongs to. + public var threadId: String + /// The {@link Comment.id} to remove. + public var commentId: String + + public init( + type: ActionType, + threadId: String, + commentId: String + ) { + self.type = type + self.threadId = threadId + self.commentId = commentId + } +} + +public struct CommentsClearedAction: Codable, Sendable { + public var type: ActionType + + public init( + type: ActionType + ) { + self.type = type + } +} + public struct RootTerminalsChangedAction: Codable, Sendable { public var type: ActionType /// Updated terminal list (full replacement) @@ -1378,6 +1457,11 @@ public enum StateAction: Codable, Sendable { case changesetOperationsChanged(ChangesetOperationsChangedAction) case changesetOperationStatusChanged(ChangesetOperationStatusChangedAction) case changesetCleared(ChangesetClearedAction) + case commentsThreadSet(CommentsThreadSetAction) + case commentsThreadRemoved(CommentsThreadRemovedAction) + case commentsCommentSet(CommentsCommentSetAction) + case commentsCommentRemoved(CommentsCommentRemovedAction) + case commentsCleared(CommentsClearedAction) case rootTerminalsChanged(RootTerminalsChangedAction) case rootConfigChanged(RootConfigChangedAction) case terminalData(TerminalDataAction) @@ -1497,6 +1581,16 @@ public enum StateAction: Codable, Sendable { self = .changesetOperationStatusChanged(try ChangesetOperationStatusChangedAction(from: decoder)) case "changeset/cleared": self = .changesetCleared(try ChangesetClearedAction(from: decoder)) + case "comments/threadSet": + self = .commentsThreadSet(try CommentsThreadSetAction(from: decoder)) + case "comments/threadRemoved": + self = .commentsThreadRemoved(try CommentsThreadRemovedAction(from: decoder)) + case "comments/commentSet": + self = .commentsCommentSet(try CommentsCommentSetAction(from: decoder)) + case "comments/commentRemoved": + self = .commentsCommentRemoved(try CommentsCommentRemovedAction(from: decoder)) + case "comments/cleared": + self = .commentsCleared(try CommentsClearedAction(from: decoder)) case "root/terminalsChanged": self = .rootTerminalsChanged(try RootTerminalsChangedAction(from: decoder)) case "root/configChanged": @@ -1580,6 +1674,11 @@ public enum StateAction: Codable, Sendable { case .changesetOperationsChanged(let v): try v.encode(to: encoder) case .changesetOperationStatusChanged(let v): try v.encode(to: encoder) case .changesetCleared(let v): try v.encode(to: encoder) + case .commentsThreadSet(let v): try v.encode(to: encoder) + case .commentsThreadRemoved(let v): try v.encode(to: encoder) + case .commentsCommentSet(let v): try v.encode(to: encoder) + case .commentsCommentRemoved(let v): try v.encode(to: encoder) + case .commentsCleared(let v): try v.encode(to: encoder) case .rootTerminalsChanged(let v): try v.encode(to: encoder) case .rootConfigChanged(let v): try v.encode(to: encoder) case .terminalData(let v): try v.encode(to: encoder) diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift index f79aa009..0dc5417d 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift @@ -1159,6 +1159,162 @@ public struct ChangesetOperationFollowUp: Codable, Sendable { } } +public struct CreateCommentThreadParams: Codable, Sendable { + /// Channel URI this command targets. + public var channel: String + /// Turn whose file versions {@link resource} + {@link range} address. + public var turnId: String + /// Anchored file URI. + public var resource: String + /// Anchored range within {@link resource}. + public var range: TextRange + /// First comment in the thread. The server assigns its {@link Comment.id}. + public var comment: NewComment + + public init( + channel: String, + turnId: String, + resource: String, + range: TextRange, + comment: NewComment + ) { + self.channel = channel + self.turnId = turnId + self.resource = resource + self.range = range + self.comment = comment + } +} + +public struct CreateCommentThreadResult: Codable, Sendable { + /// Server-assigned {@link CommentThread.id}. + public var threadId: String + /// Server-assigned {@link Comment.id} of the initial comment. + public var commentId: String + + public init( + threadId: String, + commentId: String + ) { + self.threadId = threadId + self.commentId = commentId + } +} + +public struct UpdateCommentThreadParams: Codable, Sendable { + /// Channel URI this command targets. + public var channel: String + /// The {@link CommentThread.id} to update. + public var threadId: String + /// New {@link CommentThread.turnId}, if changing. + public var turnId: String? + /// New anchored file URI, if changing. + public var resource: String? + /// New anchored range, if changing. + public var range: TextRange? + + public init( + channel: String, + threadId: String, + turnId: String? = nil, + resource: String? = nil, + range: TextRange? = nil + ) { + self.channel = channel + self.threadId = threadId + self.turnId = turnId + self.resource = resource + self.range = range + } +} + +public struct DeleteCommentThreadParams: Codable, Sendable { + /// Channel URI this command targets. + public var channel: String + /// The {@link CommentThread.id} to delete. + public var threadId: String + + public init( + channel: String, + threadId: String + ) { + self.channel = channel + self.threadId = threadId + } +} + +public struct AddCommentParams: Codable, Sendable { + /// Channel URI this command targets. + public var channel: String + /// Thread that receives the new comment. + public var threadId: String + /// Comment payload — the server assigns the id. + public var comment: NewComment + + public init( + channel: String, + threadId: String, + comment: NewComment + ) { + self.channel = channel + self.threadId = threadId + self.comment = comment + } +} + +public struct AddCommentResult: Codable, Sendable { + /// Server-assigned {@link Comment.id} of the new comment. + public var commentId: String + + public init( + commentId: String + ) { + self.commentId = commentId + } +} + +public struct EditCommentParams: Codable, Sendable { + /// Channel URI this command targets. + public var channel: String + /// Enclosing thread. + public var threadId: String + /// {@link Comment.id} to edit. + public var commentId: String + /// New comment body. + public var text: String + + public init( + channel: String, + threadId: String, + commentId: String, + text: String + ) { + self.channel = channel + self.threadId = threadId + self.commentId = commentId + self.text = text + } +} + +public struct DeleteCommentParams: Codable, Sendable { + /// Channel URI this command targets. + public var channel: String + /// Enclosing thread. + public var threadId: String + /// {@link Comment.id} to remove. + public var commentId: String + + public init( + channel: String, + threadId: String, + commentId: String + ) { + self.channel = channel + self.threadId = threadId + self.commentId = commentId + } +} + // MARK: - ReconnectResult Union public enum ReconnectResult: Codable, Sendable { diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift index e5f9ff00..c496ee70 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift @@ -169,6 +169,11 @@ public struct PartialSessionSummary: Codable, Sendable { /// session's footprint (e.g., for list rendering) without requiring the /// client to subscribe to a changeset. public var changes: ChangesSummary? + /// Lightweight summary of this session's inline comments channel + /// (`ahp-session://comments`). Surfaced so badge UI can render + /// thread / comment counts without subscribing. Absent when the session + /// does not expose a comments channel. + public var comments: CommentsSummary? public init( resource: String? = nil, @@ -182,7 +187,8 @@ public struct PartialSessionSummary: Codable, Sendable { model: ModelSelection? = nil, agent: AgentSelection? = nil, workingDirectory: String? = nil, - changes: ChangesSummary? = nil + changes: ChangesSummary? = nil, + comments: CommentsSummary? = nil ) { self.resource = resource self.provider = provider @@ -196,5 +202,6 @@ public struct PartialSessionSummary: Codable, Sendable { self.agent = agent self.workingDirectory = workingDirectory self.changes = changes + self.comments = comments } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index d6fbd9cb..a3b43ec4 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -809,6 +809,11 @@ public struct SessionSummary: Codable, Sendable { /// session's footprint (e.g., for list rendering) without requiring the /// client to subscribe to a changeset. public var changes: ChangesSummary? + /// Lightweight summary of this session's inline comments channel + /// (`ahp-session://comments`). Surfaced so badge UI can render + /// thread / comment counts without subscribing. Absent when the session + /// does not expose a comments channel. + public var comments: CommentsSummary? public init( resource: String, @@ -822,7 +827,8 @@ public struct SessionSummary: Codable, Sendable { model: ModelSelection? = nil, agent: AgentSelection? = nil, workingDirectory: String? = nil, - changes: ChangesSummary? = nil + changes: ChangesSummary? = nil, + comments: CommentsSummary? = nil ) { self.resource = resource self.provider = provider @@ -836,6 +842,7 @@ public struct SessionSummary: Codable, Sendable { self.agent = agent self.workingDirectory = workingDirectory self.changes = changes + self.comments = comments } } @@ -3359,6 +3366,129 @@ public struct ChangesetOperation: Codable, Sendable { } } +public struct CommentsSummary: Codable, Sendable { + /// The subscribable comments channel URI for the owning session + /// (typically `ahp-session://comments`). Surfaced explicitly even + /// though it is derivable from the session URI so badge UI does not need + /// to know the derivation rule. + public var resource: String + /// Total number of {@link CommentThread} entries in the channel. + public var threadCount: Int + /// Total number of {@link Comment} entries across every thread. + public var commentCount: Int + + public init( + resource: String, + threadCount: Int, + commentCount: Int + ) { + self.resource = resource + self.threadCount = threadCount + self.commentCount = commentCount + } +} + +public struct CommentsState: Codable, Sendable { + /// Comment threads in this channel, keyed by {@link CommentThread.id}. + public var threads: [CommentThread] + + public init( + threads: [CommentThread] + ) { + self.threads = threads + } +} + +public struct CommentThread: Codable, Sendable { + /// Stable identifier within the comments channel. Server-assigned. + public var id: String + /// Turn that produced the file versions this thread is anchored to. + /// Matches a {@link Turn.id} on the owning session. + public var turnId: String + /// The file the thread is anchored to. + public var resource: String + /// Range within {@link resource} the thread is anchored to. + public var range: TextRange + /// Comments in this thread, in dispatch order (oldest first). MUST + /// contain at least one entry. + public var comments: [Comment] + /// Server-defined opaque metadata, surfaced to tooling but not + /// interpreted by the protocol. + public var meta: [String: AnyCodable]? + + enum CodingKeys: String, CodingKey { + case id + case turnId + case resource + case range + case comments + case meta = "_meta" + } + + public init( + id: String, + turnId: String, + resource: String, + range: TextRange, + comments: [Comment], + meta: [String: AnyCodable]? = nil + ) { + self.id = id + self.turnId = turnId + self.resource = resource + self.range = range + self.comments = comments + self.meta = meta + } +} + +public struct Comment: Codable, Sendable { + /// Stable identifier within the enclosing thread. Server-assigned. + public var id: String + /// Comment body. Rendered as plain text unless the client opts into Markdown. + public var text: String + /// Server-defined opaque metadata, surfaced to tooling but not + /// interpreted by the protocol. + public var meta: [String: AnyCodable]? + + enum CodingKeys: String, CodingKey { + case id + case text + case meta = "_meta" + } + + public init( + id: String, + text: String, + meta: [String: AnyCodable]? = nil + ) { + self.id = id + self.text = text + self.meta = meta + } +} + +public struct NewComment: Codable, Sendable { + /// Comment body. + public var text: String + /// Server-defined opaque metadata, forwarded onto the resulting + /// {@link Comment._meta}. + public var meta: [String: AnyCodable]? + + enum CodingKeys: String, CodingKey { + case text + case meta = "_meta" + } + + public init( + text: String, + meta: [String: AnyCodable]? = nil + ) { + self.text = text + self.meta = meta + } +} + public struct TelemetryCapabilities: Codable, Sendable { /// Channel URI (or RFC 6570 URI template) for OTLP log records /// (`otlp/exportLogs` notifications). @@ -3906,12 +4036,13 @@ public enum ToolResultContent: Codable, Sendable { } } -/// The state payload of a snapshot — root, session, terminal, or changeset state. +/// The state payload of a snapshot — root, session, terminal, changeset, or comments state. public enum SnapshotState: Codable, Sendable { case root(RootState) case session(SessionState) case terminal(TerminalState) case changeset(ChangesetState) + case comments(CommentsState) public init(from decoder: Decoder) throws { // SessionState has required `summary` field, try it first @@ -3921,6 +4052,8 @@ public enum SnapshotState: Codable, Sendable { self = .terminal(terminal) } else if let changeset = try? ChangesetState(from: decoder) { self = .changeset(changeset) + } else if let comments = try? CommentsState(from: decoder) { + self = .comments(comments) } else { self = .root(try RootState(from: decoder)) } @@ -3932,6 +4065,7 @@ public enum SnapshotState: Codable, Sendable { case .session(let state): try state.encode(to: encoder) case .terminal(let state): try state.encode(to: encoder) case .changeset(let state): try state.encode(to: encoder) + case .comments(let state): try state.encode(to: encoder) } } } diff --git a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift index be111921..12c65ac7 100644 --- a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift +++ b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift @@ -89,9 +89,9 @@ final class FixtureDrivenReducerTests: XCTestCase { var skipped: [(file: String, description: String, message: String)] = [] for (file, fixture) in Self.fixtures { - // Skip terminal/changeset/resourceWatch fixtures — those reducers - // are not yet implemented in Swift - if fixture.reducer == "terminal" || fixture.reducer == "changeset" || fixture.reducer == "resourceWatch" { + // Skip terminal/changeset/comments/resourceWatch fixtures — + // those reducers are not yet implemented in Swift + if fixture.reducer == "terminal" || fixture.reducer == "changeset" || fixture.reducer == "comments" || fixture.reducer == "resourceWatch" { continue } diff --git a/clients/swift/CHANGELOG.md b/clients/swift/CHANGELOG.md index 4a4408e2..361a5f73 100644 --- a/clients/swift/CHANGELOG.md +++ b/clients/swift/CHANGELOG.md @@ -24,6 +24,15 @@ the tag matches the version pinned in [`VERSION`](VERSION). `idle → running → error` lifecycle of a changeset operation. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. +- New comments channel wire types (`ahp-session://comments`): + `CommentsState`, `CommentThread`, `Comment`, `NewComment`, + `CommentsSummary`; the + `comments/threadSet` / `comments/threadRemoved` / `comments/commentSet` + / `comments/commentRemoved` / `comments/cleared` cases on `StateAction`; + `CreateCommentThreadParams/Result`, `UpdateCommentThreadParams`, + `DeleteCommentThreadParams`, `AddCommentParams/Result`, + `EditCommentParams`, `DeleteCommentParams`; and `SnapshotState.comments`. + Reducer logic is deferred (matches the changeset/resource-watch parity). ### Changed diff --git a/clients/typescript/CHANGELOG.md b/clients/typescript/CHANGELOG.md index 5d80d4e7..67ffdb92 100644 --- a/clients/typescript/CHANGELOG.md +++ b/clients/typescript/CHANGELOG.md @@ -27,6 +27,13 @@ hotfix escape hatch. `idle → running → error` lifecycle of a changeset operation. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. +- New comments channel (`ahp-session://comments`): `CommentsState`, + `CommentThread`, `Comment`, `NewComment`, `CommentsSummary`, + the `commentsReducer`, the `comments/threadSet`, `comments/threadRemoved`, + `comments/commentSet`, `comments/commentRemoved`, `comments/cleared` actions, + and the `createCommentThread`, `updateCommentThread`, `deleteCommentThread`, + `addComment`, `editComment`, `deleteComment` commands. `SessionSummary.comments` + surfaces the per-session `CommentsSummary` for badge UI. ### Changed diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 9dc8e18b..e54bdae9 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -87,6 +87,7 @@ export default withMermaid(defineConfig({ { text: 'Session Channel', link: '/reference/session' }, { text: 'Terminal Channel', link: '/reference/terminal' }, { text: 'Changeset Channel', link: '/reference/changeset' }, + { text: 'Comments Channel', link: '/reference/comments' }, { text: 'Telemetry Channel', link: '/reference/otlp' }, ], }, diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 5eed1229..bdb397f8 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -1474,6 +1474,96 @@ "type" ] }, + "CommentsThreadSetAction": { + "type": "object", + "description": "Upsert a {@link CommentThread} in the comments channel — adds a new\nthread, or replaces an existing one identified by\n{@link CommentThread.id}. When replacing, the full thread payload\n(including its {@link CommentThread.comments | comments} list) is\nsubstituted; producers SHOULD prefer {@link CommentsCommentSetAction}\nfor per-comment edits to keep wire updates small.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.CommentsThreadSet" + }, + "thread": { + "$ref": "#/$defs/CommentThread", + "description": "The new or replacement thread. MUST contain at least one comment." + } + }, + "required": [ + "type", + "thread" + ] + }, + "CommentsThreadRemovedAction": { + "type": "object", + "description": "Remove a {@link CommentThread} from the channel by its id.\n\nThe server emits this in two cases:\n1. The client explicitly invoked\n {@link DeleteCommentThreadParams | `deleteCommentThread`}.\n2. The client invoked {@link DeleteCommentParams | `deleteComment`} on\n the last remaining comment in the thread — the protocol collapses\n the thread rather than leaving an empty one behind.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.CommentsThreadRemoved" + }, + "threadId": { + "type": "string", + "description": "The {@link CommentThread.id} of the thread to remove." + } + }, + "required": [ + "type", + "threadId" + ] + }, + "CommentsCommentSetAction": { + "type": "object", + "description": "Upsert a {@link Comment} within an existing thread — adds a new\ncomment, or replaces one identified by {@link Comment.id}. If\n{@link threadId} does not match any current thread the action is a\nno-op.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.CommentsCommentSet" + }, + "threadId": { + "type": "string", + "description": "The {@link CommentThread.id} the comment belongs to." + }, + "comment": { + "$ref": "#/$defs/Comment", + "description": "The new or replacement comment." + } + }, + "required": [ + "type", + "threadId", + "comment" + ] + }, + "CommentsCommentRemovedAction": { + "type": "object", + "description": "Remove a single {@link Comment} from a thread without collapsing the\nthread itself. Used when more than one comment remains — the server\nMUST dispatch {@link CommentsThreadRemovedAction} instead when removing\nthe last comment would otherwise leave the thread empty.\n\nIf either {@link threadId} or {@link commentId} does not match the\ncurrent state the action is a no-op.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.CommentsCommentRemoved" + }, + "threadId": { + "type": "string", + "description": "The {@link CommentThread.id} the comment belongs to." + }, + "commentId": { + "type": "string", + "description": "The {@link Comment.id} to remove." + } + }, + "required": [ + "type", + "threadId", + "commentId" + ] + }, + "CommentsClearedAction": { + "type": "object", + "description": "Drop every thread from the comments channel.\n\nDispatched when the owning session is going away and the channel is\nabout to become un-subscribable. Clients SHOULD release references on\nreceipt and react to the corresponding session-level lifecycle signal\n(e.g. `root/sessionRemoved`) to fully tear down UI.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.CommentsCleared" + } + }, + "required": [ + "type" + ] + }, "ResourceWatchChangedAction": { "type": "object", "description": "A batch of resource changes observed by the watcher.\n\nWatch events are coalesced into batches by the server to keep the\naction stream tractable; an empty `changes.items` list MUST NOT be\ndispatched. The reducer does not retain change history — these\nactions exist purely to deliver events to subscribers, who consume\nthem directly off the action stream and apply their own logic.", @@ -1916,6 +2006,9 @@ }, { "$ref": "#/$defs/ChangesetState" + }, + { + "$ref": "#/$defs/CommentsState" } ], "description": "The current state of the resource" @@ -2292,6 +2385,10 @@ "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." + }, + "comments": { + "$ref": "#/$defs/CommentsSummary", + "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." } }, "required": [ @@ -4993,6 +5090,127 @@ "status" ] }, + "CommentsSummary": { + "type": "object", + "description": "Lightweight per-session summary of the comments channel, surfaced on\n{@link SessionSummary.comments} so badge UI can render thread / comment\ncounts without subscribing to the channel itself.", + "properties": { + "resource": { + "$ref": "#/$defs/URI", + "description": "The subscribable comments channel URI for the owning session\n(typically `ahp-session://comments`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." + }, + "threadCount": { + "type": "number", + "description": "Total number of {@link CommentThread} entries in the channel." + }, + "commentCount": { + "type": "number", + "description": "Total number of {@link Comment} entries across every thread." + } + }, + "required": [ + "resource", + "threadCount", + "commentCount" + ] + }, + "CommentsState": { + "type": "object", + "description": "Full state for a session's comments channel, returned when a client\nsubscribes to an `ahp-session://comments` URI.", + "properties": { + "threads": { + "type": "array", + "items": { + "$ref": "#/$defs/CommentThread" + }, + "description": "Comment threads in this channel, keyed by {@link CommentThread.id}." + } + }, + "required": [ + "threads" + ] + }, + "CommentThread": { + "type": "object", + "description": "A conversation anchored to a specific range in a specific file produced\nby a specific turn.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "properties": { + "id": { + "type": "string", + "description": "Stable identifier within the comments channel. Server-assigned." + }, + "turnId": { + "type": "string", + "description": "Turn that produced the file versions this thread is anchored to.\nMatches a {@link Turn.id} on the owning session." + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "The file the thread is anchored to." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Range within {@link resource} the thread is anchored to." + }, + "comments": { + "type": "array", + "items": { + "$ref": "#/$defs/Comment" + }, + "description": "Comments in this thread, in dispatch order (oldest first). MUST\ncontain at least one entry." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + } + }, + "required": [ + "id", + "turnId", + "resource", + "range", + "comments" + ] + }, + "Comment": { + "type": "object", + "description": "A single comment within a {@link CommentThread}.", + "properties": { + "id": { + "type": "string", + "description": "Stable identifier within the enclosing thread. Server-assigned." + }, + "text": { + "type": "string", + "description": "Comment body. Rendered as plain text unless the client opts into Markdown." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + } + }, + "required": [ + "id", + "text" + ] + }, + "NewComment": { + "type": "object", + "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", + "properties": { + "text": { + "type": "string", + "description": "Comment body." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link Comment._meta}." + } + }, + "required": [ + "text" + ] + }, "TelemetryCapabilities": { "type": "object", "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", @@ -5479,6 +5697,21 @@ { "$ref": "#/$defs/ChangesetClearedAction" }, + { + "$ref": "#/$defs/CommentsThreadSetAction" + }, + { + "$ref": "#/$defs/CommentsThreadRemovedAction" + }, + { + "$ref": "#/$defs/CommentsCommentSetAction" + }, + { + "$ref": "#/$defs/CommentsCommentRemovedAction" + }, + { + "$ref": "#/$defs/CommentsClearedAction" + }, { "$ref": "#/$defs/TerminalDataAction" }, diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 4b2bbac0..1efdf096 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -1101,6 +1101,192 @@ } } }, + "CreateCommentThreadParams": { + "type": "object", + "description": "Create a new {@link CommentThread} anchored to a file range from a\nspecific turn.\n\nThe initial comment is required — the protocol forbids empty threads,\nso thread creation and first-comment creation are fused into one\ncommand. The server assigns both {@link CreateCommentThreadResult.threadId}\nand {@link CreateCommentThreadResult.commentId}, then broadcasts a\n{@link CommentsThreadSetAction} on the channel.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI, e.g. `ahp-session://comments`." + }, + "turnId": { + "type": "string", + "description": "Turn whose file versions {@link resource} + {@link range} address." + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "Anchored file URI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Anchored range within {@link resource}." + }, + "comment": { + "$ref": "#/$defs/NewComment", + "description": "First comment in the thread. The server assigns its {@link Comment.id}." + } + }, + "required": [ + "channel", + "turnId", + "resource", + "range", + "comment" + ] + }, + "CreateCommentThreadResult": { + "type": "object", + "description": "Result of {@link CreateCommentThreadParams | `createCommentThread`}.", + "properties": { + "threadId": { + "type": "string", + "description": "Server-assigned {@link CommentThread.id}." + }, + "commentId": { + "type": "string", + "description": "Server-assigned {@link Comment.id} of the initial comment." + } + }, + "required": [ + "threadId", + "commentId" + ] + }, + "UpdateCommentThreadParams": { + "type": "object", + "description": "Re-anchor an existing {@link CommentThread} — typically used to re-pin\na thread to a different range or a newer turn after an edit. Comments\nthemselves are not modified by this command; use\n{@link AddCommentParams | `addComment`},\n{@link EditCommentParams | `editComment`}, or\n{@link DeleteCommentParams | `deleteComment`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting thread state as a {@link CommentsThreadSetAction}.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI." + }, + "threadId": { + "type": "string", + "description": "The {@link CommentThread.id} to update." + }, + "turnId": { + "type": "string", + "description": "New {@link CommentThread.turnId}, if changing." + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "New anchored file URI, if changing." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "New anchored range, if changing." + } + }, + "required": [ + "channel", + "threadId" + ] + }, + "DeleteCommentThreadParams": { + "type": "object", + "description": "Delete an entire comment thread (and every comment it contains). The\nserver echoes a {@link CommentsThreadRemovedAction} on the channel.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI." + }, + "threadId": { + "type": "string", + "description": "The {@link CommentThread.id} to delete." + } + }, + "required": [ + "channel", + "threadId" + ] + }, + "AddCommentParams": { + "type": "object", + "description": "Append a new {@link Comment} to an existing thread. The server assigns\nthe resulting {@link Comment.id} and echoes a\n{@link CommentsCommentSetAction}.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI." + }, + "threadId": { + "type": "string", + "description": "Thread that receives the new comment." + }, + "comment": { + "$ref": "#/$defs/NewComment", + "description": "Comment payload — the server assigns the id." + } + }, + "required": [ + "channel", + "threadId", + "comment" + ] + }, + "AddCommentResult": { + "type": "object", + "description": "Result of {@link AddCommentParams | `addComment`}.", + "properties": { + "commentId": { + "type": "string", + "description": "Server-assigned {@link Comment.id} of the new comment." + } + }, + "required": [ + "commentId" + ] + }, + "EditCommentParams": { + "type": "object", + "description": "Edit the body of an existing comment in place. The server echoes a\n{@link CommentsCommentSetAction} carrying the updated comment.\n\nOnly the body is mutable through this command; to change\n{@link Comment.source} or {@link Comment._meta} delete and re-create\nthe comment.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI." + }, + "threadId": { + "type": "string", + "description": "Enclosing thread." + }, + "commentId": { + "type": "string", + "description": "{@link Comment.id} to edit." + }, + "text": { + "type": "string", + "description": "New comment body." + } + }, + "required": [ + "channel", + "threadId", + "commentId", + "text" + ] + }, + "DeleteCommentParams": { + "type": "object", + "description": "Remove a single comment from a thread.\n\nIf the removal would leave the thread empty (i.e. the targeted comment\nis the only one remaining), the server collapses the thread instead\n— it dispatches a {@link CommentsThreadRemovedAction} and the thread\ndisappears from {@link CommentsState.threads}. Otherwise the server\nechoes a {@link CommentsCommentRemovedAction}.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI." + }, + "threadId": { + "type": "string", + "description": "Enclosing thread." + }, + "commentId": { + "type": "string", + "description": "{@link Comment.id} to remove." + } + }, + "required": [ + "channel", + "threadId", + "commentId" + ] + }, "CreateResourceWatchParams": { "type": "object", "description": "Creates a resource watcher on the receiver's filesystem.\n\nThe receiver allocates an `ahp-resource-watch:/` channel URI and\nreturns it on {@link CreateResourceWatchResult.channel}. The caller then\n[`subscribe`](./subscriptions)s to that channel to receive\n`resourceWatch/changed` actions over the standard action envelope.\n\nThe watch lifecycle is tied to subscription: when every subscriber has\nunsubscribed (or the underlying connection drops), the receiver MUST\nrelease the watcher. There is no explicit dispose command — `unsubscribe`\nis the only handle the caller needs.\n\nLike the rest of the `resource*` family, `createResourceWatch` is\nsymmetrical and MAY be sent in either direction. Access is gated through\nthe same permission flow as `resourceRead`/`resourceWrite`.", @@ -1567,6 +1753,9 @@ }, { "$ref": "#/$defs/ChangesetState" + }, + { + "$ref": "#/$defs/CommentsState" } ], "description": "The current state of the resource" @@ -1943,6 +2132,10 @@ "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." + }, + "comments": { + "$ref": "#/$defs/CommentsSummary", + "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." } }, "required": [ @@ -4644,6 +4837,127 @@ "status" ] }, + "CommentsSummary": { + "type": "object", + "description": "Lightweight per-session summary of the comments channel, surfaced on\n{@link SessionSummary.comments} so badge UI can render thread / comment\ncounts without subscribing to the channel itself.", + "properties": { + "resource": { + "$ref": "#/$defs/URI", + "description": "The subscribable comments channel URI for the owning session\n(typically `ahp-session://comments`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." + }, + "threadCount": { + "type": "number", + "description": "Total number of {@link CommentThread} entries in the channel." + }, + "commentCount": { + "type": "number", + "description": "Total number of {@link Comment} entries across every thread." + } + }, + "required": [ + "resource", + "threadCount", + "commentCount" + ] + }, + "CommentsState": { + "type": "object", + "description": "Full state for a session's comments channel, returned when a client\nsubscribes to an `ahp-session://comments` URI.", + "properties": { + "threads": { + "type": "array", + "items": { + "$ref": "#/$defs/CommentThread" + }, + "description": "Comment threads in this channel, keyed by {@link CommentThread.id}." + } + }, + "required": [ + "threads" + ] + }, + "CommentThread": { + "type": "object", + "description": "A conversation anchored to a specific range in a specific file produced\nby a specific turn.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "properties": { + "id": { + "type": "string", + "description": "Stable identifier within the comments channel. Server-assigned." + }, + "turnId": { + "type": "string", + "description": "Turn that produced the file versions this thread is anchored to.\nMatches a {@link Turn.id} on the owning session." + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "The file the thread is anchored to." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Range within {@link resource} the thread is anchored to." + }, + "comments": { + "type": "array", + "items": { + "$ref": "#/$defs/Comment" + }, + "description": "Comments in this thread, in dispatch order (oldest first). MUST\ncontain at least one entry." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + } + }, + "required": [ + "id", + "turnId", + "resource", + "range", + "comments" + ] + }, + "Comment": { + "type": "object", + "description": "A single comment within a {@link CommentThread}.", + "properties": { + "id": { + "type": "string", + "description": "Stable identifier within the enclosing thread. Server-assigned." + }, + "text": { + "type": "string", + "description": "Comment body. Rendered as plain text unless the client opts into Markdown." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + } + }, + "required": [ + "id", + "text" + ] + }, + "NewComment": { + "type": "object", + "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", + "properties": { + "text": { + "type": "string", + "description": "Comment body." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link Comment._meta}." + } + }, + "required": [ + "text" + ] + }, "TelemetryCapabilities": { "type": "object", "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", @@ -6191,6 +6505,96 @@ "type" ] }, + "CommentsThreadSetAction": { + "type": "object", + "description": "Upsert a {@link CommentThread} in the comments channel — adds a new\nthread, or replaces an existing one identified by\n{@link CommentThread.id}. When replacing, the full thread payload\n(including its {@link CommentThread.comments | comments} list) is\nsubstituted; producers SHOULD prefer {@link CommentsCommentSetAction}\nfor per-comment edits to keep wire updates small.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.CommentsThreadSet" + }, + "thread": { + "$ref": "#/$defs/CommentThread", + "description": "The new or replacement thread. MUST contain at least one comment." + } + }, + "required": [ + "type", + "thread" + ] + }, + "CommentsThreadRemovedAction": { + "type": "object", + "description": "Remove a {@link CommentThread} from the channel by its id.\n\nThe server emits this in two cases:\n1. The client explicitly invoked\n {@link DeleteCommentThreadParams | `deleteCommentThread`}.\n2. The client invoked {@link DeleteCommentParams | `deleteComment`} on\n the last remaining comment in the thread — the protocol collapses\n the thread rather than leaving an empty one behind.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.CommentsThreadRemoved" + }, + "threadId": { + "type": "string", + "description": "The {@link CommentThread.id} of the thread to remove." + } + }, + "required": [ + "type", + "threadId" + ] + }, + "CommentsCommentSetAction": { + "type": "object", + "description": "Upsert a {@link Comment} within an existing thread — adds a new\ncomment, or replaces one identified by {@link Comment.id}. If\n{@link threadId} does not match any current thread the action is a\nno-op.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.CommentsCommentSet" + }, + "threadId": { + "type": "string", + "description": "The {@link CommentThread.id} the comment belongs to." + }, + "comment": { + "$ref": "#/$defs/Comment", + "description": "The new or replacement comment." + } + }, + "required": [ + "type", + "threadId", + "comment" + ] + }, + "CommentsCommentRemovedAction": { + "type": "object", + "description": "Remove a single {@link Comment} from a thread without collapsing the\nthread itself. Used when more than one comment remains — the server\nMUST dispatch {@link CommentsThreadRemovedAction} instead when removing\nthe last comment would otherwise leave the thread empty.\n\nIf either {@link threadId} or {@link commentId} does not match the\ncurrent state the action is a no-op.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.CommentsCommentRemoved" + }, + "threadId": { + "type": "string", + "description": "The {@link CommentThread.id} the comment belongs to." + }, + "commentId": { + "type": "string", + "description": "The {@link Comment.id} to remove." + } + }, + "required": [ + "type", + "threadId", + "commentId" + ] + }, + "CommentsClearedAction": { + "type": "object", + "description": "Drop every thread from the comments channel.\n\nDispatched when the owning session is going away and the channel is\nabout to become un-subscribable. Clients SHOULD release references on\nreceipt and react to the corresponding session-level lifecycle signal\n(e.g. `root/sessionRemoved`) to fully tear down UI.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.CommentsCleared" + } + }, + "required": [ + "type" + ] + }, "ResourceWatchChangedAction": { "type": "object", "description": "A batch of resource changes observed by the watcher.\n\nWatch events are coalesced into batches by the server to keep the\naction stream tractable; an empty `changes.items` list MUST NOT be\ndispatched. The reducer does not retain change history — these\nactions exist purely to deliver events to subscribers, who consume\nthem directly off the action stream and apply their own logic.", diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 7bfa2977..fdb574e2 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -499,6 +499,9 @@ }, { "$ref": "#/$defs/ChangesetState" + }, + { + "$ref": "#/$defs/CommentsState" } ], "description": "The current state of the resource" @@ -875,6 +878,10 @@ "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." + }, + "comments": { + "$ref": "#/$defs/CommentsSummary", + "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." } }, "required": [ @@ -3576,6 +3583,127 @@ "status" ] }, + "CommentsSummary": { + "type": "object", + "description": "Lightweight per-session summary of the comments channel, surfaced on\n{@link SessionSummary.comments} so badge UI can render thread / comment\ncounts without subscribing to the channel itself.", + "properties": { + "resource": { + "$ref": "#/$defs/URI", + "description": "The subscribable comments channel URI for the owning session\n(typically `ahp-session://comments`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." + }, + "threadCount": { + "type": "number", + "description": "Total number of {@link CommentThread} entries in the channel." + }, + "commentCount": { + "type": "number", + "description": "Total number of {@link Comment} entries across every thread." + } + }, + "required": [ + "resource", + "threadCount", + "commentCount" + ] + }, + "CommentsState": { + "type": "object", + "description": "Full state for a session's comments channel, returned when a client\nsubscribes to an `ahp-session://comments` URI.", + "properties": { + "threads": { + "type": "array", + "items": { + "$ref": "#/$defs/CommentThread" + }, + "description": "Comment threads in this channel, keyed by {@link CommentThread.id}." + } + }, + "required": [ + "threads" + ] + }, + "CommentThread": { + "type": "object", + "description": "A conversation anchored to a specific range in a specific file produced\nby a specific turn.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "properties": { + "id": { + "type": "string", + "description": "Stable identifier within the comments channel. Server-assigned." + }, + "turnId": { + "type": "string", + "description": "Turn that produced the file versions this thread is anchored to.\nMatches a {@link Turn.id} on the owning session." + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "The file the thread is anchored to." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Range within {@link resource} the thread is anchored to." + }, + "comments": { + "type": "array", + "items": { + "$ref": "#/$defs/Comment" + }, + "description": "Comments in this thread, in dispatch order (oldest first). MUST\ncontain at least one entry." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + } + }, + "required": [ + "id", + "turnId", + "resource", + "range", + "comments" + ] + }, + "Comment": { + "type": "object", + "description": "A single comment within a {@link CommentThread}.", + "properties": { + "id": { + "type": "string", + "description": "Stable identifier within the enclosing thread. Server-assigned." + }, + "text": { + "type": "string", + "description": "Comment body. Rendered as plain text unless the client opts into Markdown." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + } + }, + "required": [ + "id", + "text" + ] + }, + "NewComment": { + "type": "object", + "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", + "properties": { + "text": { + "type": "string", + "description": "Comment body." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link Comment._meta}." + } + }, + "required": [ + "text" + ] + }, "TelemetryCapabilities": { "type": "object", "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", @@ -4750,6 +4878,192 @@ } } }, + "CreateCommentThreadParams": { + "type": "object", + "description": "Create a new {@link CommentThread} anchored to a file range from a\nspecific turn.\n\nThe initial comment is required — the protocol forbids empty threads,\nso thread creation and first-comment creation are fused into one\ncommand. The server assigns both {@link CreateCommentThreadResult.threadId}\nand {@link CreateCommentThreadResult.commentId}, then broadcasts a\n{@link CommentsThreadSetAction} on the channel.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI, e.g. `ahp-session://comments`." + }, + "turnId": { + "type": "string", + "description": "Turn whose file versions {@link resource} + {@link range} address." + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "Anchored file URI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Anchored range within {@link resource}." + }, + "comment": { + "$ref": "#/$defs/NewComment", + "description": "First comment in the thread. The server assigns its {@link Comment.id}." + } + }, + "required": [ + "channel", + "turnId", + "resource", + "range", + "comment" + ] + }, + "CreateCommentThreadResult": { + "type": "object", + "description": "Result of {@link CreateCommentThreadParams | `createCommentThread`}.", + "properties": { + "threadId": { + "type": "string", + "description": "Server-assigned {@link CommentThread.id}." + }, + "commentId": { + "type": "string", + "description": "Server-assigned {@link Comment.id} of the initial comment." + } + }, + "required": [ + "threadId", + "commentId" + ] + }, + "UpdateCommentThreadParams": { + "type": "object", + "description": "Re-anchor an existing {@link CommentThread} — typically used to re-pin\na thread to a different range or a newer turn after an edit. Comments\nthemselves are not modified by this command; use\n{@link AddCommentParams | `addComment`},\n{@link EditCommentParams | `editComment`}, or\n{@link DeleteCommentParams | `deleteComment`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting thread state as a {@link CommentsThreadSetAction}.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI." + }, + "threadId": { + "type": "string", + "description": "The {@link CommentThread.id} to update." + }, + "turnId": { + "type": "string", + "description": "New {@link CommentThread.turnId}, if changing." + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "New anchored file URI, if changing." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "New anchored range, if changing." + } + }, + "required": [ + "channel", + "threadId" + ] + }, + "DeleteCommentThreadParams": { + "type": "object", + "description": "Delete an entire comment thread (and every comment it contains). The\nserver echoes a {@link CommentsThreadRemovedAction} on the channel.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI." + }, + "threadId": { + "type": "string", + "description": "The {@link CommentThread.id} to delete." + } + }, + "required": [ + "channel", + "threadId" + ] + }, + "AddCommentParams": { + "type": "object", + "description": "Append a new {@link Comment} to an existing thread. The server assigns\nthe resulting {@link Comment.id} and echoes a\n{@link CommentsCommentSetAction}.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI." + }, + "threadId": { + "type": "string", + "description": "Thread that receives the new comment." + }, + "comment": { + "$ref": "#/$defs/NewComment", + "description": "Comment payload — the server assigns the id." + } + }, + "required": [ + "channel", + "threadId", + "comment" + ] + }, + "AddCommentResult": { + "type": "object", + "description": "Result of {@link AddCommentParams | `addComment`}.", + "properties": { + "commentId": { + "type": "string", + "description": "Server-assigned {@link Comment.id} of the new comment." + } + }, + "required": [ + "commentId" + ] + }, + "EditCommentParams": { + "type": "object", + "description": "Edit the body of an existing comment in place. The server echoes a\n{@link CommentsCommentSetAction} carrying the updated comment.\n\nOnly the body is mutable through this command; to change\n{@link Comment.source} or {@link Comment._meta} delete and re-create\nthe comment.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI." + }, + "threadId": { + "type": "string", + "description": "Enclosing thread." + }, + "commentId": { + "type": "string", + "description": "{@link Comment.id} to edit." + }, + "text": { + "type": "string", + "description": "New comment body." + } + }, + "required": [ + "channel", + "threadId", + "commentId", + "text" + ] + }, + "DeleteCommentParams": { + "type": "object", + "description": "Remove a single comment from a thread.\n\nIf the removal would leave the thread empty (i.e. the targeted comment\nis the only one remaining), the server collapses the thread instead\n— it dispatches a {@link CommentsThreadRemovedAction} and the thread\ndisappears from {@link CommentsState.threads}. Otherwise the server\nechoes a {@link CommentsCommentRemovedAction}.", + "properties": { + "channel": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI." + }, + "threadId": { + "type": "string", + "description": "Enclosing thread." + }, + "commentId": { + "type": "string", + "description": "{@link Comment.id} to remove." + } + }, + "required": [ + "channel", + "threadId", + "commentId" + ] + }, "CreateResourceWatchParams": { "type": "object", "description": "Creates a resource watcher on the receiver's filesystem.\n\nThe receiver allocates an `ahp-resource-watch:/` channel URI and\nreturns it on {@link CreateResourceWatchResult.channel}. The caller then\n[`subscribe`](./subscriptions)s to that channel to receive\n`resourceWatch/changed` actions over the standard action envelope.\n\nThe watch lifecycle is tied to subscription: when every subscriber has\nunsubscribed (or the underlying connection drops), the receiver MUST\nrelease the watcher. There is no explicit dispose command — `unsubscribe`\nis the only handle the caller needs.\n\nLike the rest of the `resource*` family, `createResourceWatch` is\nsymmetrical and MAY be sent in either direction. Access is gated through\nthe same permission flow as `resourceRead`/`resourceWrite`.", diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index b93d9cda..a0a77e90 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -125,6 +125,10 @@ "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." + }, + "comments": { + "$ref": "#/$defs/CommentsSummary", + "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." } }, "description": "Mutable summary fields that changed; omitted fields are unchanged.\n\nIdentity fields (`resource`, `provider`, `createdAt`) never change and\nMUST be omitted by senders; receivers SHOULD ignore them if present." @@ -624,6 +628,9 @@ }, { "$ref": "#/$defs/ChangesetState" + }, + { + "$ref": "#/$defs/CommentsState" } ], "description": "The current state of the resource" @@ -1000,6 +1007,10 @@ "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." + }, + "comments": { + "$ref": "#/$defs/CommentsSummary", + "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." } }, "required": [ @@ -3701,6 +3712,127 @@ "status" ] }, + "CommentsSummary": { + "type": "object", + "description": "Lightweight per-session summary of the comments channel, surfaced on\n{@link SessionSummary.comments} so badge UI can render thread / comment\ncounts without subscribing to the channel itself.", + "properties": { + "resource": { + "$ref": "#/$defs/URI", + "description": "The subscribable comments channel URI for the owning session\n(typically `ahp-session://comments`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." + }, + "threadCount": { + "type": "number", + "description": "Total number of {@link CommentThread} entries in the channel." + }, + "commentCount": { + "type": "number", + "description": "Total number of {@link Comment} entries across every thread." + } + }, + "required": [ + "resource", + "threadCount", + "commentCount" + ] + }, + "CommentsState": { + "type": "object", + "description": "Full state for a session's comments channel, returned when a client\nsubscribes to an `ahp-session://comments` URI.", + "properties": { + "threads": { + "type": "array", + "items": { + "$ref": "#/$defs/CommentThread" + }, + "description": "Comment threads in this channel, keyed by {@link CommentThread.id}." + } + }, + "required": [ + "threads" + ] + }, + "CommentThread": { + "type": "object", + "description": "A conversation anchored to a specific range in a specific file produced\nby a specific turn.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "properties": { + "id": { + "type": "string", + "description": "Stable identifier within the comments channel. Server-assigned." + }, + "turnId": { + "type": "string", + "description": "Turn that produced the file versions this thread is anchored to.\nMatches a {@link Turn.id} on the owning session." + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "The file the thread is anchored to." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Range within {@link resource} the thread is anchored to." + }, + "comments": { + "type": "array", + "items": { + "$ref": "#/$defs/Comment" + }, + "description": "Comments in this thread, in dispatch order (oldest first). MUST\ncontain at least one entry." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + } + }, + "required": [ + "id", + "turnId", + "resource", + "range", + "comments" + ] + }, + "Comment": { + "type": "object", + "description": "A single comment within a {@link CommentThread}.", + "properties": { + "id": { + "type": "string", + "description": "Stable identifier within the enclosing thread. Server-assigned." + }, + "text": { + "type": "string", + "description": "Comment body. Rendered as plain text unless the client opts into Markdown." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + } + }, + "required": [ + "id", + "text" + ] + }, + "NewComment": { + "type": "object", + "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", + "properties": { + "text": { + "type": "string", + "description": "Comment body." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link Comment._meta}." + } + }, + "required": [ + "text" + ] + }, "TelemetryCapabilities": { "type": "object", "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", diff --git a/schema/state.schema.json b/schema/state.schema.json index 52e5371f..8de19bd7 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -410,6 +410,9 @@ }, { "$ref": "#/$defs/ChangesetState" + }, + { + "$ref": "#/$defs/CommentsState" } ], "description": "The current state of the resource" @@ -786,6 +789,10 @@ "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." + }, + "comments": { + "$ref": "#/$defs/CommentsSummary", + "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." } }, "required": [ @@ -3487,6 +3494,127 @@ "status" ] }, + "CommentsSummary": { + "type": "object", + "description": "Lightweight per-session summary of the comments channel, surfaced on\n{@link SessionSummary.comments} so badge UI can render thread / comment\ncounts without subscribing to the channel itself.", + "properties": { + "resource": { + "$ref": "#/$defs/URI", + "description": "The subscribable comments channel URI for the owning session\n(typically `ahp-session://comments`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." + }, + "threadCount": { + "type": "number", + "description": "Total number of {@link CommentThread} entries in the channel." + }, + "commentCount": { + "type": "number", + "description": "Total number of {@link Comment} entries across every thread." + } + }, + "required": [ + "resource", + "threadCount", + "commentCount" + ] + }, + "CommentsState": { + "type": "object", + "description": "Full state for a session's comments channel, returned when a client\nsubscribes to an `ahp-session://comments` URI.", + "properties": { + "threads": { + "type": "array", + "items": { + "$ref": "#/$defs/CommentThread" + }, + "description": "Comment threads in this channel, keyed by {@link CommentThread.id}." + } + }, + "required": [ + "threads" + ] + }, + "CommentThread": { + "type": "object", + "description": "A conversation anchored to a specific range in a specific file produced\nby a specific turn.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "properties": { + "id": { + "type": "string", + "description": "Stable identifier within the comments channel. Server-assigned." + }, + "turnId": { + "type": "string", + "description": "Turn that produced the file versions this thread is anchored to.\nMatches a {@link Turn.id} on the owning session." + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "The file the thread is anchored to." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Range within {@link resource} the thread is anchored to." + }, + "comments": { + "type": "array", + "items": { + "$ref": "#/$defs/Comment" + }, + "description": "Comments in this thread, in dispatch order (oldest first). MUST\ncontain at least one entry." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + } + }, + "required": [ + "id", + "turnId", + "resource", + "range", + "comments" + ] + }, + "Comment": { + "type": "object", + "description": "A single comment within a {@link CommentThread}.", + "properties": { + "id": { + "type": "string", + "description": "Stable identifier within the enclosing thread. Server-assigned." + }, + "text": { + "type": "string", + "description": "Comment body. Rendered as plain text unless the client opts into Markdown." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + } + }, + "required": [ + "id", + "text" + ] + }, + "NewComment": { + "type": "object", + "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", + "properties": { + "text": { + "type": "string", + "description": "Comment body." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link Comment._meta}." + } + }, + "required": [ + "text" + ] + }, "TelemetryCapabilities": { "type": "object", "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", diff --git a/scripts/find-protocol-sources.ts b/scripts/find-protocol-sources.ts index da7cac8a..f77ee23c 100644 --- a/scripts/find-protocol-sources.ts +++ b/scripts/find-protocol-sources.ts @@ -20,6 +20,7 @@ export const PROTOCOL_SOURCE_DIRS: readonly string[] = [ 'channels-session', 'channels-terminal', 'channels-changeset', + 'channels-comments', 'channels-otlp', 'channels-resource-watch', ]; diff --git a/scripts/generate-action-origin.ts b/scripts/generate-action-origin.ts index e976980f..31002113 100644 --- a/scripts/generate-action-origin.ts +++ b/scripts/generate-action-origin.ts @@ -17,7 +17,7 @@ const GENERATED_HEADER = `// Generated from types/actions.ts — do not edit // Run \`npm run generate\` to regenerate. `; -type ActionScope = 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; +type ActionScope = 'root' | 'session' | 'terminal' | 'changeset' | 'comments' | 'resourceWatch'; interface ActionInfo { /** The interface name (e.g. 'RootAgentsChangedAction') */ @@ -152,6 +152,7 @@ export function generateActionOrigin(project: Project, outDir: string): void { const scope: ActionScope = category === 'Root Actions' ? 'root' : category === 'Terminal Actions' ? 'terminal' : category === 'Changeset Actions' ? 'changeset' + : category === 'Comments Actions' ? 'comments' : category === 'Resource Watch Actions' ? 'resourceWatch' : 'session'; const isClientDispatchable = hasJsDocTag(node as any, 'clientDispatchable'); @@ -202,6 +203,7 @@ export function generateActionOrigin(project: Project, outDir: string): void { const sessionActions = actions.filter(a => a.scope === 'session'); const terminalActions = actions.filter(a => a.scope === 'terminal'); const changesetActions = actions.filter(a => a.scope === 'changeset'); + const commentsActions = actions.filter(a => a.scope === 'comments'); const resourceWatchActions = actions.filter(a => a.scope === 'resourceWatch'); const clientRootActions = rootActions.filter(a => a.isClientDispatchable); const serverRootActions = rootActions.filter(a => !a.isClientDispatchable); @@ -211,6 +213,8 @@ export function generateActionOrigin(project: Project, outDir: string): void { const serverTerminalActions = terminalActions.filter(a => !a.isClientDispatchable); const clientChangesetActions = changesetActions.filter(a => a.isClientDispatchable); const serverChangesetActions = changesetActions.filter(a => !a.isClientDispatchable); + const clientCommentsActions = commentsActions.filter(a => a.isClientDispatchable); + const serverCommentsActions = commentsActions.filter(a => !a.isClientDispatchable); const clientResourceWatchActions = resourceWatchActions.filter(a => a.isClientDispatchable); const serverResourceWatchActions = resourceWatchActions.filter(a => !a.isClientDispatchable); @@ -345,6 +349,45 @@ export function generateActionOrigin(project: Project, outDir: string): void { lines.push(`;`); lines.push(``); + // CommentsAction + lines.push(`/** Union of all comments-scoped actions. */`); + lines.push(`export type CommentsAction =`); + if (commentsActions.length === 0) { + lines.push(` never`); + } else { + for (let i = 0; i < commentsActions.length; i++) { + lines.push(` | ${commentsActions[i].name}`); + } + } + lines.push(`;`); + lines.push(``); + + // ClientCommentsAction + lines.push(`/** Union of comments actions that clients may dispatch. */`); + lines.push(`export type ClientCommentsAction =`); + if (clientCommentsActions.length === 0) { + lines.push(` never`); + } else { + for (let i = 0; i < clientCommentsActions.length; i++) { + lines.push(` | ${clientCommentsActions[i].name}`); + } + } + lines.push(`;`); + lines.push(``); + + // ServerCommentsAction + lines.push(`/** Union of comments actions that only the server may produce. */`); + lines.push(`export type ServerCommentsAction =`); + if (serverCommentsActions.length === 0) { + lines.push(` never`); + } else { + for (let i = 0; i < serverCommentsActions.length; i++) { + lines.push(` | ${serverCommentsActions[i].name}`); + } + } + lines.push(`;`); + lines.push(``); + // ResourceWatchAction lines.push(`/** Union of all resource-watch-scoped actions. */`); lines.push(`export type ResourceWatchAction =`); diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 35f8d68b..8d359436 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -157,7 +157,8 @@ function mapType(tsType: string): string { tsType === 'IRootState | ISessionState | ITerminalState' || tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState' + tsType === 'RootState | SessionState | TerminalState | ChangesetState' || + tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState' ) { return 'SnapshotState'; } @@ -725,6 +726,11 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'ChangesetState' }, { name: 'ChangesetFile' }, { name: 'ChangesetOperation' }, + { name: 'CommentsSummary' }, + { name: 'CommentsState' }, + { name: 'CommentThread' }, + { name: 'Comment' }, + { name: 'NewComment' }, { name: 'TelemetryCapabilities' }, { name: 'ResourceWatchState' }, { name: 'ResourceChange' }, @@ -890,14 +896,16 @@ const CUSTOMIZATION_LOAD_STATE_UNION: UnionConfig = { function generateSnapshotState(): string { return `// SnapshotState is the state payload of a snapshot — root, session, -// terminal, or changeset state. The active variant is chosen by which -// pointer field is non-nil; UnmarshalJSON probes for required fields in -// the canonical order (session → terminal → changeset → root). +// terminal, changeset, or comments state. The active variant is chosen +// by which pointer field is non-nil; UnmarshalJSON probes for required +// fields in the canonical order (session → terminal → changeset → +// comments → root). type SnapshotState struct { \tRoot *RootState \`json:"-"\` \tSession *SessionState \`json:"-"\` \tTerminal *TerminalState \`json:"-"\` \tChangeset *ChangesetState \`json:"-"\` +\tComments *CommentsState \`json:"-"\` } // MarshalJSON encodes whichever variant is currently populated. @@ -909,6 +917,8 @@ func (s SnapshotState) MarshalJSON() ([]byte, error) { \t\treturn json.Marshal(s.Terminal) \tcase s.Changeset != nil: \t\treturn json.Marshal(s.Changeset) +\tcase s.Comments != nil: +\t\treturn json.Marshal(s.Comments) \tcase s.Root != nil: \t\treturn json.Marshal(s.Root) \tdefault: @@ -943,6 +953,12 @@ func (s *SnapshotState) UnmarshalJSON(data []byte) error { \t\t\treturn err \t\t} \t\ts.Changeset = &v +\tcase containsAll(probe, "threads"): +\t\tvar v CommentsState +\t\tif err := json.Unmarshal(data, &v); err != nil { +\t\t\treturn err +\t\t} +\t\ts.Comments = &v \tdefault: \t\tvar v RootState \t\tif err := json.Unmarshal(data, &v); err != nil { @@ -1077,6 +1093,11 @@ const ACTION_VARIANTS: { { type: 'changeset/operationsChanged', variantName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', variantName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', variantName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, + { type: 'comments/threadSet', variantName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, + { type: 'comments/threadRemoved', variantName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, + { type: 'comments/commentSet', variantName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, + { type: 'comments/commentRemoved', variantName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, + { type: 'comments/cleared', variantName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', variantName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'terminal/data', variantName: 'TerminalData', tsInterface: 'TerminalDataAction' }, { type: 'terminal/input', variantName: 'TerminalInput', tsInterface: 'TerminalInputAction' }, @@ -1221,6 +1242,12 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: str { name: 'CompletionsParams' }, { name: 'CompletionItem' }, { name: 'CompletionsResult' }, { name: 'InvokeChangesetOperationParams' }, { name: 'InvokeChangesetOperationResult' }, { name: 'ChangesetOperationFollowUp' }, + { name: 'CreateCommentThreadParams' }, { name: 'CreateCommentThreadResult' }, + { name: 'UpdateCommentThreadParams' }, + { name: 'DeleteCommentThreadParams' }, + { name: 'AddCommentParams' }, { name: 'AddCommentResult' }, + { name: 'EditCommentParams' }, + { name: 'DeleteCommentParams' }, ]; const RECONNECT_RESULT_UNION: UnionConfig = { diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 3e8a53eb..359e7ca3 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -140,7 +140,8 @@ function mapType(tsType: string): string { if ( tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState' + tsType === 'RootState | SessionState | TerminalState | ChangesetState' || + tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState' ) { return 'SnapshotState'; } @@ -625,7 +626,7 @@ internal object StringOrMarkdownSerializer : KSerializer { function generateSnapshotState(): string { return `/** - * The state payload of a snapshot — root, session, terminal, or changeset state. + * The state payload of a snapshot — root, session, terminal, changeset, or comments state. */ @Serializable(with = SnapshotStateSerializer::class) sealed interface SnapshotState { @@ -633,6 +634,7 @@ sealed interface SnapshotState { @JvmInline value class Session(val value: SessionState) : SnapshotState @JvmInline value class Terminal(val value: TerminalState) : SnapshotState @JvmInline value class Changeset(val value: ChangesetState) : SnapshotState + @JvmInline value class Comments(val value: CommentsState) : SnapshotState } internal object SnapshotStateSerializer : KSerializer { @@ -647,12 +649,14 @@ internal object SnapshotStateSerializer : KSerializer { ?: error("Expected JsonObject for SnapshotState") // Try the most distinctive shape first. SessionState has required // \`summary\`; ChangesetState has required \`status\` + \`files\`; - // TerminalState has \`uri\` / \`size\` / \`buffer\`; RootState is the - // catch-all. + // CommentsState has required \`threads\`; TerminalState has + // \`uri\` / \`size\` / \`buffer\`; RootState is the catch-all. return when { obj.containsKey("summary") -> SnapshotState.Session(input.json.decodeFromJsonElement(SessionState.serializer(), element)) obj.containsKey("status") && obj.containsKey("files") -> SnapshotState.Changeset(input.json.decodeFromJsonElement(ChangesetState.serializer(), element)) + obj.containsKey("threads") -> + SnapshotState.Comments(input.json.decodeFromJsonElement(CommentsState.serializer(), element)) obj.containsKey("size") || obj.containsKey("uri") || obj.containsKey("buffer") -> SnapshotState.Terminal(input.json.decodeFromJsonElement(TerminalState.serializer(), element)) else -> SnapshotState.Root(input.json.decodeFromJsonElement(RootState.serializer(), element)) @@ -667,6 +671,7 @@ internal object SnapshotStateSerializer : KSerializer { is SnapshotState.Session -> output.json.encodeToJsonElement(SessionState.serializer(), value.value) is SnapshotState.Terminal -> output.json.encodeToJsonElement(TerminalState.serializer(), value.value) is SnapshotState.Changeset -> output.json.encodeToJsonElement(ChangesetState.serializer(), value.value) + is SnapshotState.Comments -> output.json.encodeToJsonElement(CommentsState.serializer(), value.value) } output.encodeJsonElement(element) } @@ -780,6 +785,7 @@ const STATE_STRUCTS = [ 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', 'Changeset', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', + 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', 'NewComment', 'TelemetryCapabilities', 'ResourceWatchState', 'ResourceChange', ]; @@ -1036,6 +1042,11 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'changeset/operationsChanged', caseName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', caseName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', caseName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, + { type: 'comments/threadSet', caseName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, + { type: 'comments/threadRemoved', caseName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, + { type: 'comments/commentSet', caseName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, + { type: 'comments/commentRemoved', caseName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, + { type: 'comments/cleared', caseName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', caseName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'root/configChanged', caseName: 'RootConfigChanged', tsInterface: 'RootConfigChangedAction' }, { type: 'terminal/data', caseName: 'TerminalData', tsInterface: 'TerminalDataAction' }, @@ -1214,6 +1225,12 @@ const COMMAND_STRUCTS = [ 'CompletionsParams', 'CompletionItem', 'CompletionsResult', 'InvokeChangesetOperationParams', 'InvokeChangesetOperationResult', 'ChangesetOperationFollowUp', + 'CreateCommentThreadParams', 'CreateCommentThreadResult', + 'UpdateCommentThreadParams', + 'DeleteCommentThreadParams', + 'AddCommentParams', 'AddCommentResult', + 'EditCommentParams', + 'DeleteCommentParams', ]; const RECONNECT_RESULT_UNION: UnionConfig = { @@ -1603,6 +1620,24 @@ object AhpCommands { fun invokeChangesetOperation(id: Long, params: InvokeChangesetOperationParams): JsonRpcRequest = JsonRpcRequest(id = id, method = "invokeChangesetOperation", params = params) + + fun createCommentThread(id: Long, params: CreateCommentThreadParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "createCommentThread", params = params) + + fun updateCommentThread(id: Long, params: UpdateCommentThreadParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "updateCommentThread", params = params) + + fun deleteCommentThread(id: Long, params: DeleteCommentThreadParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "deleteCommentThread", params = params) + + fun addComment(id: Long, params: AddCommentParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "addComment", params = params) + + fun editComment(id: Long, params: EditCommentParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "editComment", params = params) + + fun deleteComment(id: Long, params: DeleteCommentParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "deleteComment", params = params) } /** diff --git a/scripts/generate-markdown.ts b/scripts/generate-markdown.ts index 36a69356..231eae72 100644 --- a/scripts/generate-markdown.ts +++ b/scripts/generate-markdown.ts @@ -53,6 +53,7 @@ const DIR_TO_PAGE: Record = { 'channels-session': 'session', 'channels-terminal': 'terminal', 'channels-changeset': 'changeset', + 'channels-comments': 'comments', 'channels-otlp': 'otlp', }; @@ -1003,6 +1004,35 @@ function generateChangesetChannelPage(project: Project): string { return lines.join('\n'); } +function generateCommentsChannelPage(project: Project): string { + currentPage = 'comments'; + const stateSf = findChannelSourceFile(project, 'channels-comments', 'state.ts'); + const actionsSf = findChannelSourceFile(project, 'channels-comments', 'actions.ts'); + const commandsSf = findChannelSourceFile(project, 'channels-comments', 'commands.ts'); + + const lines: string[] = [GENERATED_HEADER]; + lines.push('# Comments Channel\n'); + lines.push('Reference for the `ahp-session://comments` channel — per-session inline file comments. Threads anchor to a `(turnId, resource, range)` triple and always carry at least one comment; mutations flow through dedicated commands so the server can enforce the single-comment-minimum invariant.\n'); + lines.push(schemaLink('state.schema.json')); + + if (stateSf) { + lines.push('## State Types\n'); + lines.push(emitStateTypesSection([stateSf])); + } + if (actionsSf) { + lines.push('## Actions\n'); + lines.push('Mutate `CommentsState`. Scoped to a comments URI via the enclosing `ActionEnvelope.channel`. Every comments action is server-only.\n'); + lines.push(schemaLink('actions.schema.json')); + lines.push(emitActionsSection([actionsSf])); + } + if (commandsSf) { + lines.push('## Commands\n'); + lines.push(schemaLink('commands.schema.json')); + lines.push(emitCommandsSection(project, [commandsSf])); + } + return lines.join('\n'); +} + function generateOtlpChannelPage(project: Project): string { currentPage = 'otlp'; const stateSf = findChannelSourceFile(project, 'channels-otlp', 'state.ts'); @@ -1243,6 +1273,7 @@ export function generateMarkdownDocs(project: Project, outDir: string): void { { filename: 'session.md', generator: generateSessionChannelPage }, { filename: 'terminal.md', generator: generateTerminalChannelPage }, { filename: 'changeset.md', generator: generateChangesetChannelPage }, + { filename: 'comments.md', generator: generateCommentsChannelPage }, { filename: 'otlp.md', generator: generateOtlpChannelPage }, { filename: 'messages.md', generator: generateMessagesPage }, { filename: 'error-codes.md', generator: generateErrorCodesPage }, diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 59ba3101..67bbbd4e 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -146,7 +146,8 @@ function mapType(tsType: string, propName?: string, containerName?: string): str if (tsType === 'IRootState | ISessionState' || tsType === 'IRootState | ISessionState | ITerminalState' || tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' - || tsType === 'RootState | SessionState | TerminalState | ChangesetState') { + || tsType === 'RootState | SessionState | TerminalState | ChangesetState' + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState') { return 'SnapshotState'; } @@ -625,6 +626,11 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'ChangesetState' }, { name: 'ChangesetFile' }, { name: 'ChangesetOperation' }, + { name: 'CommentsSummary' }, + { name: 'CommentsState' }, + { name: 'CommentThread' }, + { name: 'Comment' }, + { name: 'NewComment' }, { name: 'TelemetryCapabilities' }, { name: 'ResourceWatchState' }, { name: 'ResourceChange' }, @@ -789,18 +795,20 @@ const CUSTOMIZATION_LOAD_STATE_UNION: UnionConfig = { }; function generateSnapshotState(): string { - return `/// The state payload of a snapshot — root, session, terminal, or -/// changeset state. + return `/// The state payload of a snapshot — root, session, terminal, +/// changeset, or comments state. /// /// Deserialized by trying session first (has required \`summary\`), then /// terminal (has required \`content\`), then changeset (has required -/// \`status\` and \`files\`), then root. +/// \`status\` and \`files\`), then comments (has required \`threads\`), +/// then root. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum SnapshotState { Session(Box), Terminal(Box), Changeset(Box), + Comments(Box), Root(Box), }`; } @@ -918,6 +926,11 @@ const ACTION_VARIANTS: { { type: 'changeset/operationsChanged', variantName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', variantName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', variantName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, + { type: 'comments/threadSet', variantName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, + { type: 'comments/threadRemoved', variantName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, + { type: 'comments/commentSet', variantName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, + { type: 'comments/commentRemoved', variantName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, + { type: 'comments/cleared', variantName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', variantName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'terminal/data', variantName: 'TerminalData', tsInterface: 'TerminalDataAction' }, { type: 'terminal/input', variantName: 'TerminalInput', tsInterface: 'TerminalInputAction' }, @@ -968,7 +981,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, Changeset, Comment, CommentThread};'); lines.push(''); // ActionType enum @@ -1076,6 +1089,12 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: s { name: 'CompletionsParams' }, { name: 'CompletionItem' }, { name: 'CompletionsResult' }, { name: 'InvokeChangesetOperationParams' }, { name: 'InvokeChangesetOperationResult' }, { name: 'ChangesetOperationFollowUp' }, + { name: 'CreateCommentThreadParams' }, { name: 'CreateCommentThreadResult' }, + { name: 'UpdateCommentThreadParams' }, + { name: 'DeleteCommentThreadParams' }, + { name: 'AddCommentParams' }, { name: 'AddCommentResult' }, + { name: 'EditCommentParams' }, + { name: 'DeleteCommentParams' }, ]; const RECONNECT_RESULT_UNION: UnionConfig = { @@ -1093,7 +1112,7 @@ function generateCommandsFile(project: Project): string { lines.push('#[allow(unused_imports)]'); lines.push('use crate::actions::{ActionEnvelope, StateAction};'); lines.push('#[allow(unused_imports)]'); - lines.push('use crate::state::{AgentSelection, ContentRef, MessageAttachment, ModelSelection, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, Turn};'); + lines.push('use crate::state::{AgentSelection, ContentRef, MessageAttachment, ModelSelection, NewComment, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, TextRange, Turn};'); lines.push(''); lines.push('// ─── Enums ────────────────────────────────────────────────────────────\n'); @@ -1176,7 +1195,7 @@ const NOTIFICATION_STRUCTS = [ function generateNotificationsFile(project: Project): string { const lines: string[] = [GENERATED_HEADER]; lines.push('#[allow(unused_imports)]'); - lines.push('use crate::state::{AgentSelection, ChangesSummary, Changeset, FileEdit, ModelSelection, ProjectInfo, SessionStatus, SessionSummary};'); + lines.push('use crate::state::{AgentSelection, ChangesSummary, Changeset, CommentsSummary, FileEdit, ModelSelection, ProjectInfo, SessionStatus, SessionSummary};'); lines.push(''); lines.push('// ─── Enums ────────────────────────────────────────────────────────────\n'); diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index b10219b8..13f8ac11 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -106,7 +106,8 @@ function mapType(tsType: string, propName?: string, containerName?: string): str // Known unions if (tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' - || tsType === 'RootState | SessionState | TerminalState | ChangesetState') return 'SnapshotState'; + || tsType === 'RootState | SessionState | TerminalState | ChangesetState' + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState') return 'SnapshotState'; // T | null → T? const nullMatch = tsType.match(/^(.+?)\s*\|\s*null$/); @@ -537,6 +538,7 @@ const STATE_STRUCTS = [ 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', 'Changeset', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', + 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', 'NewComment', 'TelemetryCapabilities', 'ResourceWatchState', 'ResourceChange', ]; @@ -751,12 +753,13 @@ public enum StringOrMarkdown: Codable, Sendable, Equatable { } function generateSnapshotState(): string { - return `/// The state payload of a snapshot — root, session, terminal, or changeset state. + return `/// The state payload of a snapshot — root, session, terminal, changeset, or comments state. public enum SnapshotState: Codable, Sendable { case root(RootState) case session(SessionState) case terminal(TerminalState) case changeset(ChangesetState) + case comments(CommentsState) public init(from decoder: Decoder) throws { // SessionState has required \`summary\` field, try it first @@ -766,6 +769,8 @@ public enum SnapshotState: Codable, Sendable { self = .terminal(terminal) } else if let changeset = try? ChangesetState(from: decoder) { self = .changeset(changeset) + } else if let comments = try? CommentsState(from: decoder) { + self = .comments(comments) } else { self = .root(try RootState(from: decoder)) } @@ -777,6 +782,7 @@ public enum SnapshotState: Codable, Sendable { case .session(let state): try state.encode(to: encoder) case .terminal(let state): try state.encode(to: encoder) case .changeset(let state): try state.encode(to: encoder) + case .comments(let state): try state.encode(to: encoder) } } }`; @@ -895,6 +901,11 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'changeset/operationsChanged', caseName: 'changesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', caseName: 'changesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', caseName: 'changesetCleared', tsInterface: 'ChangesetClearedAction' }, + { type: 'comments/threadSet', caseName: 'commentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, + { type: 'comments/threadRemoved', caseName: 'commentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, + { type: 'comments/commentSet', caseName: 'commentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, + { type: 'comments/commentRemoved', caseName: 'commentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, + { type: 'comments/cleared', caseName: 'commentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', caseName: 'rootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'root/configChanged', caseName: 'rootConfigChanged', tsInterface: 'RootConfigChangedAction' }, { type: 'terminal/data', caseName: 'terminalData', tsInterface: 'TerminalDataAction' }, @@ -1079,6 +1090,12 @@ const COMMAND_STRUCTS = [ 'CompletionsParams', 'CompletionItem', 'CompletionsResult', 'InvokeChangesetOperationParams', 'InvokeChangesetOperationResult', 'ChangesetOperationFollowUp', + 'CreateCommentThreadParams', 'CreateCommentThreadResult', + 'UpdateCommentThreadParams', + 'DeleteCommentThreadParams', + 'AddCommentParams', 'AddCommentResult', + 'EditCommentParams', + 'DeleteCommentParams', ]; const RECONNECT_RESULT_UNION: UnionConfig = { diff --git a/types/action-origin.generated.ts b/types/action-origin.generated.ts index 53d66fdc..c829466a 100644 --- a/types/action-origin.generated.ts +++ b/types/action-origin.generated.ts @@ -53,6 +53,11 @@ import type { ChangesetOperationsChangedAction, ChangesetOperationStatusChangedAction, ChangesetClearedAction, + CommentsThreadSetAction, + CommentsThreadRemovedAction, + CommentsCommentSetAction, + CommentsCommentRemovedAction, + CommentsClearedAction, TerminalDataAction, TerminalInputAction, TerminalResizedAction, @@ -242,6 +247,29 @@ export type ServerChangesetAction = | ChangesetClearedAction ; +/** Union of all comments-scoped actions. */ +export type CommentsAction = + | CommentsThreadSetAction + | CommentsThreadRemovedAction + | CommentsCommentSetAction + | CommentsCommentRemovedAction + | CommentsClearedAction +; + +/** Union of comments actions that clients may dispatch. */ +export type ClientCommentsAction = + never +; + +/** Union of comments actions that only the server may produce. */ +export type ServerCommentsAction = + | CommentsThreadSetAction + | CommentsThreadRemovedAction + | CommentsCommentSetAction + | CommentsCommentRemovedAction + | CommentsClearedAction +; + /** Union of all resource-watch-scoped actions. */ export type ResourceWatchAction = | ResourceWatchChangedAction @@ -314,6 +342,11 @@ export const IS_CLIENT_DISPATCHABLE: { readonly [K in StateAction['type']]: bool [ActionType.ChangesetOperationsChanged]: false, [ActionType.ChangesetOperationStatusChanged]: false, [ActionType.ChangesetCleared]: false, + [ActionType.CommentsThreadSet]: false, + [ActionType.CommentsThreadRemoved]: false, + [ActionType.CommentsCommentSet]: false, + [ActionType.CommentsCommentRemoved]: false, + [ActionType.CommentsCleared]: false, [ActionType.TerminalData]: false, [ActionType.TerminalInput]: true, [ActionType.TerminalResized]: true, diff --git a/types/actions.ts b/types/actions.ts index 67979c04..1601b5d9 100644 --- a/types/actions.ts +++ b/types/actions.ts @@ -12,4 +12,5 @@ export * from './channels-root/actions.js'; export * from './channels-session/actions.js'; export * from './channels-terminal/actions.js'; export * from './channels-changeset/actions.js'; +export * from './channels-comments/actions.js'; export * from './channels-resource-watch/actions.js'; diff --git a/types/channels-comments/actions.ts b/types/channels-comments/actions.ts new file mode 100644 index 00000000..edd82fed --- /dev/null +++ b/types/channels-comments/actions.ts @@ -0,0 +1,105 @@ +/** + * Comments Channel Actions — Mutations of an `ahp-session://comments` + * channel's state. + * + * Every comments action is server-only: clients drive mutations through + * the {@link CreateCommentThreadParams | `createCommentThread`}, + * {@link AddCommentParams | `addComment`}, etc. commands, and the server + * echoes the resulting state change as one of these actions. Mirrors the + * shape of the `channeset/*` action family. + * + * @module channels-comments/actions + */ + +import { ActionType } from '../common/actions.js'; +import type { Comment, CommentThread } from './state.js'; + +// ─── Comments Actions ──────────────────────────────────────────────────────── + +/** + * Upsert a {@link CommentThread} in the comments channel — adds a new + * thread, or replaces an existing one identified by + * {@link CommentThread.id}. When replacing, the full thread payload + * (including its {@link CommentThread.comments | comments} list) is + * substituted; producers SHOULD prefer {@link CommentsCommentSetAction} + * for per-comment edits to keep wire updates small. + * + * @category Comments Actions + * @version 3 + */ +export interface CommentsThreadSetAction { + type: ActionType.CommentsThreadSet; + /** The new or replacement thread. MUST contain at least one comment. */ + thread: CommentThread; +} + +/** + * Remove a {@link CommentThread} from the channel by its id. + * + * The server emits this in two cases: + * 1. The client explicitly invoked + * {@link DeleteCommentThreadParams | `deleteCommentThread`}. + * 2. The client invoked {@link DeleteCommentParams | `deleteComment`} on + * the last remaining comment in the thread — the protocol collapses + * the thread rather than leaving an empty one behind. + * + * @category Comments Actions + * @version 3 + */ +export interface CommentsThreadRemovedAction { + type: ActionType.CommentsThreadRemoved; + /** The {@link CommentThread.id} of the thread to remove. */ + threadId: string; +} + +/** + * Upsert a {@link Comment} within an existing thread — adds a new + * comment, or replaces one identified by {@link Comment.id}. If + * {@link threadId} does not match any current thread the action is a + * no-op. + * + * @category Comments Actions + * @version 3 + */ +export interface CommentsCommentSetAction { + type: ActionType.CommentsCommentSet; + /** The {@link CommentThread.id} the comment belongs to. */ + threadId: string; + /** The new or replacement comment. */ + comment: Comment; +} + +/** + * Remove a single {@link Comment} from a thread without collapsing the + * thread itself. Used when more than one comment remains — the server + * MUST dispatch {@link CommentsThreadRemovedAction} instead when removing + * the last comment would otherwise leave the thread empty. + * + * If either {@link threadId} or {@link commentId} does not match the + * current state the action is a no-op. + * + * @category Comments Actions + * @version 3 + */ +export interface CommentsCommentRemovedAction { + type: ActionType.CommentsCommentRemoved; + /** The {@link CommentThread.id} the comment belongs to. */ + threadId: string; + /** The {@link Comment.id} to remove. */ + commentId: string; +} + +/** + * Drop every thread from the comments channel. + * + * Dispatched when the owning session is going away and the channel is + * about to become un-subscribable. Clients SHOULD release references on + * receipt and react to the corresponding session-level lifecycle signal + * (e.g. `root/sessionRemoved`) to fully tear down UI. + * + * @category Comments Actions + * @version 3 + */ +export interface CommentsClearedAction { + type: ActionType.CommentsCleared; +} diff --git a/types/channels-comments/commands.ts b/types/channels-comments/commands.ts new file mode 100644 index 00000000..256d94d6 --- /dev/null +++ b/types/channels-comments/commands.ts @@ -0,0 +1,196 @@ +/** + * Comments Channel Commands — Client-driven mutations of an + * `ahp-session://comments` channel. + * + * The protocol forbids empty threads, so thread and first-comment + * creation are fused into {@link CreateCommentThreadParams | + * `createCommentThread`} and the server collapses a thread whose last + * comment is deleted into a {@link CommentsThreadRemovedAction}. Every + * accepted command echoes back through the normal `comments/*` action + * stream on the channel. + * + * @module channels-comments/commands + */ + +import type { URI, TextRange } from '../common/state.js'; +import type { BaseParams } from '../common/commands.js'; +import type { NewComment } from './state.js'; + +// ─── createCommentThread ───────────────────────────────────────────────────── + +/** + * Create a new {@link CommentThread} anchored to a file range from a + * specific turn. + * + * The initial comment is required — the protocol forbids empty threads, + * so thread creation and first-comment creation are fused into one + * command. The server assigns both {@link CreateCommentThreadResult.threadId} + * and {@link CreateCommentThreadResult.commentId}, then broadcasts a + * {@link CommentsThreadSetAction} on the channel. + * + * @category Commands + * @method createCommentThread + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface CreateCommentThreadParams extends BaseParams { + /** The comments channel URI, e.g. `ahp-session://comments`. */ + channel: URI; + /** Turn whose file versions {@link resource} + {@link range} address. */ + turnId: string; + /** Anchored file URI. */ + resource: URI; + /** Anchored range within {@link resource}. */ + range: TextRange; + /** First comment in the thread. The server assigns its {@link Comment.id}. */ + comment: NewComment; +} + +/** + * Result of {@link CreateCommentThreadParams | `createCommentThread`}. + * + * @category Commands + */ +export interface CreateCommentThreadResult { + /** Server-assigned {@link CommentThread.id}. */ + threadId: string; + /** Server-assigned {@link Comment.id} of the initial comment. */ + commentId: string; +} + +// ─── updateCommentThread ───────────────────────────────────────────────────── + +/** + * Re-anchor an existing {@link CommentThread} — typically used to re-pin + * a thread to a different range or a newer turn after an edit. Comments + * themselves are not modified by this command; use + * {@link AddCommentParams | `addComment`}, + * {@link EditCommentParams | `editComment`}, or + * {@link DeleteCommentParams | `deleteComment`} for that. + * + * Omitted optional fields preserve their current value. The server + * echoes the resulting thread state as a {@link CommentsThreadSetAction}. + * + * @category Commands + * @method updateCommentThread + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface UpdateCommentThreadParams extends BaseParams { + /** The comments channel URI. */ + channel: URI; + /** The {@link CommentThread.id} to update. */ + threadId: string; + /** New {@link CommentThread.turnId}, if changing. */ + turnId?: string; + /** New anchored file URI, if changing. */ + resource?: URI; + /** New anchored range, if changing. */ + range?: TextRange; +} + +// ─── deleteCommentThread ───────────────────────────────────────────────────── + +/** + * Delete an entire comment thread (and every comment it contains). The + * server echoes a {@link CommentsThreadRemovedAction} on the channel. + * + * @category Commands + * @method deleteCommentThread + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface DeleteCommentThreadParams extends BaseParams { + /** The comments channel URI. */ + channel: URI; + /** The {@link CommentThread.id} to delete. */ + threadId: string; +} + +// ─── addComment ────────────────────────────────────────────────────────────── + +/** + * Append a new {@link Comment} to an existing thread. The server assigns + * the resulting {@link Comment.id} and echoes a + * {@link CommentsCommentSetAction}. + * + * @category Commands + * @method addComment + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface AddCommentParams extends BaseParams { + /** The comments channel URI. */ + channel: URI; + /** Thread that receives the new comment. */ + threadId: string; + /** Comment payload — the server assigns the id. */ + comment: NewComment; +} + +/** + * Result of {@link AddCommentParams | `addComment`}. + * + * @category Commands + */ +export interface AddCommentResult { + /** Server-assigned {@link Comment.id} of the new comment. */ + commentId: string; +} + +// ─── editComment ───────────────────────────────────────────────────────────── + +/** + * Edit the body of an existing comment in place. The server echoes a + * {@link CommentsCommentSetAction} carrying the updated comment. + * + * Only the body is mutable through this command; to change + * {@link Comment.source} or {@link Comment._meta} delete and re-create + * the comment. + * + * @category Commands + * @method editComment + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface EditCommentParams extends BaseParams { + /** The comments channel URI. */ + channel: URI; + /** Enclosing thread. */ + threadId: string; + /** {@link Comment.id} to edit. */ + commentId: string; + /** New comment body. */ + text: string; +} + +// ─── deleteComment ─────────────────────────────────────────────────────────── + +/** + * Remove a single comment from a thread. + * + * If the removal would leave the thread empty (i.e. the targeted comment + * is the only one remaining), the server collapses the thread instead + * — it dispatches a {@link CommentsThreadRemovedAction} and the thread + * disappears from {@link CommentsState.threads}. Otherwise the server + * echoes a {@link CommentsCommentRemovedAction}. + * + * @category Commands + * @method deleteComment + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface DeleteCommentParams extends BaseParams { + /** The comments channel URI. */ + channel: URI; + /** Enclosing thread. */ + threadId: string; + /** {@link Comment.id} to remove. */ + commentId: string; +} diff --git a/types/channels-comments/reducer.ts b/types/channels-comments/reducer.ts new file mode 100644 index 00000000..4a01711f --- /dev/null +++ b/types/channels-comments/reducer.ts @@ -0,0 +1,94 @@ +/** + * Comments Channel Reducer — Pure reducer for `CommentsState`. + * + * @module channels-comments/reducer + */ + +import { ActionType } from '../common/actions.js'; +import type { Comment, CommentThread, CommentsState } from './state.js'; +import type { CommentsAction } from '../action-origin.generated.js'; +import { softAssertNever } from '../common/reducer-helpers.js'; + +/** + * Pure reducer for comments state. Handles every {@link CommentsAction} + * variant. + * + * Per the spec, every comments action is server-only. The reducer + * preserves the dispatch order of threads (and of comments within a + * thread): new entries are appended; `*Set` actions with a matching id + * replace in place, while actions whose target id is unknown are no-ops + * (mirroring `changeset/fileRemoved` semantics). The single-comment + * minimum invariant is enforced by the server, not the reducer — a + * malformed server that removes a thread's last comment via + * {@link CommentsCommentRemovedAction} would leave an empty thread, + * which is observable but not catastrophic. + */ +export function commentsReducer(state: CommentsState, action: CommentsAction, log?: (msg: string) => void): CommentsState { + switch (action.type) { + case ActionType.CommentsThreadSet: { + const idx = state.threads.findIndex(t => t.id === action.thread.id); + if (idx < 0) { + return { ...state, threads: [...state.threads, action.thread] }; + } + const next: CommentThread[] = [...state.threads]; + next[idx] = action.thread; + return { ...state, threads: next }; + } + + case ActionType.CommentsThreadRemoved: { + const idx = state.threads.findIndex(t => t.id === action.threadId); + if (idx < 0) { + return state; + } + const next: CommentThread[] = [...state.threads]; + next.splice(idx, 1); + return { ...state, threads: next }; + } + + case ActionType.CommentsCommentSet: { + const tIdx = state.threads.findIndex(t => t.id === action.threadId); + if (tIdx < 0) { + return state; + } + const thread = state.threads[tIdx]; + const cIdx = thread.comments.findIndex(c => c.id === action.comment.id); + let nextComments: Comment[]; + if (cIdx < 0) { + nextComments = [...thread.comments, action.comment]; + } else { + nextComments = [...thread.comments]; + nextComments[cIdx] = action.comment; + } + const nextThreads: CommentThread[] = [...state.threads]; + nextThreads[tIdx] = { ...thread, comments: nextComments }; + return { ...state, threads: nextThreads }; + } + + case ActionType.CommentsCommentRemoved: { + const tIdx = state.threads.findIndex(t => t.id === action.threadId); + if (tIdx < 0) { + return state; + } + const thread = state.threads[tIdx]; + const cIdx = thread.comments.findIndex(c => c.id === action.commentId); + if (cIdx < 0) { + return state; + } + const nextComments: Comment[] = [...thread.comments]; + nextComments.splice(cIdx, 1); + const nextThreads: CommentThread[] = [...state.threads]; + nextThreads[tIdx] = { ...thread, comments: nextComments }; + return { ...state, threads: nextThreads }; + } + + case ActionType.CommentsCleared: + if (state.threads.length === 0) { + return state; + } + return { ...state, threads: [] }; + + default: + softAssertNever(action, log); + return state; + } +} diff --git a/types/channels-comments/state.ts b/types/channels-comments/state.ts new file mode 100644 index 00000000..067de5fb --- /dev/null +++ b/types/channels-comments/state.ts @@ -0,0 +1,130 @@ +/** + * Comments Channel State Types — Per-session inline file-comment state + * exposed on the `ahp-session://comments` channel. + * + * Each session owns at most one comments channel. The channel URI is + * derived from the session URI by appending `/comments` and is also + * surfaced explicitly on {@link CommentsSummary.resource} for badge UI. + * + * @module channels-comments/state + */ + +import type { URI, TextRange } from '../common/state.js'; + +// ─── Comments Summary ──────────────────────────────────────────────────────── + +/** + * Lightweight per-session summary of the comments channel, surfaced on + * {@link SessionSummary.comments} so badge UI can render thread / comment + * counts without subscribing to the channel itself. + * + * @category Comments + */ +export interface CommentsSummary { + /** + * The subscribable comments channel URI for the owning session + * (typically `ahp-session://comments`). Surfaced explicitly even + * though it is derivable from the session URI so badge UI does not need + * to know the derivation rule. + */ + resource: URI; + /** Total number of {@link CommentThread} entries in the channel. */ + threadCount: number; + /** Total number of {@link Comment} entries across every thread. */ + commentCount: number; +} + +// ─── Comments State ────────────────────────────────────────────────────────── + +/** + * Full state for a session's comments channel, returned when a client + * subscribes to an `ahp-session://comments` URI. + * + * @category Comments + */ +export interface CommentsState { + /** Comment threads in this channel, keyed by {@link CommentThread.id}. */ + threads: CommentThread[]; +} + +// ─── Comment Thread ────────────────────────────────────────────────────────── + +/** + * A conversation anchored to a specific range in a specific file produced + * by a specific turn. + * + * {@link turnId} anchors the thread to the file versions that turn + * produced, so a later turn that rewrites the same file does not silently + * invalidate the comment's anchor — clients can resolve {@link resource} + * and {@link range} against the turn's changeset. + * + * Every thread MUST contain at least one {@link Comment}. The server + * enforces this invariant: {@link CreateCommentThreadParams | + * `createCommentThread`} requires an initial comment, and deleting the + * last remaining comment collapses the thread into a + * {@link CommentsThreadRemovedAction} rather than leaving an empty thread + * behind. + * + * @category Comments + */ +export interface CommentThread { + /** Stable identifier within the comments channel. Server-assigned. */ + id: string; + /** + * Turn that produced the file versions this thread is anchored to. + * Matches a {@link Turn.id} on the owning session. + */ + turnId: string; + /** The file the thread is anchored to. */ + resource: URI; + /** Range within {@link resource} the thread is anchored to. */ + range: TextRange; + /** + * Comments in this thread, in dispatch order (oldest first). MUST + * contain at least one entry. + */ + comments: Comment[]; + /** + * Server-defined opaque metadata, surfaced to tooling but not + * interpreted by the protocol. + */ + _meta?: Record; +} + +// ─── Comment ───────────────────────────────────────────────────────────────── + +/** + * A single comment within a {@link CommentThread}. + * + * @category Comments + */ +export interface Comment { + /** Stable identifier within the enclosing thread. Server-assigned. */ + id: string; + /** Comment body. Rendered as plain text unless the client opts into Markdown. */ + text: string; + /** + * Server-defined opaque metadata, surfaced to tooling but not + * interpreted by the protocol. + */ + _meta?: Record; +} + +// ─── New Comment ───────────────────────────────────────────────────────────── + +/** + * Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`} + * and {@link AddCommentParams | `addComment`}. The server assigns the + * resulting {@link Comment.id}. + * + * @category Comments + */ +export interface NewComment { + /** Comment body. */ + text: string; + /** + * Server-defined opaque metadata, forwarded onto the resulting + * {@link Comment._meta}. + */ + _meta?: Record; +} diff --git a/types/channels-session/state.ts b/types/channels-session/state.ts index 488ed6d8..c2948fb4 100644 --- a/types/channels-session/state.ts +++ b/types/channels-session/state.ts @@ -17,8 +17,9 @@ import type { TextSelection, UsageInfo, } from '../common/state.js'; -import type { ModelSelection } from '../channels-root/state.js'; import type { Changeset } from '../channels-changeset/state.js'; +import type { CommentsSummary } from '../channels-comments/state.js'; +import type { ModelSelection } from '../channels-root/state.js'; // ─── Pending Message Types ─────────────────────────────────────────────────── @@ -223,6 +224,13 @@ export interface SessionSummary { * client to subscribe to a changeset. */ changes?: ChangesSummary; + /** + * Lightweight summary of this session's inline comments channel + * (`ahp-session://comments`). Surfaced so badge UI can render + * thread / comment counts without subscribing. Absent when the session + * does not expose a comments channel. + */ + comments?: CommentsSummary; } /** diff --git a/types/commands.ts b/types/commands.ts index 03931e26..dc9281d0 100644 --- a/types/commands.ts +++ b/types/commands.ts @@ -12,4 +12,5 @@ export * from './channels-root/commands.js'; export * from './channels-session/commands.js'; export * from './channels-terminal/commands.js'; export * from './channels-changeset/commands.js'; +export * from './channels-comments/commands.js'; export * from './channels-resource-watch/commands.js'; diff --git a/types/common/actions.ts b/types/common/actions.ts index 1121f09d..0f47ca96 100644 --- a/types/common/actions.ts +++ b/types/common/actions.ts @@ -67,6 +67,14 @@ import type { ChangesetClearedAction, } from '../channels-changeset/actions.js'; +import type { + CommentsThreadSetAction, + CommentsThreadRemovedAction, + CommentsCommentSetAction, + CommentsCommentRemovedAction, + CommentsClearedAction, +} from '../channels-comments/actions.js'; + import type { TerminalDataAction, TerminalInputAction, @@ -141,6 +149,11 @@ export const enum ActionType { ChangesetOperationsChanged = 'changeset/operationsChanged', ChangesetOperationStatusChanged = 'changeset/operationStatusChanged', ChangesetCleared = 'changeset/cleared', + CommentsThreadSet = 'comments/threadSet', + CommentsThreadRemoved = 'comments/threadRemoved', + CommentsCommentSet = 'comments/commentSet', + CommentsCommentRemoved = 'comments/commentRemoved', + CommentsCleared = 'comments/cleared', RootTerminalsChanged = 'root/terminalsChanged', RootConfigChanged = 'root/configChanged', TerminalData = 'terminal/data', @@ -241,6 +254,11 @@ export type StateAction = | ChangesetOperationsChangedAction | ChangesetOperationStatusChangedAction | ChangesetClearedAction + | CommentsThreadSetAction + | CommentsThreadRemovedAction + | CommentsCommentSetAction + | CommentsCommentRemovedAction + | CommentsClearedAction | TerminalDataAction | TerminalInputAction | TerminalResizedAction diff --git a/types/common/messages.ts b/types/common/messages.ts index f8d8efa4..9e5ffedc 100644 --- a/types/common/messages.ts +++ b/types/common/messages.ts @@ -66,6 +66,16 @@ import type { InvokeChangesetOperationParams, InvokeChangesetOperationResult, } from '../channels-changeset/commands.js'; +import type { + CreateCommentThreadParams, + CreateCommentThreadResult, + UpdateCommentThreadParams, + DeleteCommentThreadParams, + AddCommentParams, + AddCommentResult, + EditCommentParams, + DeleteCommentParams, +} from '../channels-comments/commands.js'; import type { ActionEnvelope } from './actions.js'; import type { @@ -167,6 +177,12 @@ export interface CommandMap { 'sessionConfigCompletions': { params: SessionConfigCompletionsParams; result: SessionConfigCompletionsResult }; 'completions': { params: CompletionsParams; result: CompletionsResult }; 'invokeChangesetOperation': { params: InvokeChangesetOperationParams; result: InvokeChangesetOperationResult }; + 'createCommentThread': { params: CreateCommentThreadParams; result: CreateCommentThreadResult }; + 'updateCommentThread': { params: UpdateCommentThreadParams; result: null }; + 'deleteCommentThread': { params: DeleteCommentThreadParams; result: null }; + 'addComment': { params: AddCommentParams; result: AddCommentResult }; + 'editComment': { params: EditCommentParams; result: null }; + 'deleteComment': { params: DeleteCommentParams; result: null }; } /** diff --git a/types/common/reducer-helpers.ts b/types/common/reducer-helpers.ts index bf5ab48c..9fa3a484 100644 --- a/types/common/reducer-helpers.ts +++ b/types/common/reducer-helpers.ts @@ -14,6 +14,8 @@ import type { ClientTerminalAction, ChangesetAction, ClientChangesetAction, + CommentsAction, + ClientCommentsAction, } from '../action-origin.generated.js'; import { IS_CLIENT_DISPATCHABLE } from '../action-origin.generated.js'; @@ -38,6 +40,6 @@ export function softAssertNever(value: never, log?: (msg: string) => void): void * Servers SHOULD call this to validate incoming `dispatchAction` requests * and reject any action the client is not allowed to originate. */ -export function isClientDispatchable(action: RootAction | SessionAction | TerminalAction | ChangesetAction): action is ClientRootAction | ClientSessionAction | ClientTerminalAction | ClientChangesetAction { +export function isClientDispatchable(action: RootAction | SessionAction | TerminalAction | ChangesetAction | CommentsAction): action is ClientRootAction | ClientSessionAction | ClientTerminalAction | ClientChangesetAction | ClientCommentsAction { return IS_CLIENT_DISPATCHABLE[action.type]; } diff --git a/types/common/state.ts b/types/common/state.ts index f1f7ff59..21cd9405 100644 --- a/types/common/state.ts +++ b/types/common/state.ts @@ -11,6 +11,7 @@ import type { RootState } from '../channels-root/state.js'; import type { SessionState } from '../channels-session/state.js'; import type { TerminalState } from '../channels-terminal/state.js'; import type { ChangesetState } from '../channels-changeset/state.js'; +import type { CommentsState } from '../channels-comments/state.js'; // ─── Type Aliases ──────────────────────────────────────────────────────────── @@ -323,7 +324,7 @@ export interface Snapshot { /** The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`) */ resource: URI; /** The current state of the resource */ - state: RootState | SessionState | TerminalState | ChangesetState; + state: RootState | SessionState | TerminalState | ChangesetState | CommentsState; /** The `serverSeq` at which this snapshot was taken. Subsequent actions will have `serverSeq > fromSeq`. */ fromSeq: number; } diff --git a/types/index.ts b/types/index.ts index bd7327be..577cf66d 100644 --- a/types/index.ts +++ b/types/index.ts @@ -93,6 +93,11 @@ export type { ChangesetState, ChangesetFile, ChangesetOperation, + CommentsSummary, + CommentsState, + CommentThread, + Comment, + NewComment, TelemetryCapabilities, ResourceWatchState, ResourceChange, @@ -170,6 +175,11 @@ export type { ChangesetOperationsChangedAction, ChangesetOperationStatusChangedAction, ChangesetClearedAction, + CommentsThreadSetAction, + CommentsThreadRemovedAction, + CommentsCommentSetAction, + CommentsCommentRemovedAction, + CommentsClearedAction, StateAction, RootTerminalsChangedAction, RootConfigChangedAction, @@ -201,6 +211,9 @@ export type { ChangesetAction, ClientChangesetAction, ServerChangesetAction, + CommentsAction, + ClientCommentsAction, + ServerCommentsAction, ResourceWatchAction, ClientResourceWatchAction, ServerResourceWatchAction, @@ -214,6 +227,7 @@ export { sessionReducer, terminalReducer, changesetReducer, + commentsReducer, resourceWatchReducer, isClientDispatchable, } from './reducers.js'; @@ -279,6 +293,14 @@ export type { InvokeChangesetOperationResult, ChangesetOperationTarget, ChangesetOperationFollowUp, + CreateCommentThreadParams, + CreateCommentThreadResult, + UpdateCommentThreadParams, + DeleteCommentThreadParams, + AddCommentParams, + AddCommentResult, + EditCommentParams, + DeleteCommentParams, } from './commands.js'; export { ReconnectResultType, ContentEncoding, CompletionItemKind, ResourceType, ResourceWriteMode } from './commands.js'; diff --git a/types/messages.test.ts b/types/messages.test.ts index 9c43db72..12af7dc0 100644 --- a/types/messages.test.ts +++ b/types/messages.test.ts @@ -30,6 +30,7 @@ function readChannelSources(baseName: string): string { 'channels-session', 'channels-terminal', 'channels-changeset', + 'channels-comments', 'channels-resource-watch', ]; return dirs diff --git a/types/reducers.test.ts b/types/reducers.test.ts index 00c50f1b..208a6c8f 100644 --- a/types/reducers.test.ts +++ b/types/reducers.test.ts @@ -22,12 +22,13 @@ import { sessionReducer, terminalReducer, changesetReducer, + commentsReducer, resourceWatchReducer, isClientDispatchable, } from './reducers.js'; import { IS_CLIENT_DISPATCHABLE } from './action-origin.generated.js'; import { ActionType } from './actions.js'; -import type { RootState, SessionState, ChangesetState, ResourceWatchState } from './state.js'; +import type { RootState, SessionState, ChangesetState, CommentsState, ResourceWatchState } from './state.js'; import { SessionLifecycle, SessionStatus, @@ -55,6 +56,7 @@ function readChannelSources(baseName: string): string { 'channels-session', 'channels-terminal', 'channels-changeset', + 'channels-comments', 'channels-resource-watch', ]; return dirs @@ -73,10 +75,10 @@ function readChannelSources(baseName: string): string { interface Fixture { description: string; - reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; - initial: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; + reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'comments' | 'resourceWatch'; + initial: RootState | SessionState | TerminalState | ChangesetState | CommentsState | ResourceWatchState; actions: unknown[]; - expected: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; + expected: RootState | SessionState | TerminalState | ChangesetState | CommentsState | ResourceWatchState; } /** @@ -134,6 +136,8 @@ describe('reducer fixtures', () => { state = terminalReducer(state as TerminalState, action as any); } else if (fixture.reducer === 'changeset') { state = changesetReducer(state as ChangesetState, action as any); + } else if (fixture.reducer === 'comments') { + state = commentsReducer(state as CommentsState, action as any); } else if (fixture.reducer === 'resourceWatch') { state = resourceWatchReducer(state as ResourceWatchState, action as any); } else { diff --git a/types/reducers.ts b/types/reducers.ts index b56a7e94..6a91272b 100644 --- a/types/reducers.ts +++ b/types/reducers.ts @@ -9,5 +9,6 @@ export { rootReducer } from './channels-root/reducer.js'; export { sessionReducer } from './channels-session/reducer.js'; export { terminalReducer } from './channels-terminal/reducer.js'; export { changesetReducer } from './channels-changeset/reducer.js'; +export { commentsReducer } from './channels-comments/reducer.js'; export { resourceWatchReducer } from './channels-resource-watch/reducer.js'; export { softAssertNever, isClientDispatchable } from './common/reducer-helpers.js'; diff --git a/types/state.ts b/types/state.ts index 325d197d..0670e165 100644 --- a/types/state.ts +++ b/types/state.ts @@ -12,5 +12,6 @@ export * from './channels-root/state.js'; export * from './channels-session/state.js'; export * from './channels-terminal/state.js'; export * from './channels-changeset/state.js'; +export * from './channels-comments/state.js'; export * from './channels-otlp/state.js'; export * from './channels-resource-watch/state.js'; diff --git a/types/test-cases/reducers/210-comments-threadset-appends-new-thread.json b/types/test-cases/reducers/210-comments-threadset-appends-new-thread.json new file mode 100644 index 00000000..d67ccbcd --- /dev/null +++ b/types/test-cases/reducers/210-comments-threadset-appends-new-thread.json @@ -0,0 +1,77 @@ +{ + "description": "comments/threadSet appends a new thread when its id is unknown", + "reducer": "comments", + "initial": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { + "id": "c-1", + "text": "first thread, first comment" + } + ] + } + ] + }, + "actions": [ + { + "type": "comments/threadSet", + "thread": { + "id": "t-2", + "turnId": "turn-2", + "resource": "file:///src/b.ts", + "range": { + "start": { "line": 10, "character": 0 }, + "end": { "line": 12, "character": 0 } + }, + "comments": [ + { + "id": "c-2", + "text": "second thread, first comment" + } + ] + } + } + ], + "expected": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { + "id": "c-1", + "text": "first thread, first comment" + } + ] + }, + { + "id": "t-2", + "turnId": "turn-2", + "resource": "file:///src/b.ts", + "range": { + "start": { "line": 10, "character": 0 }, + "end": { "line": 12, "character": 0 } + }, + "comments": [ + { + "id": "c-2", + "text": "second thread, first comment" + } + ] + } + ] + } +} diff --git a/types/test-cases/reducers/211-comments-threadset-replaces-existing-thread.json b/types/test-cases/reducers/211-comments-threadset-replaces-existing-thread.json new file mode 100644 index 00000000..7a3a3e2b --- /dev/null +++ b/types/test-cases/reducers/211-comments-threadset-replaces-existing-thread.json @@ -0,0 +1,55 @@ +{ + "description": "comments/threadSet replaces an existing thread when the id matches", + "reducer": "comments", + "initial": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "original" } + ] + } + ] + }, + "actions": [ + { + "type": "comments/threadSet", + "thread": { + "id": "t-1", + "turnId": "turn-2", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 5, "character": 0 }, + "end": { "line": 5, "character": 10 } + }, + "comments": [ + { "id": "c-1", "text": "rewritten" }, + { "id": "c-2", "text": "reply" } + ] + } + } + ], + "expected": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-2", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 5, "character": 0 }, + "end": { "line": 5, "character": 10 } + }, + "comments": [ + { "id": "c-1", "text": "rewritten" }, + { "id": "c-2", "text": "reply" } + ] + } + ] + } +} diff --git a/types/test-cases/reducers/212-comments-threadremoved-drops-matching-thread.json b/types/test-cases/reducers/212-comments-threadremoved-drops-matching-thread.json new file mode 100644 index 00000000..e92a2ab7 --- /dev/null +++ b/types/test-cases/reducers/212-comments-threadremoved-drops-matching-thread.json @@ -0,0 +1,52 @@ +{ + "description": "comments/threadRemoved drops the matching thread and is a no-op for unknown ids", + "reducer": "comments", + "initial": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "keep" } + ] + }, + { + "id": "t-2", + "turnId": "turn-1", + "resource": "file:///src/b.ts", + "range": { + "start": { "line": 2, "character": 0 }, + "end": { "line": 2, "character": 4 } + }, + "comments": [ + { "id": "c-2", "text": "drop me" } + ] + } + ] + }, + "actions": [ + { "type": "comments/threadRemoved", "threadId": "t-2" }, + { "type": "comments/threadRemoved", "threadId": "missing" } + ], + "expected": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "keep" } + ] + } + ] + } +} diff --git a/types/test-cases/reducers/213-comments-commentset-appends-and-replaces.json b/types/test-cases/reducers/213-comments-commentset-appends-and-replaces.json new file mode 100644 index 00000000..fb95d787 --- /dev/null +++ b/types/test-cases/reducers/213-comments-commentset-appends-and-replaces.json @@ -0,0 +1,49 @@ +{ + "description": "comments/commentSet appends or replaces a comment within a thread", + "reducer": "comments", + "initial": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "original" } + ] + } + ] + }, + "actions": [ + { + "type": "comments/commentSet", + "threadId": "t-1", + "comment": { "id": "c-2", "text": "second" } + }, + { + "type": "comments/commentSet", + "threadId": "t-1", + "comment": { "id": "c-1", "text": "edited" } + } + ], + "expected": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "edited" }, + { "id": "c-2", "text": "second" } + ] + } + ] + } +} diff --git a/types/test-cases/reducers/214-comments-commentset-unknown-thread-is-no-op.json b/types/test-cases/reducers/214-comments-commentset-unknown-thread-is-no-op.json new file mode 100644 index 00000000..cf876e68 --- /dev/null +++ b/types/test-cases/reducers/214-comments-commentset-unknown-thread-is-no-op.json @@ -0,0 +1,43 @@ +{ + "description": "comments/commentSet is a no-op when the enclosing thread is unknown", + "reducer": "comments", + "initial": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "only" } + ] + } + ] + }, + "actions": [ + { + "type": "comments/commentSet", + "threadId": "missing", + "comment": { "id": "c-x", "text": "lost" } + } + ], + "expected": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "only" } + ] + } + ] + } +} diff --git a/types/test-cases/reducers/215-comments-commentremoved-drops-matching-comment.json b/types/test-cases/reducers/215-comments-commentremoved-drops-matching-comment.json new file mode 100644 index 00000000..43d0adea --- /dev/null +++ b/types/test-cases/reducers/215-comments-commentremoved-drops-matching-comment.json @@ -0,0 +1,44 @@ +{ + "description": "comments/commentRemoved drops the matching comment and leaves other comments untouched", + "reducer": "comments", + "initial": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "first" }, + { "id": "c-2", "text": "second" }, + { "id": "c-3", "text": "third" } + ] + } + ] + }, + "actions": [ + { "type": "comments/commentRemoved", "threadId": "t-1", "commentId": "c-2" }, + { "type": "comments/commentRemoved", "threadId": "t-1", "commentId": "missing" }, + { "type": "comments/commentRemoved", "threadId": "missing", "commentId": "c-1" } + ], + "expected": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "first" }, + { "id": "c-3", "text": "third" } + ] + } + ] + } +} diff --git a/types/test-cases/reducers/216-comments-cleared-empties-threads.json b/types/test-cases/reducers/216-comments-cleared-empties-threads.json new file mode 100644 index 00000000..5f7747ba --- /dev/null +++ b/types/test-cases/reducers/216-comments-cleared-empties-threads.json @@ -0,0 +1,27 @@ +{ + "description": "comments/cleared empties the thread list and is a no-op on an already-empty channel", + "reducer": "comments", + "initial": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "first" } + ] + } + ] + }, + "actions": [ + { "type": "comments/cleared" }, + { "type": "comments/cleared" } + ], + "expected": { + "threads": [] + } +} diff --git a/types/test-cases/reducers/217-comments-unknown-action-type-is-no-op.json b/types/test-cases/reducers/217-comments-unknown-action-type-is-no-op.json new file mode 100644 index 00000000..3ccad602 --- /dev/null +++ b/types/test-cases/reducers/217-comments-unknown-action-type-is-no-op.json @@ -0,0 +1,39 @@ +{ + "description": "comments unknown action type is a no-op", + "reducer": "comments", + "initial": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "only" } + ] + } + ] + }, + "actions": [ + { "type": "comments/unknownActionType" } + ], + "expected": { + "threads": [ + { + "id": "t-1", + "turnId": "turn-1", + "resource": "file:///src/a.ts", + "range": { + "start": { "line": 0, "character": 0 }, + "end": { "line": 0, "character": 5 } + }, + "comments": [ + { "id": "c-1", "text": "only" } + ] + } + ] + } +} diff --git a/types/version/message-checks.ts b/types/version/message-checks.ts index a97ee2f1..a9ab12bc 100644 --- a/types/version/message-checks.ts +++ b/types/version/message-checks.ts @@ -74,7 +74,13 @@ type _ExpectedCommands = | 'resolveSessionConfig' | 'sessionConfigCompletions' | 'completions' - | 'invokeChangesetOperation'; + | 'invokeChangesetOperation' + | 'createCommentThread' + | 'updateCommentThread' + | 'deleteCommentThread' + | 'addComment' + | 'editComment' + | 'deleteComment'; /** All methods annotated `@messageType Notification` (client → server). */ type _ExpectedClientNotifications = diff --git a/types/version/registry.ts b/types/version/registry.ts index b27704cf..68c5b82f 100644 --- a/types/version/registry.ts +++ b/types/version/registry.ts @@ -123,6 +123,11 @@ export const ACTION_INTRODUCED_IN: { readonly [K in StateAction['type']]: string [ActionType.ChangesetOperationsChanged]: '0.2.0', [ActionType.ChangesetOperationStatusChanged]: '0.3.0', [ActionType.ChangesetCleared]: '0.2.0', + [ActionType.CommentsThreadSet]: '0.3.0', + [ActionType.CommentsThreadRemoved]: '0.3.0', + [ActionType.CommentsCommentSet]: '0.3.0', + [ActionType.CommentsCommentRemoved]: '0.3.0', + [ActionType.CommentsCleared]: '0.3.0', [ActionType.RootTerminalsChanged]: '0.1.0', [ActionType.RootConfigChanged]: '0.1.0', [ActionType.TerminalData]: '0.1.0', From 3485fe62e48fc1c9d114b61ea40fc6c394ab372c Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 4 Jun 2026 21:52:11 +0200 Subject: [PATCH 02/11] and the rest --- .gitattributes | 0 clients/go/ahptypes/commands.generated.go | 4 +- clients/go/ahptypes/state.generated.go | 10 +- .../generated/Commands.generated.kt | 4 +- .../generated/State.generated.kt | 10 +- clients/rust/crates/ahp-types/src/commands.rs | 4 +- clients/rust/crates/ahp-types/src/state.rs | 10 +- .../Generated/Commands.generated.swift | 6 +- .../Generated/State.generated.swift | 14 +- docs/.vitepress/config.mts | 1 - docs/guide/comments.md | 0 docs/specification/comments-channel.md | 0 schema/actions.schema.json | 8 +- schema/commands.schema.json | 12 +- schema/errors.schema.json | 12 +- schema/notifications.schema.json | 8 +- schema/state.schema.json | 8 +- scripts/generate-go.ts | 36 +- scripts/generate-kotlin.ts | 33 +- scripts/generate-markdown.ts | 31 - scripts/generate-rust.ts | 25 +- scripts/generate-swift.ts | 22 +- types/channels-comments/commands.ts | 6 +- types/channels-comments/state.ts | 14 +- types/common/actions.ts | 18 - types/common/reducer-helpers.ts | 4 +- types/reducers.test.ts | 904 +++++++++++++++++- ...comments-threadset-appends-new-thread.json | 0 ...ts-threadset-replaces-existing-thread.json | 0 ...readset-empty-comments-removes-thread.json | 0 ...ts-threadset-empty-unknown-id-is-noop.json | 0 ...s-threadremoved-drops-matching-thread.json | 0 ...ents-threadremoved-unknown-id-is-noop.json | 0 ...mments-commentset-appends-new-comment.json | 0 ...nts-commentset-edits-existing-comment.json | 0 ...nts-commentset-unknown-thread-is-noop.json | 0 ...comments-commentremoved-drops-comment.json | 0 ...ntremoved-last-comment-removes-thread.json | 0 ...commentremoved-unknown-thread-is-noop.json | 0 ...entremoved-unknown-comment-id-is-noop.json | 0 ...13-comments-cleared-drops-all-threads.json | 0 .../314-comments-cleared-empty-is-noop.json | 0 ...-comments-unknown-action-type-is-noop.json | 0 ...-commentssummarychanged-sets-comments.json | 0 ...ommentssummarychanged-clears-comments.json | 0 45 files changed, 1022 insertions(+), 182 deletions(-) create mode 100644 .gitattributes create mode 100644 docs/guide/comments.md create mode 100644 docs/specification/comments-channel.md create mode 100644 types/test-cases/reducers/300-comments-threadset-appends-new-thread.json create mode 100644 types/test-cases/reducers/301-comments-threadset-replaces-existing-thread.json create mode 100644 types/test-cases/reducers/302-comments-threadset-empty-comments-removes-thread.json create mode 100644 types/test-cases/reducers/303-comments-threadset-empty-unknown-id-is-noop.json create mode 100644 types/test-cases/reducers/304-comments-threadremoved-drops-matching-thread.json create mode 100644 types/test-cases/reducers/305-comments-threadremoved-unknown-id-is-noop.json create mode 100644 types/test-cases/reducers/306-comments-commentset-appends-new-comment.json create mode 100644 types/test-cases/reducers/307-comments-commentset-edits-existing-comment.json create mode 100644 types/test-cases/reducers/308-comments-commentset-unknown-thread-is-noop.json create mode 100644 types/test-cases/reducers/309-comments-commentremoved-drops-comment.json create mode 100644 types/test-cases/reducers/310-comments-commentremoved-last-comment-removes-thread.json create mode 100644 types/test-cases/reducers/311-comments-commentremoved-unknown-thread-is-noop.json create mode 100644 types/test-cases/reducers/312-comments-commentremoved-unknown-comment-id-is-noop.json create mode 100644 types/test-cases/reducers/313-comments-cleared-drops-all-threads.json create mode 100644 types/test-cases/reducers/314-comments-cleared-empty-is-noop.json create mode 100644 types/test-cases/reducers/315-comments-unknown-action-type-is-noop.json create mode 100644 types/test-cases/reducers/316-session-commentssummarychanged-sets-comments.json create mode 100644 types/test-cases/reducers/317-session-commentssummarychanged-clears-comments.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e69de29b diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go index 8cfd8677..29b2835d 100644 --- a/clients/go/ahptypes/commands.generated.go +++ b/clients/go/ahptypes/commands.generated.go @@ -946,8 +946,8 @@ type EditCommentParams struct { ThreadId string `json:"threadId"` // {@link Comment.id} to edit. CommentId string `json:"commentId"` - // New comment body. - Text string `json:"text"` + // New comment body. See {@link Comment.text}. + Text StringOrMarkdown `json:"text"` } // Remove a single comment from a thread. diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index ca8c7957..4e01cd72 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -2192,8 +2192,10 @@ type CommentThread struct { type Comment struct { // Stable identifier within the enclosing thread. Server-assigned. Id string `json:"id"` - // Comment body. Rendered as plain text unless the client opts into Markdown. - Text string `json:"text"` + // Comment body. A bare `string` is rendered as plain text; pass + // `{ markdown: "…" }` to opt into Markdown rendering. See + // {@link StringOrMarkdown}. + Text StringOrMarkdown `json:"text"` // Server-defined opaque metadata, surfaced to tooling but not // interpreted by the protocol. Meta map[string]json.RawMessage `json:"_meta,omitempty"` @@ -2203,8 +2205,8 @@ type Comment struct { // and {@link AddCommentParams | `addComment`}. The server assigns the // resulting {@link Comment.id}. type NewComment struct { - // Comment body. - Text string `json:"text"` + // Comment body. See {@link Comment.text}. + Text StringOrMarkdown `json:"text"` // Server-defined opaque metadata, forwarded onto the resulting // {@link Comment._meta}. Meta map[string]json.RawMessage `json:"_meta,omitempty"` diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt index bc4f7a9a..e3d26c37 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt @@ -1121,9 +1121,9 @@ data class EditCommentParams( */ val commentId: String, /** - * New comment body. + * New comment body. See {@link Comment.text}. */ - val text: String + val text: StringOrMarkdown ) @Serializable 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 5b555267..364be742 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 @@ -3134,9 +3134,11 @@ data class Comment( */ val id: String, /** - * Comment body. Rendered as plain text unless the client opts into Markdown. + * Comment body. A bare `string` is rendered as plain text; pass + * `{ markdown: "…" }` to opt into Markdown rendering. See + * {@link StringOrMarkdown}. */ - val text: String, + val text: StringOrMarkdown, /** * Server-defined opaque metadata, surfaced to tooling but not * interpreted by the protocol. @@ -3148,9 +3150,9 @@ data class Comment( @Serializable data class NewComment( /** - * Comment body. + * Comment body. See {@link Comment.text}. */ - val text: String, + val text: StringOrMarkdown, /** * Server-defined opaque metadata, forwarded onto the resulting * {@link Comment._meta}. diff --git a/clients/rust/crates/ahp-types/src/commands.rs b/clients/rust/crates/ahp-types/src/commands.rs index 1d5f87c3..8d56b101 100644 --- a/clients/rust/crates/ahp-types/src/commands.rs +++ b/clients/rust/crates/ahp-types/src/commands.rs @@ -1128,8 +1128,8 @@ pub struct EditCommentParams { pub thread_id: String, /// {@link Comment.id} to edit. pub comment_id: String, - /// New comment body. - pub text: String, + /// New comment body. See {@link Comment.text}. + pub text: StringOrMarkdown, } /// Remove a single comment from a thread. diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index fd9b7a93..ba4379b3 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -2658,8 +2658,10 @@ pub struct CommentThread { pub struct Comment { /// Stable identifier within the enclosing thread. Server-assigned. pub id: String, - /// Comment body. Rendered as plain text unless the client opts into Markdown. - pub text: String, + /// Comment body. A bare `string` is rendered as plain text; pass + /// `{ markdown: "…" }` to opt into Markdown rendering. See + /// {@link StringOrMarkdown}. + pub text: StringOrMarkdown, /// Server-defined opaque metadata, surfaced to tooling but not /// interpreted by the protocol. #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] @@ -2672,8 +2674,8 @@ pub struct Comment { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct NewComment { - /// Comment body. - pub text: String, + /// Comment body. See {@link Comment.text}. + pub text: StringOrMarkdown, /// Server-defined opaque metadata, forwarded onto the resulting /// {@link Comment._meta}. #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift index 0dc5417d..3928acd8 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift @@ -1280,14 +1280,14 @@ public struct EditCommentParams: Codable, Sendable { public var threadId: String /// {@link Comment.id} to edit. public var commentId: String - /// New comment body. - public var text: String + /// New comment body. See {@link Comment.text}. + public var text: StringOrMarkdown public init( channel: String, threadId: String, commentId: String, - text: String + text: StringOrMarkdown ) { self.channel = channel self.threadId = threadId diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index a3b43ec4..23096fa7 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -3445,8 +3445,10 @@ public struct CommentThread: Codable, Sendable { public struct Comment: Codable, Sendable { /// Stable identifier within the enclosing thread. Server-assigned. public var id: String - /// Comment body. Rendered as plain text unless the client opts into Markdown. - public var text: String + /// Comment body. A bare `string` is rendered as plain text; pass + /// `{ markdown: "…" }` to opt into Markdown rendering. See + /// {@link StringOrMarkdown}. + public var text: StringOrMarkdown /// Server-defined opaque metadata, surfaced to tooling but not /// interpreted by the protocol. public var meta: [String: AnyCodable]? @@ -3459,7 +3461,7 @@ public struct Comment: Codable, Sendable { public init( id: String, - text: String, + text: StringOrMarkdown, meta: [String: AnyCodable]? = nil ) { self.id = id @@ -3469,8 +3471,8 @@ public struct Comment: Codable, Sendable { } public struct NewComment: Codable, Sendable { - /// Comment body. - public var text: String + /// Comment body. See {@link Comment.text}. + public var text: StringOrMarkdown /// Server-defined opaque metadata, forwarded onto the resulting /// {@link Comment._meta}. public var meta: [String: AnyCodable]? @@ -3481,7 +3483,7 @@ public struct NewComment: Codable, Sendable { } public init( - text: String, + text: StringOrMarkdown, meta: [String: AnyCodable]? = nil ) { self.text = text diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index e54bdae9..9dc8e18b 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -87,7 +87,6 @@ export default withMermaid(defineConfig({ { text: 'Session Channel', link: '/reference/session' }, { text: 'Terminal Channel', link: '/reference/terminal' }, { text: 'Changeset Channel', link: '/reference/changeset' }, - { text: 'Comments Channel', link: '/reference/comments' }, { text: 'Telemetry Channel', link: '/reference/otlp' }, ], }, diff --git a/docs/guide/comments.md b/docs/guide/comments.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/specification/comments-channel.md b/docs/specification/comments-channel.md new file mode 100644 index 00000000..e69de29b diff --git a/schema/actions.schema.json b/schema/actions.schema.json index bdb397f8..11c33eae 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -5179,8 +5179,8 @@ "description": "Stable identifier within the enclosing thread. Server-assigned." }, "text": { - "type": "string", - "description": "Comment body. Rendered as plain text unless the client opts into Markdown." + "$ref": "#/$defs/StringOrMarkdown", + "description": "Comment body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." }, "_meta": { "type": "object", @@ -5198,8 +5198,8 @@ "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", "properties": { "text": { - "type": "string", - "description": "Comment body." + "$ref": "#/$defs/StringOrMarkdown", + "description": "Comment body. See {@link Comment.text}." }, "_meta": { "type": "object", diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 1efdf096..f6a33dfe 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -1253,8 +1253,8 @@ "description": "{@link Comment.id} to edit." }, "text": { - "type": "string", - "description": "New comment body." + "$ref": "#/$defs/StringOrMarkdown", + "description": "New comment body. See {@link Comment.text}." } }, "required": [ @@ -4926,8 +4926,8 @@ "description": "Stable identifier within the enclosing thread. Server-assigned." }, "text": { - "type": "string", - "description": "Comment body. Rendered as plain text unless the client opts into Markdown." + "$ref": "#/$defs/StringOrMarkdown", + "description": "Comment body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." }, "_meta": { "type": "object", @@ -4945,8 +4945,8 @@ "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", "properties": { "text": { - "type": "string", - "description": "Comment body." + "$ref": "#/$defs/StringOrMarkdown", + "description": "Comment body. See {@link Comment.text}." }, "_meta": { "type": "object", diff --git a/schema/errors.schema.json b/schema/errors.schema.json index fdb574e2..d164f6d8 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -3672,8 +3672,8 @@ "description": "Stable identifier within the enclosing thread. Server-assigned." }, "text": { - "type": "string", - "description": "Comment body. Rendered as plain text unless the client opts into Markdown." + "$ref": "#/$defs/StringOrMarkdown", + "description": "Comment body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." }, "_meta": { "type": "object", @@ -3691,8 +3691,8 @@ "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", "properties": { "text": { - "type": "string", - "description": "Comment body." + "$ref": "#/$defs/StringOrMarkdown", + "description": "Comment body. See {@link Comment.text}." }, "_meta": { "type": "object", @@ -5030,8 +5030,8 @@ "description": "{@link Comment.id} to edit." }, "text": { - "type": "string", - "description": "New comment body." + "$ref": "#/$defs/StringOrMarkdown", + "description": "New comment body. See {@link Comment.text}." } }, "required": [ diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index a0a77e90..42c1a7d4 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -3801,8 +3801,8 @@ "description": "Stable identifier within the enclosing thread. Server-assigned." }, "text": { - "type": "string", - "description": "Comment body. Rendered as plain text unless the client opts into Markdown." + "$ref": "#/$defs/StringOrMarkdown", + "description": "Comment body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." }, "_meta": { "type": "object", @@ -3820,8 +3820,8 @@ "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", "properties": { "text": { - "type": "string", - "description": "Comment body." + "$ref": "#/$defs/StringOrMarkdown", + "description": "Comment body. See {@link Comment.text}." }, "_meta": { "type": "object", diff --git a/schema/state.schema.json b/schema/state.schema.json index 8de19bd7..de6f817e 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -3583,8 +3583,8 @@ "description": "Stable identifier within the enclosing thread. Server-assigned." }, "text": { - "type": "string", - "description": "Comment body. Rendered as plain text unless the client opts into Markdown." + "$ref": "#/$defs/StringOrMarkdown", + "description": "Comment body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." }, "_meta": { "type": "object", @@ -3602,8 +3602,8 @@ "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", "properties": { "text": { - "type": "string", - "description": "Comment body." + "$ref": "#/$defs/StringOrMarkdown", + "description": "Comment body. See {@link Comment.text}." }, "_meta": { "type": "object", diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 8d359436..5ce34b23 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -157,8 +157,8 @@ function mapType(tsType: string): string { tsType === 'IRootState | ISessionState | ITerminalState' || tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState' + tsType === 'RootState | SessionState | TerminalState | ChangesetState' + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState' ) { return 'SnapshotState'; } @@ -726,11 +726,6 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'ChangesetState' }, { name: 'ChangesetFile' }, { name: 'ChangesetOperation' }, - { name: 'CommentsSummary' }, - { name: 'CommentsState' }, - { name: 'CommentThread' }, - { name: 'Comment' }, - { name: 'NewComment' }, { name: 'TelemetryCapabilities' }, { name: 'ResourceWatchState' }, { name: 'ResourceChange' }, @@ -896,16 +891,14 @@ const CUSTOMIZATION_LOAD_STATE_UNION: UnionConfig = { function generateSnapshotState(): string { return `// SnapshotState is the state payload of a snapshot — root, session, -// terminal, changeset, or comments state. The active variant is chosen -// by which pointer field is non-nil; UnmarshalJSON probes for required -// fields in the canonical order (session → terminal → changeset → -// comments → root). +// terminal, or changeset state. The active variant is chosen by which +// pointer field is non-nil; UnmarshalJSON probes for required fields in +// the canonical order (session → terminal → changeset → root). type SnapshotState struct { \tRoot *RootState \`json:"-"\` \tSession *SessionState \`json:"-"\` \tTerminal *TerminalState \`json:"-"\` \tChangeset *ChangesetState \`json:"-"\` -\tComments *CommentsState \`json:"-"\` } // MarshalJSON encodes whichever variant is currently populated. @@ -917,8 +910,6 @@ func (s SnapshotState) MarshalJSON() ([]byte, error) { \t\treturn json.Marshal(s.Terminal) \tcase s.Changeset != nil: \t\treturn json.Marshal(s.Changeset) -\tcase s.Comments != nil: -\t\treturn json.Marshal(s.Comments) \tcase s.Root != nil: \t\treturn json.Marshal(s.Root) \tdefault: @@ -953,12 +944,6 @@ func (s *SnapshotState) UnmarshalJSON(data []byte) error { \t\t\treturn err \t\t} \t\ts.Changeset = &v -\tcase containsAll(probe, "threads"): -\t\tvar v CommentsState -\t\tif err := json.Unmarshal(data, &v); err != nil { -\t\t\treturn err -\t\t} -\t\ts.Comments = &v \tdefault: \t\tvar v RootState \t\tif err := json.Unmarshal(data, &v); err != nil { @@ -1093,11 +1078,6 @@ const ACTION_VARIANTS: { { type: 'changeset/operationsChanged', variantName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', variantName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', variantName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, - { type: 'comments/threadSet', variantName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, - { type: 'comments/threadRemoved', variantName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, - { type: 'comments/commentSet', variantName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, - { type: 'comments/commentRemoved', variantName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, - { type: 'comments/cleared', variantName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', variantName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'terminal/data', variantName: 'TerminalData', tsInterface: 'TerminalDataAction' }, { type: 'terminal/input', variantName: 'TerminalInput', tsInterface: 'TerminalInputAction' }, @@ -1242,12 +1222,6 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: str { name: 'CompletionsParams' }, { name: 'CompletionItem' }, { name: 'CompletionsResult' }, { name: 'InvokeChangesetOperationParams' }, { name: 'InvokeChangesetOperationResult' }, { name: 'ChangesetOperationFollowUp' }, - { name: 'CreateCommentThreadParams' }, { name: 'CreateCommentThreadResult' }, - { name: 'UpdateCommentThreadParams' }, - { name: 'DeleteCommentThreadParams' }, - { name: 'AddCommentParams' }, { name: 'AddCommentResult' }, - { name: 'EditCommentParams' }, - { name: 'DeleteCommentParams' }, ]; const RECONNECT_RESULT_UNION: UnionConfig = { diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 359e7ca3..182aac70 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -140,8 +140,8 @@ function mapType(tsType: string): string { if ( tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState' + tsType === 'RootState | SessionState | TerminalState | ChangesetState' + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState' ) { return 'SnapshotState'; } @@ -626,7 +626,8 @@ internal object StringOrMarkdownSerializer : KSerializer { function generateSnapshotState(): string { return `/** - * The state payload of a snapshot — root, session, terminal, changeset, or comments state. + * The state payload of a snapshot — root, session, terminal, changeset, + * or comments state. */ @Serializable(with = SnapshotStateSerializer::class) sealed interface SnapshotState { @@ -649,8 +650,8 @@ internal object SnapshotStateSerializer : KSerializer { ?: error("Expected JsonObject for SnapshotState") // Try the most distinctive shape first. SessionState has required // \`summary\`; ChangesetState has required \`status\` + \`files\`; - // CommentsState has required \`threads\`; TerminalState has - // \`uri\` / \`size\` / \`buffer\`; RootState is the catch-all. + // CommentsState has required \`threads\`; TerminalState has \`uri\` + // / \`size\` / \`buffer\`; RootState is the catch-all. return when { obj.containsKey("summary") -> SnapshotState.Session(input.json.decodeFromJsonElement(SessionState.serializer(), element)) obj.containsKey("status") && obj.containsKey("files") -> @@ -746,7 +747,7 @@ const STATE_ENUMS = [ 'TurnState', 'MessageKind', 'MessageAttachmentKind', 'ResponsePartKind', 'ToolCallStatus', 'ToolCallConfirmationReason', 'ToolCallCancellationReason', 'ConfirmationOptionKind', 'ToolResultContentType', 'CustomizationType', 'CustomizationLoadStatus', 'TerminalClaimKind', - 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'ResourceChangeType', + 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'CommentSource', 'ResourceChangeType', ]; const STATE_STRUCTS = [ @@ -784,8 +785,12 @@ const STATE_STRUCTS = [ 'TerminalClientClaim', 'TerminalSessionClaim', 'TerminalState', 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', +<<<<<<< Updated upstream 'Changeset', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', - 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', 'NewComment', +======= + 'ChangesetSummary', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', + 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', +>>>>>>> Stashed changes 'TelemetryCapabilities', 'ResourceWatchState', 'ResourceChange', ]; @@ -1042,11 +1047,12 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'changeset/operationsChanged', caseName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', caseName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', caseName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, - { type: 'comments/threadSet', caseName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, - { type: 'comments/threadRemoved', caseName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, - { type: 'comments/commentSet', caseName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, - { type: 'comments/commentRemoved', caseName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, - { type: 'comments/cleared', caseName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, + { type: 'session/commentsChanged', caseName: 'SessionCommentsChanged', tsInterface: 'SessionCommentsChangedAction' }, + { type: 'comment/threadSet', caseName: 'CommentThreadSet', tsInterface: 'CommentThreadSetAction' }, + { type: 'comment/threadRemoved', caseName: 'CommentThreadRemoved', tsInterface: 'CommentThreadRemovedAction' }, + { type: 'comment/set', caseName: 'CommentSet', tsInterface: 'CommentSetAction' }, + { type: 'comment/removed', caseName: 'CommentRemoved', tsInterface: 'CommentRemovedAction' }, + { type: 'comment/cleared', caseName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', caseName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'root/configChanged', caseName: 'RootConfigChanged', tsInterface: 'RootConfigChangedAction' }, { type: 'terminal/data', caseName: 'TerminalData', tsInterface: 'TerminalDataAction' }, @@ -1224,13 +1230,14 @@ const COMMAND_STRUCTS = [ 'SessionConfigValueItem', 'CompletionsParams', 'CompletionItem', 'CompletionsResult', 'InvokeChangesetOperationParams', 'InvokeChangesetOperationResult', - 'ChangesetOperationFollowUp', + 'NewComment', 'CreateCommentThreadParams', 'CreateCommentThreadResult', 'UpdateCommentThreadParams', 'DeleteCommentThreadParams', 'AddCommentParams', 'AddCommentResult', 'EditCommentParams', 'DeleteCommentParams', + 'ChangesetOperationFollowUp', ]; const RECONNECT_RESULT_UNION: UnionConfig = { diff --git a/scripts/generate-markdown.ts b/scripts/generate-markdown.ts index 231eae72..36a69356 100644 --- a/scripts/generate-markdown.ts +++ b/scripts/generate-markdown.ts @@ -53,7 +53,6 @@ const DIR_TO_PAGE: Record = { 'channels-session': 'session', 'channels-terminal': 'terminal', 'channels-changeset': 'changeset', - 'channels-comments': 'comments', 'channels-otlp': 'otlp', }; @@ -1004,35 +1003,6 @@ function generateChangesetChannelPage(project: Project): string { return lines.join('\n'); } -function generateCommentsChannelPage(project: Project): string { - currentPage = 'comments'; - const stateSf = findChannelSourceFile(project, 'channels-comments', 'state.ts'); - const actionsSf = findChannelSourceFile(project, 'channels-comments', 'actions.ts'); - const commandsSf = findChannelSourceFile(project, 'channels-comments', 'commands.ts'); - - const lines: string[] = [GENERATED_HEADER]; - lines.push('# Comments Channel\n'); - lines.push('Reference for the `ahp-session://comments` channel — per-session inline file comments. Threads anchor to a `(turnId, resource, range)` triple and always carry at least one comment; mutations flow through dedicated commands so the server can enforce the single-comment-minimum invariant.\n'); - lines.push(schemaLink('state.schema.json')); - - if (stateSf) { - lines.push('## State Types\n'); - lines.push(emitStateTypesSection([stateSf])); - } - if (actionsSf) { - lines.push('## Actions\n'); - lines.push('Mutate `CommentsState`. Scoped to a comments URI via the enclosing `ActionEnvelope.channel`. Every comments action is server-only.\n'); - lines.push(schemaLink('actions.schema.json')); - lines.push(emitActionsSection([actionsSf])); - } - if (commandsSf) { - lines.push('## Commands\n'); - lines.push(schemaLink('commands.schema.json')); - lines.push(emitCommandsSection(project, [commandsSf])); - } - return lines.join('\n'); -} - function generateOtlpChannelPage(project: Project): string { currentPage = 'otlp'; const stateSf = findChannelSourceFile(project, 'channels-otlp', 'state.ts'); @@ -1273,7 +1243,6 @@ export function generateMarkdownDocs(project: Project, outDir: string): void { { filename: 'session.md', generator: generateSessionChannelPage }, { filename: 'terminal.md', generator: generateTerminalChannelPage }, { filename: 'changeset.md', generator: generateChangesetChannelPage }, - { filename: 'comments.md', generator: generateCommentsChannelPage }, { filename: 'otlp.md', generator: generateOtlpChannelPage }, { filename: 'messages.md', generator: generateMessagesPage }, { filename: 'error-codes.md', generator: generateErrorCodesPage }, diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 67bbbd4e..94dbbad4 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -526,7 +526,7 @@ const STATE_ENUMS = [ 'ToolCallConfirmationReason', 'ToolCallCancellationReason', 'ConfirmationOptionKind', 'ToolResultContentType', 'CustomizationType', 'CustomizationLoadStatus', 'TerminalClaimKind', - 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'ResourceChangeType', + 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'CommentSource', 'ResourceChangeType', ]; /** @@ -630,7 +630,6 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'CommentsState' }, { name: 'CommentThread' }, { name: 'Comment' }, - { name: 'NewComment' }, { name: 'TelemetryCapabilities' }, { name: 'ResourceWatchState' }, { name: 'ResourceChange' }, @@ -903,6 +902,7 @@ const ACTION_VARIANTS: { { type: 'session/isArchivedChanged', variantName: 'SessionIsArchivedChanged', tsInterface: 'SessionIsArchivedChangedAction' }, { type: 'session/activityChanged', variantName: 'SessionActivityChanged', tsInterface: 'SessionActivityChangedAction' }, { type: 'session/changesetsChanged', variantName: 'SessionChangesetsChanged', tsInterface: 'SessionChangesetsChangedAction' }, + { type: 'session/commentsChanged', variantName: 'SessionCommentsChanged', tsInterface: 'SessionCommentsChangedAction' }, { type: 'session/serverToolsChanged', variantName: 'SessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', variantName: 'SessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', variantName: 'SessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, @@ -926,11 +926,11 @@ const ACTION_VARIANTS: { { type: 'changeset/operationsChanged', variantName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', variantName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', variantName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, - { type: 'comments/threadSet', variantName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, - { type: 'comments/threadRemoved', variantName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, - { type: 'comments/commentSet', variantName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, - { type: 'comments/commentRemoved', variantName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, - { type: 'comments/cleared', variantName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, + { type: 'comment/threadSet', variantName: 'CommentThreadSet', tsInterface: 'CommentThreadSetAction' }, + { type: 'comment/threadRemoved', variantName: 'CommentThreadRemoved', tsInterface: 'CommentThreadRemovedAction' }, + { type: 'comment/set', variantName: 'CommentSet', tsInterface: 'CommentSetAction' }, + { type: 'comment/removed', variantName: 'CommentRemoved', tsInterface: 'CommentRemovedAction' }, + { type: 'comment/cleared', variantName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', variantName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'terminal/data', variantName: 'TerminalData', tsInterface: 'TerminalDataAction' }, { type: 'terminal/input', variantName: 'TerminalInput', tsInterface: 'TerminalInputAction' }, @@ -981,7 +981,11 @@ 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, Comment, CommentThread};'); +<<<<<<< Updated upstream + 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, ChangesetSummary, CommentsSummary, CommentThread, Comment};'); +>>>>>>> Stashed changes lines.push(''); // ActionType enum @@ -1089,6 +1093,7 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: s { name: 'CompletionsParams' }, { name: 'CompletionItem' }, { name: 'CompletionsResult' }, { name: 'InvokeChangesetOperationParams' }, { name: 'InvokeChangesetOperationResult' }, { name: 'ChangesetOperationFollowUp' }, + { name: 'NewComment' }, { name: 'CreateCommentThreadParams' }, { name: 'CreateCommentThreadResult' }, { name: 'UpdateCommentThreadParams' }, { name: 'DeleteCommentThreadParams' }, @@ -1112,7 +1117,7 @@ function generateCommandsFile(project: Project): string { lines.push('#[allow(unused_imports)]'); lines.push('use crate::actions::{ActionEnvelope, StateAction};'); lines.push('#[allow(unused_imports)]'); - lines.push('use crate::state::{AgentSelection, ContentRef, MessageAttachment, ModelSelection, NewComment, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, TextRange, Turn};'); + lines.push('use crate::state::{AgentSelection, ContentRef, MessageAttachment, ModelSelection, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, Turn};'); lines.push(''); lines.push('// ─── Enums ────────────────────────────────────────────────────────────\n'); @@ -1195,7 +1200,7 @@ const NOTIFICATION_STRUCTS = [ function generateNotificationsFile(project: Project): string { const lines: string[] = [GENERATED_HEADER]; lines.push('#[allow(unused_imports)]'); - lines.push('use crate::state::{AgentSelection, ChangesSummary, Changeset, CommentsSummary, FileEdit, ModelSelection, ProjectInfo, SessionStatus, SessionSummary};'); + lines.push('use crate::state::{AgentSelection, ChangesSummary, Changeset, FileEdit, ModelSelection, ProjectInfo, SessionStatus, SessionSummary};'); lines.push(''); lines.push('// ─── Enums ────────────────────────────────────────────────────────────\n'); diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index 13f8ac11..f64eb5a6 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -498,7 +498,7 @@ const STATE_ENUMS = [ 'TurnState', 'MessageAttachmentKind', 'ResponsePartKind', 'ToolCallStatus', 'ToolCallConfirmationReason', 'ToolCallCancellationReason', 'ConfirmationOptionKind', 'ToolResultContentType', 'CustomizationType', 'CustomizationLoadStatus', 'TerminalClaimKind', - 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'ResourceChangeType', + 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'CommentSource', 'ResourceChangeType', ]; const STATE_STRUCTS = [ @@ -537,8 +537,12 @@ const STATE_STRUCTS = [ 'TerminalClientClaim', 'TerminalSessionClaim', 'TerminalState', 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', +<<<<<<< Updated upstream 'Changeset', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', - 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', 'NewComment', +======= + 'ChangesetSummary', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', + 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', +>>>>>>> Stashed changes 'TelemetryCapabilities', 'ResourceWatchState', 'ResourceChange', ]; @@ -901,11 +905,12 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'changeset/operationsChanged', caseName: 'changesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', caseName: 'changesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', caseName: 'changesetCleared', tsInterface: 'ChangesetClearedAction' }, - { type: 'comments/threadSet', caseName: 'commentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, - { type: 'comments/threadRemoved', caseName: 'commentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, - { type: 'comments/commentSet', caseName: 'commentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, - { type: 'comments/commentRemoved', caseName: 'commentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, - { type: 'comments/cleared', caseName: 'commentsCleared', tsInterface: 'CommentsClearedAction' }, + { type: 'session/commentsChanged', caseName: 'sessionCommentsChanged', tsInterface: 'SessionCommentsChangedAction' }, + { type: 'comment/threadSet', caseName: 'commentThreadSet', tsInterface: 'CommentThreadSetAction' }, + { type: 'comment/threadRemoved', caseName: 'commentThreadRemoved', tsInterface: 'CommentThreadRemovedAction' }, + { type: 'comment/set', caseName: 'commentSet', tsInterface: 'CommentSetAction' }, + { type: 'comment/removed', caseName: 'commentRemoved', tsInterface: 'CommentRemovedAction' }, + { type: 'comment/cleared', caseName: 'commentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', caseName: 'rootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'root/configChanged', caseName: 'rootConfigChanged', tsInterface: 'RootConfigChangedAction' }, { type: 'terminal/data', caseName: 'terminalData', tsInterface: 'TerminalDataAction' }, @@ -1089,13 +1094,14 @@ const COMMAND_STRUCTS = [ 'SessionConfigValueItem', 'CompletionsParams', 'CompletionItem', 'CompletionsResult', 'InvokeChangesetOperationParams', 'InvokeChangesetOperationResult', - 'ChangesetOperationFollowUp', + 'NewComment', 'CreateCommentThreadParams', 'CreateCommentThreadResult', 'UpdateCommentThreadParams', 'DeleteCommentThreadParams', 'AddCommentParams', 'AddCommentResult', 'EditCommentParams', 'DeleteCommentParams', + 'ChangesetOperationFollowUp', ]; const RECONNECT_RESULT_UNION: UnionConfig = { diff --git a/types/channels-comments/commands.ts b/types/channels-comments/commands.ts index 256d94d6..d4c6035c 100644 --- a/types/channels-comments/commands.ts +++ b/types/channels-comments/commands.ts @@ -12,7 +12,7 @@ * @module channels-comments/commands */ -import type { URI, TextRange } from '../common/state.js'; +import type { URI, StringOrMarkdown, TextRange } from '../common/state.js'; import type { BaseParams } from '../common/commands.js'; import type { NewComment } from './state.js'; @@ -165,8 +165,8 @@ export interface EditCommentParams extends BaseParams { threadId: string; /** {@link Comment.id} to edit. */ commentId: string; - /** New comment body. */ - text: string; + /** New comment body. See {@link Comment.text}. */ + text: StringOrMarkdown; } // ─── deleteComment ─────────────────────────────────────────────────────────── diff --git a/types/channels-comments/state.ts b/types/channels-comments/state.ts index 067de5fb..70020696 100644 --- a/types/channels-comments/state.ts +++ b/types/channels-comments/state.ts @@ -9,7 +9,7 @@ * @module channels-comments/state */ -import type { URI, TextRange } from '../common/state.js'; +import type { URI, StringOrMarkdown, TextRange } from '../common/state.js'; // ─── Comments Summary ──────────────────────────────────────────────────────── @@ -101,8 +101,12 @@ export interface CommentThread { export interface Comment { /** Stable identifier within the enclosing thread. Server-assigned. */ id: string; - /** Comment body. Rendered as plain text unless the client opts into Markdown. */ - text: string; + /** + * Comment body. A bare `string` is rendered as plain text; pass + * `{ markdown: "…" }` to opt into Markdown rendering. See + * {@link StringOrMarkdown}. + */ + text: StringOrMarkdown; /** * Server-defined opaque metadata, surfaced to tooling but not * interpreted by the protocol. @@ -120,8 +124,8 @@ export interface Comment { * @category Comments */ export interface NewComment { - /** Comment body. */ - text: string; + /** Comment body. See {@link Comment.text}. */ + text: StringOrMarkdown; /** * Server-defined opaque metadata, forwarded onto the resulting * {@link Comment._meta}. diff --git a/types/common/actions.ts b/types/common/actions.ts index 0f47ca96..1121f09d 100644 --- a/types/common/actions.ts +++ b/types/common/actions.ts @@ -67,14 +67,6 @@ import type { ChangesetClearedAction, } from '../channels-changeset/actions.js'; -import type { - CommentsThreadSetAction, - CommentsThreadRemovedAction, - CommentsCommentSetAction, - CommentsCommentRemovedAction, - CommentsClearedAction, -} from '../channels-comments/actions.js'; - import type { TerminalDataAction, TerminalInputAction, @@ -149,11 +141,6 @@ export const enum ActionType { ChangesetOperationsChanged = 'changeset/operationsChanged', ChangesetOperationStatusChanged = 'changeset/operationStatusChanged', ChangesetCleared = 'changeset/cleared', - CommentsThreadSet = 'comments/threadSet', - CommentsThreadRemoved = 'comments/threadRemoved', - CommentsCommentSet = 'comments/commentSet', - CommentsCommentRemoved = 'comments/commentRemoved', - CommentsCleared = 'comments/cleared', RootTerminalsChanged = 'root/terminalsChanged', RootConfigChanged = 'root/configChanged', TerminalData = 'terminal/data', @@ -254,11 +241,6 @@ export type StateAction = | ChangesetOperationsChangedAction | ChangesetOperationStatusChangedAction | ChangesetClearedAction - | CommentsThreadSetAction - | CommentsThreadRemovedAction - | CommentsCommentSetAction - | CommentsCommentRemovedAction - | CommentsClearedAction | TerminalDataAction | TerminalInputAction | TerminalResizedAction diff --git a/types/common/reducer-helpers.ts b/types/common/reducer-helpers.ts index 9fa3a484..bf5ab48c 100644 --- a/types/common/reducer-helpers.ts +++ b/types/common/reducer-helpers.ts @@ -14,8 +14,6 @@ import type { ClientTerminalAction, ChangesetAction, ClientChangesetAction, - CommentsAction, - ClientCommentsAction, } from '../action-origin.generated.js'; import { IS_CLIENT_DISPATCHABLE } from '../action-origin.generated.js'; @@ -40,6 +38,6 @@ export function softAssertNever(value: never, log?: (msg: string) => void): void * Servers SHOULD call this to validate incoming `dispatchAction` requests * and reject any action the client is not allowed to originate. */ -export function isClientDispatchable(action: RootAction | SessionAction | TerminalAction | ChangesetAction | CommentsAction): action is ClientRootAction | ClientSessionAction | ClientTerminalAction | ClientChangesetAction | ClientCommentsAction { +export function isClientDispatchable(action: RootAction | SessionAction | TerminalAction | ChangesetAction): action is ClientRootAction | ClientSessionAction | ClientTerminalAction | ClientChangesetAction { return IS_CLIENT_DISPATCHABLE[action.type]; } diff --git a/types/reducers.test.ts b/types/reducers.test.ts index 208a6c8f..671fd4b4 100644 --- a/types/reducers.test.ts +++ b/types/reducers.test.ts @@ -22,13 +22,12 @@ import { sessionReducer, terminalReducer, changesetReducer, - commentsReducer, resourceWatchReducer, isClientDispatchable, } from './reducers.js'; import { IS_CLIENT_DISPATCHABLE } from './action-origin.generated.js'; import { ActionType } from './actions.js'; -import type { RootState, SessionState, ChangesetState, CommentsState, ResourceWatchState } from './state.js'; +import type { RootState, SessionState, ChangesetState, ResourceWatchState } from './state.js'; import { SessionLifecycle, SessionStatus, @@ -56,7 +55,6 @@ function readChannelSources(baseName: string): string { 'channels-session', 'channels-terminal', 'channels-changeset', - 'channels-comments', 'channels-resource-watch', ]; return dirs @@ -75,10 +73,902 @@ function readChannelSources(baseName: string): string { interface Fixture { description: string; - reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'comments' | 'resourceWatch'; - initial: RootState | SessionState | TerminalState | ChangesetState | CommentsState | ResourceWatchState; + reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; + initial: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; actions: unknown[]; - expected: RootState | SessionState | TerminalState | ChangesetState | CommentsState | ResourceWatchState; + expected: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; +} + +/** + * Recursively replaces JSON `null` with `undefined` to match TypeScript + * reducer output, which uses `undefined` for absent optional fields. + */ +function nullToUndefined(value: T): T { + if (value === null) return undefined as unknown as T; + if (Array.isArray(value)) return value.map(nullToUndefined) as unknown as T; + if (typeof value === 'object') { + const result: Record = {}; + for (const [k, v] of Object.entries(value as Record)) { + result[k] = nullToUndefined(v); + } + return result as T; + } + return value; +} + +const fixtureDir = resolve(root, 'test-cases', 'reducers'); +const fixtureFiles = readdirSync(fixtureDir).filter(f => f.endsWith('.json')).sort(); + +const fixtures: Fixture[] = fixtureFiles.map(f => { + const raw = JSON.parse(readFileSync(resolve(fixtureDir, f), 'utf-8')); + return nullToUndefined(raw) as Fixture; +}); + +// ─── Fixture-Driven Reducer Tests ──────────────────────────────────────────── + +/** + * The reducers call Date.now() for modifiedAt timestamps. + * We mock it to a fixed value (9999) matching what was used during + * fixture generation, so expected values match exactly. + */ +const MOCK_NOW = 9999; +let originalDateNow: typeof Date.now; + +describe('reducer fixtures', () => { + beforeEach(() => { + originalDateNow = Date.now; + Date.now = () => MOCK_NOW; + }); + + afterEach(() => { + Date.now = originalDateNow; + }); + + for (const fixture of fixtures) { + it(fixture.description, () => { + let state = fixture.initial; + for (const action of fixture.actions) { + if (fixture.reducer === 'root') { + state = rootReducer(state as RootState, action as any); + } else if (fixture.reducer === 'terminal') { + state = terminalReducer(state as TerminalState, action as any); + } else if (fixture.reducer === 'changeset') { + state = changesetReducer(state as ChangesetState, action as any); + } else if (fixture.reducer === 'resourceWatch') { + state = resourceWatchReducer(state as ResourceWatchState, action as any); + } else { + state = sessionReducer(state as SessionState, action as any); + } + } + assert.deepStrictEqual(state, fixture.expected); + }); + } +}); + +// ─── IS_CLIENT_DISPATCHABLE validation ─────────────────────────────────────── +// +// These tests parse TypeScript source, so they must remain JS-only. + +describe('IS_CLIENT_DISPATCHABLE', () => { + it('matches @clientDispatchable annotations in actions.ts', () => { + const source = readChannelSources('actions.ts'); + + const jsdocInterfaceRe = /\/\*\*([\s\S]*?)\*\/\s*export\s+(?:interface|type)\s+(\w+)/g; + const clientDispatchableTypes = new Set(); + + for (const match of source.matchAll(jsdocInterfaceRe)) { + const [, jsdoc, name] = match; + if (!name.endsWith('Action')) continue; + + const afterDecl = source.slice(match.index! + match[0].length); + const typeMatch = afterDecl.match(/type:\s*ActionType\.(\w+)/); + if (!typeMatch) continue; + + if (jsdoc.includes('@clientDispatchable')) { + clientDispatchableTypes.add(typeMatch[1]); + } + } + + const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; + const enumMap = new Map(); + for (const match of source.matchAll(enumValueRe)) { + enumMap.set(match[1], match[2]); + } + + for (const [memberName, stringValue] of enumMap) { + if (!(stringValue in IS_CLIENT_DISPATCHABLE)) continue; + const expected = clientDispatchableTypes.has(memberName); + const actual = IS_CLIENT_DISPATCHABLE[stringValue as keyof typeof IS_CLIENT_DISPATCHABLE]; + assert.equal( + actual, + expected, + `IS_CLIENT_DISPATCHABLE['${stringValue}'] should be ${expected} (ActionType.${memberName})`, + ); + } + }); + + it('covers every ActionType enum member', () => { + const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; + const allValues: string[] = []; + for (const match of readChannelSources('actions.ts').matchAll(enumValueRe)) { + allValues.push(match[2]); + } + + const mapKeys = Object.keys(IS_CLIENT_DISPATCHABLE); + const missing = allValues.filter(v => !mapKeys.includes(v)); + assert.deepStrictEqual(missing, [], `Missing from IS_CLIENT_DISPATCHABLE: ${missing.join(', ')}`); + + const extra = mapKeys.filter(v => !allValues.includes(v)); + assert.deepStrictEqual(extra, [], `Extra in IS_CLIENT_DISPATCHABLE: ${extra.join(', ')}`); + }); +}); + +// ─── Dispatch Validation ───────────────────────────────────────────────────── + +describe('isClientDispatchable', () => { + it('returns true for client-dispatchable actions', () => { + const action = { type: ActionType.SessionTurnStarted, turnId: 't', message: { text: 'Hello', origin: { kind: MessageKind.User } } } as const; + assert.equal(isClientDispatchable(action), true); + }); + + it('returns false for server-only actions', () => { + const action = { type: ActionType.SessionReady, session: 'x' } as const; + assert.equal(isClientDispatchable(action), false); + }); +}); + +// ─── Immutability Checks ───────────────────────────────────────────────────── +// +// Verifying that the reducer does not mutate the input state requires +// identity checks (===), which can't be expressed in JSON fixtures. + +describe('reducer immutability', () => { + it('rootReducer does not mutate original state', () => { + const state: RootState = { agents: [] }; + const agents = [{ provider: 'x', displayName: 'X', description: 'x', models: [] }]; + rootReducer(state, { type: ActionType.RootAgentsChanged, agents }); + assert.deepStrictEqual(state.agents, []); + }); + + it('sessionReducer does not mutate original turns array', () => { + const turn1 = { id: 't1', message: { text: 'First', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const turn2 = { id: 't2', message: { text: 'Second', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const turn3 = { id: 't3', message: { text: 'Third', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const state: SessionState = { + summary: { resource: 'x', provider: 'copilot', title: 'T', status: SessionStatus.Idle, createdAt: 1000, modifiedAt: 1000, project: { uri: 'file:///test-project', displayName: 'Test Project' } }, + lifecycle: SessionLifecycle.Ready, + turns: [turn1, turn2, turn3], + }; + const original = [...state.turns]; + sessionReducer(state, { type: ActionType.SessionTruncated, turnId: 't1' }); + assert.deepStrictEqual(state.turns, original); + }); +}); +/** + * Reducer unit tests — driven by JSON fixtures for cross-language parity. + * + * Fixture format: { description, reducer, initial, actions, expected } + * Fixtures live in types/test-cases/reducers/*.json and can be consumed by + * any language implementation to verify reducer parity. + * + * Tests that are inherently JS-specific (source-code parsing, identity checks) + * remain as manual test cases below the fixture-driven tests. + * + * Run: npx tsx --test types/reducers.test.ts + */ + + + +const root = resolve(dirname(fileURLToPath(import.meta.url))); + +function readSource(file: string): string { + return readFileSync(resolve(root, file), 'utf-8'); +} + +/** + * Reads and concatenates every canonical per-channel source file matching + * `baseName` (e.g. `actions.ts`) under `types/common/` and + * `types/channels-*\/`. Used after the channel-organized refactor so the + * parsing in this test sees the union of declarations split across channels. + */ +function readChannelSources(baseName: string): string { + const dirs = [ + 'common', + 'channels-root', + 'channels-session', + 'channels-terminal', + 'channels-changeset', + 'channels-resource-watch', + ]; + return dirs + .map(dir => { + const p = resolve(root, dir, baseName); + try { + return readFileSync(p, 'utf-8'); + } catch { + return ''; + } + }) + .join('\n'); +} + +// ─── Fixture Loading ───────────────────────────────────────────────────────── + +interface Fixture { + description: string; + reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; + initial: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; + actions: unknown[]; + expected: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; +} + +/** + * Recursively replaces JSON `null` with `undefined` to match TypeScript + * reducer output, which uses `undefined` for absent optional fields. + */ +function nullToUndefined(value: T): T { + if (value === null) return undefined as unknown as T; + if (Array.isArray(value)) return value.map(nullToUndefined) as unknown as T; + if (typeof value === 'object') { + const result: Record = {}; + for (const [k, v] of Object.entries(value as Record)) { + result[k] = nullToUndefined(v); + } + return result as T; + } + return value; +} + +const fixtureDir = resolve(root, 'test-cases', 'reducers'); +const fixtureFiles = readdirSync(fixtureDir).filter(f => f.endsWith('.json')).sort(); + +const fixtures: Fixture[] = fixtureFiles.map(f => { + const raw = JSON.parse(readFileSync(resolve(fixtureDir, f), 'utf-8')); + return nullToUndefined(raw) as Fixture; +}); + +// ─── Fixture-Driven Reducer Tests ──────────────────────────────────────────── + +/** + * The reducers call Date.now() for modifiedAt timestamps. + * We mock it to a fixed value (9999) matching what was used during + * fixture generation, so expected values match exactly. + */ +const MOCK_NOW = 9999; +let originalDateNow: typeof Date.now; + +describe('reducer fixtures', () => { + beforeEach(() => { + originalDateNow = Date.now; + Date.now = () => MOCK_NOW; + }); + + afterEach(() => { + Date.now = originalDateNow; + }); + + for (const fixture of fixtures) { + it(fixture.description, () => { + let state = fixture.initial; + for (const action of fixture.actions) { + if (fixture.reducer === 'root') { + state = rootReducer(state as RootState, action as any); + } else if (fixture.reducer === 'terminal') { + state = terminalReducer(state as TerminalState, action as any); + } else if (fixture.reducer === 'changeset') { + state = changesetReducer(state as ChangesetState, action as any); + } else if (fixture.reducer === 'resourceWatch') { + state = resourceWatchReducer(state as ResourceWatchState, action as any); + } else { + state = sessionReducer(state as SessionState, action as any); + } + } + assert.deepStrictEqual(state, fixture.expected); + }); + } +}); + +// ─── IS_CLIENT_DISPATCHABLE validation ─────────────────────────────────────── +// +// These tests parse TypeScript source, so they must remain JS-only. + +describe('IS_CLIENT_DISPATCHABLE', () => { + it('matches @clientDispatchable annotations in actions.ts', () => { + const source = readChannelSources('actions.ts'); + + const jsdocInterfaceRe = /\/\*\*([\s\S]*?)\*\/\s*export\s+(?:interface|type)\s+(\w+)/g; + const clientDispatchableTypes = new Set(); + + for (const match of source.matchAll(jsdocInterfaceRe)) { + const [, jsdoc, name] = match; + if (!name.endsWith('Action')) continue; + + const afterDecl = source.slice(match.index! + match[0].length); + const typeMatch = afterDecl.match(/type:\s*ActionType\.(\w+)/); + if (!typeMatch) continue; + + if (jsdoc.includes('@clientDispatchable')) { + clientDispatchableTypes.add(typeMatch[1]); + } + } + + const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; + const enumMap = new Map(); + for (const match of source.matchAll(enumValueRe)) { + enumMap.set(match[1], match[2]); + } + + for (const [memberName, stringValue] of enumMap) { + if (!(stringValue in IS_CLIENT_DISPATCHABLE)) continue; + const expected = clientDispatchableTypes.has(memberName); + const actual = IS_CLIENT_DISPATCHABLE[stringValue as keyof typeof IS_CLIENT_DISPATCHABLE]; + assert.equal( + actual, + expected, + `IS_CLIENT_DISPATCHABLE['${stringValue}'] should be ${expected} (ActionType.${memberName})`, + ); + } + }); + + it('covers every ActionType enum member', () => { + const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; + const allValues: string[] = []; + for (const match of readChannelSources('actions.ts').matchAll(enumValueRe)) { + allValues.push(match[2]); + } + + const mapKeys = Object.keys(IS_CLIENT_DISPATCHABLE); + const missing = allValues.filter(v => !mapKeys.includes(v)); + assert.deepStrictEqual(missing, [], `Missing from IS_CLIENT_DISPATCHABLE: ${missing.join(', ')}`); + + const extra = mapKeys.filter(v => !allValues.includes(v)); + assert.deepStrictEqual(extra, [], `Extra in IS_CLIENT_DISPATCHABLE: ${extra.join(', ')}`); + }); +}); + +// ─── Dispatch Validation ───────────────────────────────────────────────────── + +describe('isClientDispatchable', () => { + it('returns true for client-dispatchable actions', () => { + const action = { type: ActionType.SessionTurnStarted, turnId: 't', message: { text: 'Hello', origin: { kind: MessageKind.User } } } as const; + assert.equal(isClientDispatchable(action), true); + }); + + it('returns false for server-only actions', () => { + const action = { type: ActionType.SessionReady, session: 'x' } as const; + assert.equal(isClientDispatchable(action), false); + }); +}); + +// ─── Immutability Checks ───────────────────────────────────────────────────── +// +// Verifying that the reducer does not mutate the input state requires +// identity checks (===), which can't be expressed in JSON fixtures. + +describe('reducer immutability', () => { + it('rootReducer does not mutate original state', () => { + const state: RootState = { agents: [] }; + const agents = [{ provider: 'x', displayName: 'X', description: 'x', models: [] }]; + rootReducer(state, { type: ActionType.RootAgentsChanged, agents }); + assert.deepStrictEqual(state.agents, []); + }); + + it('sessionReducer does not mutate original turns array', () => { + const turn1 = { id: 't1', message: { text: 'First', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const turn2 = { id: 't2', message: { text: 'Second', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const turn3 = { id: 't3', message: { text: 'Third', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const state: SessionState = { + summary: { resource: 'x', provider: 'copilot', title: 'T', status: SessionStatus.Idle, createdAt: 1000, modifiedAt: 1000, project: { uri: 'file:///test-project', displayName: 'Test Project' } }, + lifecycle: SessionLifecycle.Ready, + turns: [turn1, turn2, turn3], + }; + const original = [...state.turns]; + sessionReducer(state, { type: ActionType.SessionTruncated, turnId: 't1' }); + assert.deepStrictEqual(state.turns, original); + }); +}); +/** + * Reducer unit tests — driven by JSON fixtures for cross-language parity. + * + * Fixture format: { description, reducer, initial, actions, expected } + * Fixtures live in types/test-cases/reducers/*.json and can be consumed by + * any language implementation to verify reducer parity. + * + * Tests that are inherently JS-specific (source-code parsing, identity checks) + * remain as manual test cases below the fixture-driven tests. + * + * Run: npx tsx --test types/reducers.test.ts + */ + + + +const root = resolve(dirname(fileURLToPath(import.meta.url))); + +function readSource(file: string): string { + return readFileSync(resolve(root, file), 'utf-8'); +} + +/** + * Reads and concatenates every canonical per-channel source file matching + * `baseName` (e.g. `actions.ts`) under `types/common/` and + * `types/channels-*\/`. Used after the channel-organized refactor so the + * parsing in this test sees the union of declarations split across channels. + */ +function readChannelSources(baseName: string): string { + const dirs = [ + 'common', + 'channels-root', + 'channels-session', + 'channels-terminal', + 'channels-changeset', + 'channels-resource-watch', + ]; + return dirs + .map(dir => { + const p = resolve(root, dir, baseName); + try { + return readFileSync(p, 'utf-8'); + } catch { + return ''; + } + }) + .join('\n'); +} + +// ─── Fixture Loading ───────────────────────────────────────────────────────── + +interface Fixture { + description: string; + reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; + initial: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; + actions: unknown[]; + expected: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; +} + +/** + * Recursively replaces JSON `null` with `undefined` to match TypeScript + * reducer output, which uses `undefined` for absent optional fields. + */ +function nullToUndefined(value: T): T { + if (value === null) return undefined as unknown as T; + if (Array.isArray(value)) return value.map(nullToUndefined) as unknown as T; + if (typeof value === 'object') { + const result: Record = {}; + for (const [k, v] of Object.entries(value as Record)) { + result[k] = nullToUndefined(v); + } + return result as T; + } + return value; +} + +const fixtureDir = resolve(root, 'test-cases', 'reducers'); +const fixtureFiles = readdirSync(fixtureDir).filter(f => f.endsWith('.json')).sort(); + +const fixtures: Fixture[] = fixtureFiles.map(f => { + const raw = JSON.parse(readFileSync(resolve(fixtureDir, f), 'utf-8')); + return nullToUndefined(raw) as Fixture; +}); + +// ─── Fixture-Driven Reducer Tests ──────────────────────────────────────────── + +/** + * The reducers call Date.now() for modifiedAt timestamps. + * We mock it to a fixed value (9999) matching what was used during + * fixture generation, so expected values match exactly. + */ +const MOCK_NOW = 9999; +let originalDateNow: typeof Date.now; + +describe('reducer fixtures', () => { + beforeEach(() => { + originalDateNow = Date.now; + Date.now = () => MOCK_NOW; + }); + + afterEach(() => { + Date.now = originalDateNow; + }); + + for (const fixture of fixtures) { + it(fixture.description, () => { + let state = fixture.initial; + for (const action of fixture.actions) { + if (fixture.reducer === 'root') { + state = rootReducer(state as RootState, action as any); + } else if (fixture.reducer === 'terminal') { + state = terminalReducer(state as TerminalState, action as any); + } else if (fixture.reducer === 'changeset') { + state = changesetReducer(state as ChangesetState, action as any); + } else if (fixture.reducer === 'resourceWatch') { + state = resourceWatchReducer(state as ResourceWatchState, action as any); + } else { + state = sessionReducer(state as SessionState, action as any); + } + } + assert.deepStrictEqual(state, fixture.expected); + }); + } +}); + +// ─── IS_CLIENT_DISPATCHABLE validation ─────────────────────────────────────── +// +// These tests parse TypeScript source, so they must remain JS-only. + +describe('IS_CLIENT_DISPATCHABLE', () => { + it('matches @clientDispatchable annotations in actions.ts', () => { + const source = readChannelSources('actions.ts'); + + const jsdocInterfaceRe = /\/\*\*([\s\S]*?)\*\/\s*export\s+(?:interface|type)\s+(\w+)/g; + const clientDispatchableTypes = new Set(); + + for (const match of source.matchAll(jsdocInterfaceRe)) { + const [, jsdoc, name] = match; + if (!name.endsWith('Action')) continue; + + const afterDecl = source.slice(match.index! + match[0].length); + const typeMatch = afterDecl.match(/type:\s*ActionType\.(\w+)/); + if (!typeMatch) continue; + + if (jsdoc.includes('@clientDispatchable')) { + clientDispatchableTypes.add(typeMatch[1]); + } + } + + const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; + const enumMap = new Map(); + for (const match of source.matchAll(enumValueRe)) { + enumMap.set(match[1], match[2]); + } + + for (const [memberName, stringValue] of enumMap) { + if (!(stringValue in IS_CLIENT_DISPATCHABLE)) continue; + const expected = clientDispatchableTypes.has(memberName); + const actual = IS_CLIENT_DISPATCHABLE[stringValue as keyof typeof IS_CLIENT_DISPATCHABLE]; + assert.equal( + actual, + expected, + `IS_CLIENT_DISPATCHABLE['${stringValue}'] should be ${expected} (ActionType.${memberName})`, + ); + } + }); + + it('covers every ActionType enum member', () => { + const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; + const allValues: string[] = []; + for (const match of readChannelSources('actions.ts').matchAll(enumValueRe)) { + allValues.push(match[2]); + } + + const mapKeys = Object.keys(IS_CLIENT_DISPATCHABLE); + const missing = allValues.filter(v => !mapKeys.includes(v)); + assert.deepStrictEqual(missing, [], `Missing from IS_CLIENT_DISPATCHABLE: ${missing.join(', ')}`); + + const extra = mapKeys.filter(v => !allValues.includes(v)); + assert.deepStrictEqual(extra, [], `Extra in IS_CLIENT_DISPATCHABLE: ${extra.join(', ')}`); + }); +}); + +// ─── Dispatch Validation ───────────────────────────────────────────────────── + +describe('isClientDispatchable', () => { + it('returns true for client-dispatchable actions', () => { + const action = { type: ActionType.SessionTurnStarted, turnId: 't', message: { text: 'Hello', origin: { kind: MessageKind.User } } } as const; + assert.equal(isClientDispatchable(action), true); + }); + + it('returns false for server-only actions', () => { + const action = { type: ActionType.SessionReady, session: 'x' } as const; + assert.equal(isClientDispatchable(action), false); + }); +}); + +// ─── Immutability Checks ───────────────────────────────────────────────────── +// +// Verifying that the reducer does not mutate the input state requires +// identity checks (===), which can't be expressed in JSON fixtures. + +describe('reducer immutability', () => { + it('rootReducer does not mutate original state', () => { + const state: RootState = { agents: [] }; + const agents = [{ provider: 'x', displayName: 'X', description: 'x', models: [] }]; + rootReducer(state, { type: ActionType.RootAgentsChanged, agents }); + assert.deepStrictEqual(state.agents, []); + }); + + it('sessionReducer does not mutate original turns array', () => { + const turn1 = { id: 't1', message: { text: 'First', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const turn2 = { id: 't2', message: { text: 'Second', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const turn3 = { id: 't3', message: { text: 'Third', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const state: SessionState = { + summary: { resource: 'x', provider: 'copilot', title: 'T', status: SessionStatus.Idle, createdAt: 1000, modifiedAt: 1000, project: { uri: 'file:///test-project', displayName: 'Test Project' } }, + lifecycle: SessionLifecycle.Ready, + turns: [turn1, turn2, turn3], + }; + const original = [...state.turns]; + sessionReducer(state, { type: ActionType.SessionTruncated, turnId: 't1' }); + assert.deepStrictEqual(state.turns, original); + }); +}); +/** + * Reducer unit tests — driven by JSON fixtures for cross-language parity. + * + * Fixture format: { description, reducer, initial, actions, expected } + * Fixtures live in types/test-cases/reducers/*.json and can be consumed by + * any language implementation to verify reducer parity. + * + * Tests that are inherently JS-specific (source-code parsing, identity checks) + * remain as manual test cases below the fixture-driven tests. + * + * Run: npx tsx --test types/reducers.test.ts + */ + + + +const root = resolve(dirname(fileURLToPath(import.meta.url))); + +function readSource(file: string): string { + return readFileSync(resolve(root, file), 'utf-8'); +} + +/** + * Reads and concatenates every canonical per-channel source file matching + * `baseName` (e.g. `actions.ts`) under `types/common/` and + * `types/channels-*\/`. Used after the channel-organized refactor so the + * parsing in this test sees the union of declarations split across channels. + */ +function readChannelSources(baseName: string): string { + const dirs = [ + 'common', + 'channels-root', + 'channels-session', + 'channels-terminal', + 'channels-changeset', + 'channels-resource-watch', + ]; + return dirs + .map(dir => { + const p = resolve(root, dir, baseName); + try { + return readFileSync(p, 'utf-8'); + } catch { + return ''; + } + }) + .join('\n'); +} + +// ─── Fixture Loading ───────────────────────────────────────────────────────── + +interface Fixture { + description: string; + reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; + initial: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; + actions: unknown[]; + expected: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; +} + +/** + * Recursively replaces JSON `null` with `undefined` to match TypeScript + * reducer output, which uses `undefined` for absent optional fields. + */ +function nullToUndefined(value: T): T { + if (value === null) return undefined as unknown as T; + if (Array.isArray(value)) return value.map(nullToUndefined) as unknown as T; + if (typeof value === 'object') { + const result: Record = {}; + for (const [k, v] of Object.entries(value as Record)) { + result[k] = nullToUndefined(v); + } + return result as T; + } + return value; +} + +const fixtureDir = resolve(root, 'test-cases', 'reducers'); +const fixtureFiles = readdirSync(fixtureDir).filter(f => f.endsWith('.json')).sort(); + +const fixtures: Fixture[] = fixtureFiles.map(f => { + const raw = JSON.parse(readFileSync(resolve(fixtureDir, f), 'utf-8')); + return nullToUndefined(raw) as Fixture; +}); + +// ─── Fixture-Driven Reducer Tests ──────────────────────────────────────────── + +/** + * The reducers call Date.now() for modifiedAt timestamps. + * We mock it to a fixed value (9999) matching what was used during + * fixture generation, so expected values match exactly. + */ +const MOCK_NOW = 9999; +let originalDateNow: typeof Date.now; + +describe('reducer fixtures', () => { + beforeEach(() => { + originalDateNow = Date.now; + Date.now = () => MOCK_NOW; + }); + + afterEach(() => { + Date.now = originalDateNow; + }); + + for (const fixture of fixtures) { + it(fixture.description, () => { + let state = fixture.initial; + for (const action of fixture.actions) { + if (fixture.reducer === 'root') { + state = rootReducer(state as RootState, action as any); + } else if (fixture.reducer === 'terminal') { + state = terminalReducer(state as TerminalState, action as any); + } else if (fixture.reducer === 'changeset') { + state = changesetReducer(state as ChangesetState, action as any); + } else if (fixture.reducer === 'resourceWatch') { + state = resourceWatchReducer(state as ResourceWatchState, action as any); + } else { + state = sessionReducer(state as SessionState, action as any); + } + } + assert.deepStrictEqual(state, fixture.expected); + }); + } +}); + +// ─── IS_CLIENT_DISPATCHABLE validation ─────────────────────────────────────── +// +// These tests parse TypeScript source, so they must remain JS-only. + +describe('IS_CLIENT_DISPATCHABLE', () => { + it('matches @clientDispatchable annotations in actions.ts', () => { + const source = readChannelSources('actions.ts'); + + const jsdocInterfaceRe = /\/\*\*([\s\S]*?)\*\/\s*export\s+(?:interface|type)\s+(\w+)/g; + const clientDispatchableTypes = new Set(); + + for (const match of source.matchAll(jsdocInterfaceRe)) { + const [, jsdoc, name] = match; + if (!name.endsWith('Action')) continue; + + const afterDecl = source.slice(match.index! + match[0].length); + const typeMatch = afterDecl.match(/type:\s*ActionType\.(\w+)/); + if (!typeMatch) continue; + + if (jsdoc.includes('@clientDispatchable')) { + clientDispatchableTypes.add(typeMatch[1]); + } + } + + const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; + const enumMap = new Map(); + for (const match of source.matchAll(enumValueRe)) { + enumMap.set(match[1], match[2]); + } + + for (const [memberName, stringValue] of enumMap) { + if (!(stringValue in IS_CLIENT_DISPATCHABLE)) continue; + const expected = clientDispatchableTypes.has(memberName); + const actual = IS_CLIENT_DISPATCHABLE[stringValue as keyof typeof IS_CLIENT_DISPATCHABLE]; + assert.equal( + actual, + expected, + `IS_CLIENT_DISPATCHABLE['${stringValue}'] should be ${expected} (ActionType.${memberName})`, + ); + } + }); + + it('covers every ActionType enum member', () => { + const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; + const allValues: string[] = []; + for (const match of readChannelSources('actions.ts').matchAll(enumValueRe)) { + allValues.push(match[2]); + } + + const mapKeys = Object.keys(IS_CLIENT_DISPATCHABLE); + const missing = allValues.filter(v => !mapKeys.includes(v)); + assert.deepStrictEqual(missing, [], `Missing from IS_CLIENT_DISPATCHABLE: ${missing.join(', ')}`); + + const extra = mapKeys.filter(v => !allValues.includes(v)); + assert.deepStrictEqual(extra, [], `Extra in IS_CLIENT_DISPATCHABLE: ${extra.join(', ')}`); + }); +}); + +// ─── Dispatch Validation ───────────────────────────────────────────────────── + +describe('isClientDispatchable', () => { + it('returns true for client-dispatchable actions', () => { + const action = { type: ActionType.SessionTurnStarted, turnId: 't', message: { text: 'Hello', origin: { kind: MessageKind.User } } } as const; + assert.equal(isClientDispatchable(action), true); + }); + + it('returns false for server-only actions', () => { + const action = { type: ActionType.SessionReady, session: 'x' } as const; + assert.equal(isClientDispatchable(action), false); + }); +}); + +// ─── Immutability Checks ───────────────────────────────────────────────────── +// +// Verifying that the reducer does not mutate the input state requires +// identity checks (===), which can't be expressed in JSON fixtures. + +describe('reducer immutability', () => { + it('rootReducer does not mutate original state', () => { + const state: RootState = { agents: [] }; + const agents = [{ provider: 'x', displayName: 'X', description: 'x', models: [] }]; + rootReducer(state, { type: ActionType.RootAgentsChanged, agents }); + assert.deepStrictEqual(state.agents, []); + }); + + it('sessionReducer does not mutate original turns array', () => { + const turn1 = { id: 't1', message: { text: 'First', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const turn2 = { id: 't2', message: { text: 'Second', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const turn3 = { id: 't3', message: { text: 'Third', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; + const state: SessionState = { + summary: { resource: 'x', provider: 'copilot', title: 'T', status: SessionStatus.Idle, createdAt: 1000, modifiedAt: 1000, project: { uri: 'file:///test-project', displayName: 'Test Project' } }, + lifecycle: SessionLifecycle.Ready, + turns: [turn1, turn2, turn3], + }; + const original = [...state.turns]; + sessionReducer(state, { type: ActionType.SessionTruncated, turnId: 't1' }); + assert.deepStrictEqual(state.turns, original); + }); +}); +/** + * Reducer unit tests — driven by JSON fixtures for cross-language parity. + * + * Fixture format: { description, reducer, initial, actions, expected } + * Fixtures live in types/test-cases/reducers/*.json and can be consumed by + * any language implementation to verify reducer parity. + * + * Tests that are inherently JS-specific (source-code parsing, identity checks) + * remain as manual test cases below the fixture-driven tests. + * + * Run: npx tsx --test types/reducers.test.ts + */ + + + +const root = resolve(dirname(fileURLToPath(import.meta.url))); + +function readSource(file: string): string { + return readFileSync(resolve(root, file), 'utf-8'); +} + +/** + * Reads and concatenates every canonical per-channel source file matching + * `baseName` (e.g. `actions.ts`) under `types/common/` and + * `types/channels-*\/`. Used after the channel-organized refactor so the + * parsing in this test sees the union of declarations split across channels. + */ +function readChannelSources(baseName: string): string { + const dirs = [ + 'common', + 'channels-root', + 'channels-session', + 'channels-terminal', + 'channels-changeset', + 'channels-resource-watch', + ]; + return dirs + .map(dir => { + const p = resolve(root, dir, baseName); + try { + return readFileSync(p, 'utf-8'); + } catch { + return ''; + } + }) + .join('\n'); +} + +// ─── Fixture Loading ───────────────────────────────────────────────────────── + +interface Fixture { + description: string; + reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; + initial: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; + actions: unknown[]; + expected: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; } /** @@ -136,8 +1026,6 @@ describe('reducer fixtures', () => { state = terminalReducer(state as TerminalState, action as any); } else if (fixture.reducer === 'changeset') { state = changesetReducer(state as ChangesetState, action as any); - } else if (fixture.reducer === 'comments') { - state = commentsReducer(state as CommentsState, action as any); } else if (fixture.reducer === 'resourceWatch') { state = resourceWatchReducer(state as ResourceWatchState, action as any); } else { diff --git a/types/test-cases/reducers/300-comments-threadset-appends-new-thread.json b/types/test-cases/reducers/300-comments-threadset-appends-new-thread.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/301-comments-threadset-replaces-existing-thread.json b/types/test-cases/reducers/301-comments-threadset-replaces-existing-thread.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/302-comments-threadset-empty-comments-removes-thread.json b/types/test-cases/reducers/302-comments-threadset-empty-comments-removes-thread.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/303-comments-threadset-empty-unknown-id-is-noop.json b/types/test-cases/reducers/303-comments-threadset-empty-unknown-id-is-noop.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/304-comments-threadremoved-drops-matching-thread.json b/types/test-cases/reducers/304-comments-threadremoved-drops-matching-thread.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/305-comments-threadremoved-unknown-id-is-noop.json b/types/test-cases/reducers/305-comments-threadremoved-unknown-id-is-noop.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/306-comments-commentset-appends-new-comment.json b/types/test-cases/reducers/306-comments-commentset-appends-new-comment.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/307-comments-commentset-edits-existing-comment.json b/types/test-cases/reducers/307-comments-commentset-edits-existing-comment.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/308-comments-commentset-unknown-thread-is-noop.json b/types/test-cases/reducers/308-comments-commentset-unknown-thread-is-noop.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/309-comments-commentremoved-drops-comment.json b/types/test-cases/reducers/309-comments-commentremoved-drops-comment.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/310-comments-commentremoved-last-comment-removes-thread.json b/types/test-cases/reducers/310-comments-commentremoved-last-comment-removes-thread.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/311-comments-commentremoved-unknown-thread-is-noop.json b/types/test-cases/reducers/311-comments-commentremoved-unknown-thread-is-noop.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/312-comments-commentremoved-unknown-comment-id-is-noop.json b/types/test-cases/reducers/312-comments-commentremoved-unknown-comment-id-is-noop.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/313-comments-cleared-drops-all-threads.json b/types/test-cases/reducers/313-comments-cleared-drops-all-threads.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/314-comments-cleared-empty-is-noop.json b/types/test-cases/reducers/314-comments-cleared-empty-is-noop.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/315-comments-unknown-action-type-is-noop.json b/types/test-cases/reducers/315-comments-unknown-action-type-is-noop.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/316-session-commentssummarychanged-sets-comments.json b/types/test-cases/reducers/316-session-commentssummarychanged-sets-comments.json new file mode 100644 index 00000000..e69de29b diff --git a/types/test-cases/reducers/317-session-commentssummarychanged-clears-comments.json b/types/test-cases/reducers/317-session-commentssummarychanged-clears-comments.json new file mode 100644 index 00000000..e69de29b From 10762318cb807c55d7a65fa64e73df66dc3d4978 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 4 Jun 2026 21:55:36 +0200 Subject: [PATCH 03/11] remove again --- .gitattributes | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index e69de29b..00000000 From ef8528a2a95ca2ec57be570e7f6e60c9717e5a2f Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Thu, 4 Jun 2026 23:10:42 +0200 Subject: [PATCH 04/11] fix merge --- clients/go/ahptypes/state.generated.go | 7 +- .../generated/Commands.generated.kt | 18 +- .../generated/State.generated.kt | 7 +- .../Generated/Commands.generated.swift | 28 +- docs/.vitepress/config.mts | 1 + scripts/generate-go.ts | 27 +- scripts/generate-kotlin.ts | 20 +- scripts/generate-markdown.ts | 31 + scripts/generate-rust.ts | 25 +- scripts/generate-swift.ts | 20 +- types/common/actions.ts | 18 + types/common/reducer-helpers.ts | 4 +- types/reducers.test.ts | 911 +----------------- ...comments-threadset-appends-new-thread.json | 0 ...ts-threadset-replaces-existing-thread.json | 0 ...readset-empty-comments-removes-thread.json | 0 ...ts-threadset-empty-unknown-id-is-noop.json | 0 ...s-threadremoved-drops-matching-thread.json | 0 ...ents-threadremoved-unknown-id-is-noop.json | 0 ...mments-commentset-appends-new-comment.json | 0 ...nts-commentset-edits-existing-comment.json | 0 ...nts-commentset-unknown-thread-is-noop.json | 0 ...comments-commentremoved-drops-comment.json | 0 ...ntremoved-last-comment-removes-thread.json | 0 ...commentremoved-unknown-thread-is-noop.json | 0 ...entremoved-unknown-comment-id-is-noop.json | 0 ...13-comments-cleared-drops-all-threads.json | 0 .../314-comments-cleared-empty-is-noop.json | 0 ...-comments-unknown-action-type-is-noop.json | 0 ...-commentssummarychanged-sets-comments.json | 0 ...ommentssummarychanged-clears-comments.json | 0 31 files changed, 142 insertions(+), 975 deletions(-) delete mode 100644 types/test-cases/reducers/300-comments-threadset-appends-new-thread.json delete mode 100644 types/test-cases/reducers/301-comments-threadset-replaces-existing-thread.json delete mode 100644 types/test-cases/reducers/302-comments-threadset-empty-comments-removes-thread.json delete mode 100644 types/test-cases/reducers/303-comments-threadset-empty-unknown-id-is-noop.json delete mode 100644 types/test-cases/reducers/304-comments-threadremoved-drops-matching-thread.json delete mode 100644 types/test-cases/reducers/305-comments-threadremoved-unknown-id-is-noop.json delete mode 100644 types/test-cases/reducers/306-comments-commentset-appends-new-comment.json delete mode 100644 types/test-cases/reducers/307-comments-commentset-edits-existing-comment.json delete mode 100644 types/test-cases/reducers/308-comments-commentset-unknown-thread-is-noop.json delete mode 100644 types/test-cases/reducers/309-comments-commentremoved-drops-comment.json delete mode 100644 types/test-cases/reducers/310-comments-commentremoved-last-comment-removes-thread.json delete mode 100644 types/test-cases/reducers/311-comments-commentremoved-unknown-thread-is-noop.json delete mode 100644 types/test-cases/reducers/312-comments-commentremoved-unknown-comment-id-is-noop.json delete mode 100644 types/test-cases/reducers/313-comments-cleared-drops-all-threads.json delete mode 100644 types/test-cases/reducers/314-comments-cleared-empty-is-noop.json delete mode 100644 types/test-cases/reducers/315-comments-unknown-action-type-is-noop.json delete mode 100644 types/test-cases/reducers/316-session-commentssummarychanged-sets-comments.json delete mode 100644 types/test-cases/reducers/317-session-commentssummarychanged-clears-comments.json diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 4e01cd72..d63cbe9f 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -3195,10 +3195,9 @@ func (u CustomizationLoadState) MarshalJSON() ([]byte, error) { } // SnapshotState is the state payload of a snapshot — root, session, -// terminal, changeset, or comments state. The active variant is chosen -// by which pointer field is non-nil; UnmarshalJSON probes for required -// fields in the canonical order (session → terminal → changeset → -// comments → root). +// terminal, changeset, or comments state. The active variant is chosen by which +// pointer field is non-nil; UnmarshalJSON probes for required fields in +// the canonical order (session → terminal → changeset → comments → root). type SnapshotState struct { Root *RootState `json:"-"` Session *SessionState `json:"-"` diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt index e3d26c37..e522edb9 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt @@ -1001,15 +1001,6 @@ data class InvokeChangesetOperationResult( val followUp: ChangesetOperationFollowUp? = null ) -@Serializable -data class ChangesetOperationFollowUp( - val content: ContentRef, - /** - * When `true`, open in an external handler rather than inline. - */ - val external: Boolean? = null -) - @Serializable data class CreateCommentThreadParams( /** @@ -1142,6 +1133,15 @@ data class DeleteCommentParams( val commentId: String ) +@Serializable +data class ChangesetOperationFollowUp( + val content: ContentRef, + /** + * When `true`, open in an external handler rather than inline. + */ + val external: Boolean? = null +) + // ─── ReconnectResult Union ────────────────────────────────────────────────── @Serializable(with = ReconnectResultSerializer::class) 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 364be742..45646de6 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 @@ -3917,7 +3917,8 @@ internal object ToolResultContentSerializer : KSerializer { } /** - * The state payload of a snapshot — root, session, terminal, changeset, or comments state. + * The state payload of a snapshot — root, session, terminal, changeset, + * or comments state. */ @Serializable(with = SnapshotStateSerializer::class) sealed interface SnapshotState { @@ -3940,8 +3941,8 @@ internal object SnapshotStateSerializer : KSerializer { ?: error("Expected JsonObject for SnapshotState") // Try the most distinctive shape first. SessionState has required // `summary`; ChangesetState has required `status` + `files`; - // CommentsState has required `threads`; TerminalState has - // `uri` / `size` / `buffer`; RootState is the catch-all. + // CommentsState has required `threads`; TerminalState has `uri` + // / `size` / `buffer`; RootState is the catch-all. return when { obj.containsKey("summary") -> SnapshotState.Session(input.json.decodeFromJsonElement(SessionState.serializer(), element)) obj.containsKey("status") && obj.containsKey("files") -> diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift index 3928acd8..48c44d8e 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift @@ -1145,20 +1145,6 @@ public struct InvokeChangesetOperationResult: Codable, Sendable { } } -public struct ChangesetOperationFollowUp: Codable, Sendable { - public var content: ContentRef - /// When `true`, open in an external handler rather than inline. - public var external: Bool? - - public init( - content: ContentRef, - external: Bool? = nil - ) { - self.content = content - self.external = external - } -} - public struct CreateCommentThreadParams: Codable, Sendable { /// Channel URI this command targets. public var channel: String @@ -1315,6 +1301,20 @@ public struct DeleteCommentParams: Codable, Sendable { } } +public struct ChangesetOperationFollowUp: Codable, Sendable { + public var content: ContentRef + /// When `true`, open in an external handler rather than inline. + public var external: Bool? + + public init( + content: ContentRef, + external: Bool? = nil + ) { + self.content = content + self.external = external + } +} + // MARK: - ReconnectResult Union public enum ReconnectResult: Codable, Sendable { diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 9dc8e18b..e54bdae9 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -87,6 +87,7 @@ export default withMermaid(defineConfig({ { text: 'Session Channel', link: '/reference/session' }, { text: 'Terminal Channel', link: '/reference/terminal' }, { text: 'Changeset Channel', link: '/reference/changeset' }, + { text: 'Comments Channel', link: '/reference/comments' }, { text: 'Telemetry Channel', link: '/reference/otlp' }, ], }, diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 5ce34b23..996b17dc 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -726,6 +726,11 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'ChangesetState' }, { name: 'ChangesetFile' }, { name: 'ChangesetOperation' }, + { name: 'CommentsSummary' }, + { name: 'CommentsState' }, + { name: 'CommentThread' }, + { name: 'Comment' }, + { name: 'NewComment' }, { name: 'TelemetryCapabilities' }, { name: 'ResourceWatchState' }, { name: 'ResourceChange' }, @@ -891,14 +896,15 @@ const CUSTOMIZATION_LOAD_STATE_UNION: UnionConfig = { function generateSnapshotState(): string { return `// SnapshotState is the state payload of a snapshot — root, session, -// terminal, or changeset state. The active variant is chosen by which +// terminal, changeset, or comments state. The active variant is chosen by which // pointer field is non-nil; UnmarshalJSON probes for required fields in -// the canonical order (session → terminal → changeset → root). +// the canonical order (session → terminal → changeset → comments → root). type SnapshotState struct { \tRoot *RootState \`json:"-"\` \tSession *SessionState \`json:"-"\` \tTerminal *TerminalState \`json:"-"\` \tChangeset *ChangesetState \`json:"-"\` + Comments *CommentsState \`json:"-"\` } // MarshalJSON encodes whichever variant is currently populated. @@ -910,6 +916,8 @@ func (s SnapshotState) MarshalJSON() ([]byte, error) { \t\treturn json.Marshal(s.Terminal) \tcase s.Changeset != nil: \t\treturn json.Marshal(s.Changeset) + case s.Comments != nil: + return json.Marshal(s.Comments) \tcase s.Root != nil: \t\treturn json.Marshal(s.Root) \tdefault: @@ -944,6 +952,12 @@ func (s *SnapshotState) UnmarshalJSON(data []byte) error { \t\t\treturn err \t\t} \t\ts.Changeset = &v + case containsAll(probe, "threads"): + var v CommentsState + if err := json.Unmarshal(data, &v); err != nil { + return err + } + s.Comments = &v \tdefault: \t\tvar v RootState \t\tif err := json.Unmarshal(data, &v); err != nil { @@ -1078,6 +1092,11 @@ const ACTION_VARIANTS: { { type: 'changeset/operationsChanged', variantName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', variantName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', variantName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, + { type: 'comments/threadSet', variantName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, + { type: 'comments/threadRemoved', variantName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, + { type: 'comments/commentSet', variantName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, + { type: 'comments/commentRemoved', variantName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, + { type: 'comments/cleared', variantName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', variantName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'terminal/data', variantName: 'TerminalData', tsInterface: 'TerminalDataAction' }, { type: 'terminal/input', variantName: 'TerminalInput', tsInterface: 'TerminalInputAction' }, @@ -1222,6 +1241,10 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: str { name: 'CompletionsParams' }, { name: 'CompletionItem' }, { name: 'CompletionsResult' }, { name: 'InvokeChangesetOperationParams' }, { name: 'InvokeChangesetOperationResult' }, { name: 'ChangesetOperationFollowUp' }, + { name: 'CreateCommentThreadParams' }, { name: 'CreateCommentThreadResult' }, + { name: 'UpdateCommentThreadParams' }, { name: 'DeleteCommentThreadParams' }, + { name: 'AddCommentParams' }, { name: 'AddCommentResult' }, + { name: 'EditCommentParams' }, { name: 'DeleteCommentParams' }, ]; const RECONNECT_RESULT_UNION: UnionConfig = { diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 182aac70..0545aa09 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -747,7 +747,7 @@ const STATE_ENUMS = [ 'TurnState', 'MessageKind', 'MessageAttachmentKind', 'ResponsePartKind', 'ToolCallStatus', 'ToolCallConfirmationReason', 'ToolCallCancellationReason', 'ConfirmationOptionKind', 'ToolResultContentType', 'CustomizationType', 'CustomizationLoadStatus', 'TerminalClaimKind', - 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'CommentSource', 'ResourceChangeType', + 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'ResourceChangeType', ]; const STATE_STRUCTS = [ @@ -785,12 +785,8 @@ const STATE_STRUCTS = [ 'TerminalClientClaim', 'TerminalSessionClaim', 'TerminalState', 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', -<<<<<<< Updated upstream 'Changeset', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', -======= - 'ChangesetSummary', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', - 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', ->>>>>>> Stashed changes + 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', 'NewComment', 'TelemetryCapabilities', 'ResourceWatchState', 'ResourceChange', ]; @@ -1047,12 +1043,11 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'changeset/operationsChanged', caseName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', caseName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', caseName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, - { type: 'session/commentsChanged', caseName: 'SessionCommentsChanged', tsInterface: 'SessionCommentsChangedAction' }, - { type: 'comment/threadSet', caseName: 'CommentThreadSet', tsInterface: 'CommentThreadSetAction' }, - { type: 'comment/threadRemoved', caseName: 'CommentThreadRemoved', tsInterface: 'CommentThreadRemovedAction' }, - { type: 'comment/set', caseName: 'CommentSet', tsInterface: 'CommentSetAction' }, - { type: 'comment/removed', caseName: 'CommentRemoved', tsInterface: 'CommentRemovedAction' }, - { type: 'comment/cleared', caseName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, + { type: 'comments/threadSet', caseName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, + { type: 'comments/threadRemoved', caseName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, + { type: 'comments/commentSet', caseName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, + { type: 'comments/commentRemoved', caseName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, + { type: 'comments/cleared', caseName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', caseName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'root/configChanged', caseName: 'RootConfigChanged', tsInterface: 'RootConfigChangedAction' }, { type: 'terminal/data', caseName: 'TerminalData', tsInterface: 'TerminalDataAction' }, @@ -1230,7 +1225,6 @@ const COMMAND_STRUCTS = [ 'SessionConfigValueItem', 'CompletionsParams', 'CompletionItem', 'CompletionsResult', 'InvokeChangesetOperationParams', 'InvokeChangesetOperationResult', - 'NewComment', 'CreateCommentThreadParams', 'CreateCommentThreadResult', 'UpdateCommentThreadParams', 'DeleteCommentThreadParams', diff --git a/scripts/generate-markdown.ts b/scripts/generate-markdown.ts index 36a69356..3f1fbf3f 100644 --- a/scripts/generate-markdown.ts +++ b/scripts/generate-markdown.ts @@ -53,6 +53,7 @@ const DIR_TO_PAGE: Record = { 'channels-session': 'session', 'channels-terminal': 'terminal', 'channels-changeset': 'changeset', + 'channels-comments': 'comments', 'channels-otlp': 'otlp', }; @@ -1003,6 +1004,35 @@ function generateChangesetChannelPage(project: Project): string { return lines.join('\n'); } +function generateCommentsChannelPage(project: Project): string { + currentPage = 'comments'; + const stateSf = findChannelSourceFile(project, 'channels-comments', 'state.ts'); + const actionsSf = findChannelSourceFile(project, 'channels-comments', 'actions.ts'); + const commandsSf = findChannelSourceFile(project, 'channels-comments', 'commands.ts'); + + const lines: string[] = [GENERATED_HEADER]; + lines.push('# Comments Channel\n'); + lines.push('Reference for the `ahp-session://comments` channel — server-owned comment threads anchored to file ranges within a session turn. Clients mutate comments through commands; servers echo state changes as comments actions.\n'); + lines.push(schemaLink('state.schema.json')); + + if (stateSf) { + lines.push('## State Types\n'); + lines.push(emitStateTypesSection([stateSf])); + } + if (actionsSf) { + lines.push('## Actions\n'); + lines.push('Mutate `CommentsState`. Scoped to a comments channel URI via the enclosing `ActionEnvelope.channel`.\n'); + lines.push(schemaLink('actions.schema.json')); + lines.push(emitActionsSection([actionsSf])); + } + if (commandsSf) { + lines.push('## Commands\n'); + lines.push(schemaLink('commands.schema.json')); + lines.push(emitCommandsSection(project, [commandsSf])); + } + return lines.join('\n'); +} + function generateOtlpChannelPage(project: Project): string { currentPage = 'otlp'; const stateSf = findChannelSourceFile(project, 'channels-otlp', 'state.ts'); @@ -1243,6 +1273,7 @@ export function generateMarkdownDocs(project: Project, outDir: string): void { { filename: 'session.md', generator: generateSessionChannelPage }, { filename: 'terminal.md', generator: generateTerminalChannelPage }, { filename: 'changeset.md', generator: generateChangesetChannelPage }, + { filename: 'comments.md', generator: generateCommentsChannelPage }, { filename: 'otlp.md', generator: generateOtlpChannelPage }, { filename: 'messages.md', generator: generateMessagesPage }, { filename: 'error-codes.md', generator: generateErrorCodesPage }, diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 94dbbad4..67bbbd4e 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -526,7 +526,7 @@ const STATE_ENUMS = [ 'ToolCallConfirmationReason', 'ToolCallCancellationReason', 'ConfirmationOptionKind', 'ToolResultContentType', 'CustomizationType', 'CustomizationLoadStatus', 'TerminalClaimKind', - 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'CommentSource', 'ResourceChangeType', + 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'ResourceChangeType', ]; /** @@ -630,6 +630,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'CommentsState' }, { name: 'CommentThread' }, { name: 'Comment' }, + { name: 'NewComment' }, { name: 'TelemetryCapabilities' }, { name: 'ResourceWatchState' }, { name: 'ResourceChange' }, @@ -902,7 +903,6 @@ const ACTION_VARIANTS: { { type: 'session/isArchivedChanged', variantName: 'SessionIsArchivedChanged', tsInterface: 'SessionIsArchivedChangedAction' }, { type: 'session/activityChanged', variantName: 'SessionActivityChanged', tsInterface: 'SessionActivityChangedAction' }, { type: 'session/changesetsChanged', variantName: 'SessionChangesetsChanged', tsInterface: 'SessionChangesetsChangedAction' }, - { type: 'session/commentsChanged', variantName: 'SessionCommentsChanged', tsInterface: 'SessionCommentsChangedAction' }, { type: 'session/serverToolsChanged', variantName: 'SessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', variantName: 'SessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', variantName: 'SessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, @@ -926,11 +926,11 @@ const ACTION_VARIANTS: { { type: 'changeset/operationsChanged', variantName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', variantName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', variantName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, - { type: 'comment/threadSet', variantName: 'CommentThreadSet', tsInterface: 'CommentThreadSetAction' }, - { type: 'comment/threadRemoved', variantName: 'CommentThreadRemoved', tsInterface: 'CommentThreadRemovedAction' }, - { type: 'comment/set', variantName: 'CommentSet', tsInterface: 'CommentSetAction' }, - { type: 'comment/removed', variantName: 'CommentRemoved', tsInterface: 'CommentRemovedAction' }, - { type: 'comment/cleared', variantName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, + { type: 'comments/threadSet', variantName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, + { type: 'comments/threadRemoved', variantName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, + { type: 'comments/commentSet', variantName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, + { type: 'comments/commentRemoved', variantName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, + { type: 'comments/cleared', variantName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', variantName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'terminal/data', variantName: 'TerminalData', tsInterface: 'TerminalDataAction' }, { type: 'terminal/input', variantName: 'TerminalInput', tsInterface: 'TerminalInputAction' }, @@ -981,11 +981,7 @@ pub struct SessionToolCallConfirmedAction { function generateActionsFile(project: Project): string { const lines: string[] = [GENERATED_HEADER]; -<<<<<<< Updated upstream - 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, ChangesetSummary, CommentsSummary, CommentThread, Comment};'); ->>>>>>> Stashed changes + 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, Comment, CommentThread};'); lines.push(''); // ActionType enum @@ -1093,7 +1089,6 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: s { name: 'CompletionsParams' }, { name: 'CompletionItem' }, { name: 'CompletionsResult' }, { name: 'InvokeChangesetOperationParams' }, { name: 'InvokeChangesetOperationResult' }, { name: 'ChangesetOperationFollowUp' }, - { name: 'NewComment' }, { name: 'CreateCommentThreadParams' }, { name: 'CreateCommentThreadResult' }, { name: 'UpdateCommentThreadParams' }, { name: 'DeleteCommentThreadParams' }, @@ -1117,7 +1112,7 @@ function generateCommandsFile(project: Project): string { lines.push('#[allow(unused_imports)]'); lines.push('use crate::actions::{ActionEnvelope, StateAction};'); lines.push('#[allow(unused_imports)]'); - lines.push('use crate::state::{AgentSelection, ContentRef, MessageAttachment, ModelSelection, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, Turn};'); + lines.push('use crate::state::{AgentSelection, ContentRef, MessageAttachment, ModelSelection, NewComment, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, TextRange, Turn};'); lines.push(''); lines.push('// ─── Enums ────────────────────────────────────────────────────────────\n'); @@ -1200,7 +1195,7 @@ const NOTIFICATION_STRUCTS = [ function generateNotificationsFile(project: Project): string { const lines: string[] = [GENERATED_HEADER]; lines.push('#[allow(unused_imports)]'); - lines.push('use crate::state::{AgentSelection, ChangesSummary, Changeset, FileEdit, ModelSelection, ProjectInfo, SessionStatus, SessionSummary};'); + lines.push('use crate::state::{AgentSelection, ChangesSummary, Changeset, CommentsSummary, FileEdit, ModelSelection, ProjectInfo, SessionStatus, SessionSummary};'); lines.push(''); lines.push('// ─── Enums ────────────────────────────────────────────────────────────\n'); diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index f64eb5a6..f23421c2 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -498,7 +498,7 @@ const STATE_ENUMS = [ 'TurnState', 'MessageAttachmentKind', 'ResponsePartKind', 'ToolCallStatus', 'ToolCallConfirmationReason', 'ToolCallCancellationReason', 'ConfirmationOptionKind', 'ToolResultContentType', 'CustomizationType', 'CustomizationLoadStatus', 'TerminalClaimKind', - 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'CommentSource', 'ResourceChangeType', + 'ChangesetStatus', 'ChangesetOperationStatus', 'ChangesetOperationScope', 'ResourceChangeType', ]; const STATE_STRUCTS = [ @@ -537,12 +537,8 @@ const STATE_STRUCTS = [ 'TerminalClientClaim', 'TerminalSessionClaim', 'TerminalState', 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', -<<<<<<< Updated upstream 'Changeset', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', -======= - 'ChangesetSummary', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', - 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', ->>>>>>> Stashed changes + 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', 'NewComment', 'TelemetryCapabilities', 'ResourceWatchState', 'ResourceChange', ]; @@ -905,12 +901,11 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'changeset/operationsChanged', caseName: 'changesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', caseName: 'changesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', caseName: 'changesetCleared', tsInterface: 'ChangesetClearedAction' }, - { type: 'session/commentsChanged', caseName: 'sessionCommentsChanged', tsInterface: 'SessionCommentsChangedAction' }, - { type: 'comment/threadSet', caseName: 'commentThreadSet', tsInterface: 'CommentThreadSetAction' }, - { type: 'comment/threadRemoved', caseName: 'commentThreadRemoved', tsInterface: 'CommentThreadRemovedAction' }, - { type: 'comment/set', caseName: 'commentSet', tsInterface: 'CommentSetAction' }, - { type: 'comment/removed', caseName: 'commentRemoved', tsInterface: 'CommentRemovedAction' }, - { type: 'comment/cleared', caseName: 'commentsCleared', tsInterface: 'CommentsClearedAction' }, + { type: 'comments/threadSet', caseName: 'commentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, + { type: 'comments/threadRemoved', caseName: 'commentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, + { type: 'comments/commentSet', caseName: 'commentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, + { type: 'comments/commentRemoved', caseName: 'commentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, + { type: 'comments/cleared', caseName: 'commentsCleared', tsInterface: 'CommentsClearedAction' }, { type: 'root/terminalsChanged', caseName: 'rootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'root/configChanged', caseName: 'rootConfigChanged', tsInterface: 'RootConfigChangedAction' }, { type: 'terminal/data', caseName: 'terminalData', tsInterface: 'TerminalDataAction' }, @@ -1094,7 +1089,6 @@ const COMMAND_STRUCTS = [ 'SessionConfigValueItem', 'CompletionsParams', 'CompletionItem', 'CompletionsResult', 'InvokeChangesetOperationParams', 'InvokeChangesetOperationResult', - 'NewComment', 'CreateCommentThreadParams', 'CreateCommentThreadResult', 'UpdateCommentThreadParams', 'DeleteCommentThreadParams', diff --git a/types/common/actions.ts b/types/common/actions.ts index 1121f09d..0f47ca96 100644 --- a/types/common/actions.ts +++ b/types/common/actions.ts @@ -67,6 +67,14 @@ import type { ChangesetClearedAction, } from '../channels-changeset/actions.js'; +import type { + CommentsThreadSetAction, + CommentsThreadRemovedAction, + CommentsCommentSetAction, + CommentsCommentRemovedAction, + CommentsClearedAction, +} from '../channels-comments/actions.js'; + import type { TerminalDataAction, TerminalInputAction, @@ -141,6 +149,11 @@ export const enum ActionType { ChangesetOperationsChanged = 'changeset/operationsChanged', ChangesetOperationStatusChanged = 'changeset/operationStatusChanged', ChangesetCleared = 'changeset/cleared', + CommentsThreadSet = 'comments/threadSet', + CommentsThreadRemoved = 'comments/threadRemoved', + CommentsCommentSet = 'comments/commentSet', + CommentsCommentRemoved = 'comments/commentRemoved', + CommentsCleared = 'comments/cleared', RootTerminalsChanged = 'root/terminalsChanged', RootConfigChanged = 'root/configChanged', TerminalData = 'terminal/data', @@ -241,6 +254,11 @@ export type StateAction = | ChangesetOperationsChangedAction | ChangesetOperationStatusChangedAction | ChangesetClearedAction + | CommentsThreadSetAction + | CommentsThreadRemovedAction + | CommentsCommentSetAction + | CommentsCommentRemovedAction + | CommentsClearedAction | TerminalDataAction | TerminalInputAction | TerminalResizedAction diff --git a/types/common/reducer-helpers.ts b/types/common/reducer-helpers.ts index bf5ab48c..9fa3a484 100644 --- a/types/common/reducer-helpers.ts +++ b/types/common/reducer-helpers.ts @@ -14,6 +14,8 @@ import type { ClientTerminalAction, ChangesetAction, ClientChangesetAction, + CommentsAction, + ClientCommentsAction, } from '../action-origin.generated.js'; import { IS_CLIENT_DISPATCHABLE } from '../action-origin.generated.js'; @@ -38,6 +40,6 @@ export function softAssertNever(value: never, log?: (msg: string) => void): void * Servers SHOULD call this to validate incoming `dispatchAction` requests * and reject any action the client is not allowed to originate. */ -export function isClientDispatchable(action: RootAction | SessionAction | TerminalAction | ChangesetAction): action is ClientRootAction | ClientSessionAction | ClientTerminalAction | ClientChangesetAction { +export function isClientDispatchable(action: RootAction | SessionAction | TerminalAction | ChangesetAction | CommentsAction): action is ClientRootAction | ClientSessionAction | ClientTerminalAction | ClientChangesetAction | ClientCommentsAction { return IS_CLIENT_DISPATCHABLE[action.type]; } diff --git a/types/reducers.test.ts b/types/reducers.test.ts index 671fd4b4..420af191 100644 --- a/types/reducers.test.ts +++ b/types/reducers.test.ts @@ -22,695 +22,22 @@ import { sessionReducer, terminalReducer, changesetReducer, + commentsReducer, resourceWatchReducer, isClientDispatchable, } from './reducers.js'; import { IS_CLIENT_DISPATCHABLE } from './action-origin.generated.js'; import { ActionType } from './actions.js'; -import type { RootState, SessionState, ChangesetState, ResourceWatchState } from './state.js'; +import type { RootState, SessionState, TerminalState, ChangesetState, CommentsState, ResourceWatchState } from './state.js'; import { SessionLifecycle, SessionStatus, TurnState, - MessageKind + MessageKind, } from './state.js'; -import type { TerminalState } from './state.js'; const root = resolve(dirname(fileURLToPath(import.meta.url))); -function readSource(file: string): string { - return readFileSync(resolve(root, file), 'utf-8'); -} - -/** - * Reads and concatenates every canonical per-channel source file matching - * `baseName` (e.g. `actions.ts`) under `types/common/` and - * `types/channels-*\/`. Used after the channel-organized refactor so the - * parsing in this test sees the union of declarations split across channels. - */ -function readChannelSources(baseName: string): string { - const dirs = [ - 'common', - 'channels-root', - 'channels-session', - 'channels-terminal', - 'channels-changeset', - 'channels-resource-watch', - ]; - return dirs - .map(dir => { - const p = resolve(root, dir, baseName); - try { - return readFileSync(p, 'utf-8'); - } catch { - return ''; - } - }) - .join('\n'); -} - -// ─── Fixture Loading ───────────────────────────────────────────────────────── - -interface Fixture { - description: string; - reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; - initial: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; - actions: unknown[]; - expected: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; -} - -/** - * Recursively replaces JSON `null` with `undefined` to match TypeScript - * reducer output, which uses `undefined` for absent optional fields. - */ -function nullToUndefined(value: T): T { - if (value === null) return undefined as unknown as T; - if (Array.isArray(value)) return value.map(nullToUndefined) as unknown as T; - if (typeof value === 'object') { - const result: Record = {}; - for (const [k, v] of Object.entries(value as Record)) { - result[k] = nullToUndefined(v); - } - return result as T; - } - return value; -} - -const fixtureDir = resolve(root, 'test-cases', 'reducers'); -const fixtureFiles = readdirSync(fixtureDir).filter(f => f.endsWith('.json')).sort(); - -const fixtures: Fixture[] = fixtureFiles.map(f => { - const raw = JSON.parse(readFileSync(resolve(fixtureDir, f), 'utf-8')); - return nullToUndefined(raw) as Fixture; -}); - -// ─── Fixture-Driven Reducer Tests ──────────────────────────────────────────── - -/** - * The reducers call Date.now() for modifiedAt timestamps. - * We mock it to a fixed value (9999) matching what was used during - * fixture generation, so expected values match exactly. - */ -const MOCK_NOW = 9999; -let originalDateNow: typeof Date.now; - -describe('reducer fixtures', () => { - beforeEach(() => { - originalDateNow = Date.now; - Date.now = () => MOCK_NOW; - }); - - afterEach(() => { - Date.now = originalDateNow; - }); - - for (const fixture of fixtures) { - it(fixture.description, () => { - let state = fixture.initial; - for (const action of fixture.actions) { - if (fixture.reducer === 'root') { - state = rootReducer(state as RootState, action as any); - } else if (fixture.reducer === 'terminal') { - state = terminalReducer(state as TerminalState, action as any); - } else if (fixture.reducer === 'changeset') { - state = changesetReducer(state as ChangesetState, action as any); - } else if (fixture.reducer === 'resourceWatch') { - state = resourceWatchReducer(state as ResourceWatchState, action as any); - } else { - state = sessionReducer(state as SessionState, action as any); - } - } - assert.deepStrictEqual(state, fixture.expected); - }); - } -}); - -// ─── IS_CLIENT_DISPATCHABLE validation ─────────────────────────────────────── -// -// These tests parse TypeScript source, so they must remain JS-only. - -describe('IS_CLIENT_DISPATCHABLE', () => { - it('matches @clientDispatchable annotations in actions.ts', () => { - const source = readChannelSources('actions.ts'); - - const jsdocInterfaceRe = /\/\*\*([\s\S]*?)\*\/\s*export\s+(?:interface|type)\s+(\w+)/g; - const clientDispatchableTypes = new Set(); - - for (const match of source.matchAll(jsdocInterfaceRe)) { - const [, jsdoc, name] = match; - if (!name.endsWith('Action')) continue; - - const afterDecl = source.slice(match.index! + match[0].length); - const typeMatch = afterDecl.match(/type:\s*ActionType\.(\w+)/); - if (!typeMatch) continue; - - if (jsdoc.includes('@clientDispatchable')) { - clientDispatchableTypes.add(typeMatch[1]); - } - } - - const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; - const enumMap = new Map(); - for (const match of source.matchAll(enumValueRe)) { - enumMap.set(match[1], match[2]); - } - - for (const [memberName, stringValue] of enumMap) { - if (!(stringValue in IS_CLIENT_DISPATCHABLE)) continue; - const expected = clientDispatchableTypes.has(memberName); - const actual = IS_CLIENT_DISPATCHABLE[stringValue as keyof typeof IS_CLIENT_DISPATCHABLE]; - assert.equal( - actual, - expected, - `IS_CLIENT_DISPATCHABLE['${stringValue}'] should be ${expected} (ActionType.${memberName})`, - ); - } - }); - - it('covers every ActionType enum member', () => { - const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; - const allValues: string[] = []; - for (const match of readChannelSources('actions.ts').matchAll(enumValueRe)) { - allValues.push(match[2]); - } - - const mapKeys = Object.keys(IS_CLIENT_DISPATCHABLE); - const missing = allValues.filter(v => !mapKeys.includes(v)); - assert.deepStrictEqual(missing, [], `Missing from IS_CLIENT_DISPATCHABLE: ${missing.join(', ')}`); - - const extra = mapKeys.filter(v => !allValues.includes(v)); - assert.deepStrictEqual(extra, [], `Extra in IS_CLIENT_DISPATCHABLE: ${extra.join(', ')}`); - }); -}); - -// ─── Dispatch Validation ───────────────────────────────────────────────────── - -describe('isClientDispatchable', () => { - it('returns true for client-dispatchable actions', () => { - const action = { type: ActionType.SessionTurnStarted, turnId: 't', message: { text: 'Hello', origin: { kind: MessageKind.User } } } as const; - assert.equal(isClientDispatchable(action), true); - }); - - it('returns false for server-only actions', () => { - const action = { type: ActionType.SessionReady, session: 'x' } as const; - assert.equal(isClientDispatchable(action), false); - }); -}); - -// ─── Immutability Checks ───────────────────────────────────────────────────── -// -// Verifying that the reducer does not mutate the input state requires -// identity checks (===), which can't be expressed in JSON fixtures. - -describe('reducer immutability', () => { - it('rootReducer does not mutate original state', () => { - const state: RootState = { agents: [] }; - const agents = [{ provider: 'x', displayName: 'X', description: 'x', models: [] }]; - rootReducer(state, { type: ActionType.RootAgentsChanged, agents }); - assert.deepStrictEqual(state.agents, []); - }); - - it('sessionReducer does not mutate original turns array', () => { - const turn1 = { id: 't1', message: { text: 'First', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const turn2 = { id: 't2', message: { text: 'Second', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const turn3 = { id: 't3', message: { text: 'Third', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const state: SessionState = { - summary: { resource: 'x', provider: 'copilot', title: 'T', status: SessionStatus.Idle, createdAt: 1000, modifiedAt: 1000, project: { uri: 'file:///test-project', displayName: 'Test Project' } }, - lifecycle: SessionLifecycle.Ready, - turns: [turn1, turn2, turn3], - }; - const original = [...state.turns]; - sessionReducer(state, { type: ActionType.SessionTruncated, turnId: 't1' }); - assert.deepStrictEqual(state.turns, original); - }); -}); -/** - * Reducer unit tests — driven by JSON fixtures for cross-language parity. - * - * Fixture format: { description, reducer, initial, actions, expected } - * Fixtures live in types/test-cases/reducers/*.json and can be consumed by - * any language implementation to verify reducer parity. - * - * Tests that are inherently JS-specific (source-code parsing, identity checks) - * remain as manual test cases below the fixture-driven tests. - * - * Run: npx tsx --test types/reducers.test.ts - */ - - - -const root = resolve(dirname(fileURLToPath(import.meta.url))); - -function readSource(file: string): string { - return readFileSync(resolve(root, file), 'utf-8'); -} - -/** - * Reads and concatenates every canonical per-channel source file matching - * `baseName` (e.g. `actions.ts`) under `types/common/` and - * `types/channels-*\/`. Used after the channel-organized refactor so the - * parsing in this test sees the union of declarations split across channels. - */ -function readChannelSources(baseName: string): string { - const dirs = [ - 'common', - 'channels-root', - 'channels-session', - 'channels-terminal', - 'channels-changeset', - 'channels-resource-watch', - ]; - return dirs - .map(dir => { - const p = resolve(root, dir, baseName); - try { - return readFileSync(p, 'utf-8'); - } catch { - return ''; - } - }) - .join('\n'); -} - -// ─── Fixture Loading ───────────────────────────────────────────────────────── - -interface Fixture { - description: string; - reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; - initial: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; - actions: unknown[]; - expected: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; -} - -/** - * Recursively replaces JSON `null` with `undefined` to match TypeScript - * reducer output, which uses `undefined` for absent optional fields. - */ -function nullToUndefined(value: T): T { - if (value === null) return undefined as unknown as T; - if (Array.isArray(value)) return value.map(nullToUndefined) as unknown as T; - if (typeof value === 'object') { - const result: Record = {}; - for (const [k, v] of Object.entries(value as Record)) { - result[k] = nullToUndefined(v); - } - return result as T; - } - return value; -} - -const fixtureDir = resolve(root, 'test-cases', 'reducers'); -const fixtureFiles = readdirSync(fixtureDir).filter(f => f.endsWith('.json')).sort(); - -const fixtures: Fixture[] = fixtureFiles.map(f => { - const raw = JSON.parse(readFileSync(resolve(fixtureDir, f), 'utf-8')); - return nullToUndefined(raw) as Fixture; -}); - -// ─── Fixture-Driven Reducer Tests ──────────────────────────────────────────── - -/** - * The reducers call Date.now() for modifiedAt timestamps. - * We mock it to a fixed value (9999) matching what was used during - * fixture generation, so expected values match exactly. - */ -const MOCK_NOW = 9999; -let originalDateNow: typeof Date.now; - -describe('reducer fixtures', () => { - beforeEach(() => { - originalDateNow = Date.now; - Date.now = () => MOCK_NOW; - }); - - afterEach(() => { - Date.now = originalDateNow; - }); - - for (const fixture of fixtures) { - it(fixture.description, () => { - let state = fixture.initial; - for (const action of fixture.actions) { - if (fixture.reducer === 'root') { - state = rootReducer(state as RootState, action as any); - } else if (fixture.reducer === 'terminal') { - state = terminalReducer(state as TerminalState, action as any); - } else if (fixture.reducer === 'changeset') { - state = changesetReducer(state as ChangesetState, action as any); - } else if (fixture.reducer === 'resourceWatch') { - state = resourceWatchReducer(state as ResourceWatchState, action as any); - } else { - state = sessionReducer(state as SessionState, action as any); - } - } - assert.deepStrictEqual(state, fixture.expected); - }); - } -}); - -// ─── IS_CLIENT_DISPATCHABLE validation ─────────────────────────────────────── -// -// These tests parse TypeScript source, so they must remain JS-only. - -describe('IS_CLIENT_DISPATCHABLE', () => { - it('matches @clientDispatchable annotations in actions.ts', () => { - const source = readChannelSources('actions.ts'); - - const jsdocInterfaceRe = /\/\*\*([\s\S]*?)\*\/\s*export\s+(?:interface|type)\s+(\w+)/g; - const clientDispatchableTypes = new Set(); - - for (const match of source.matchAll(jsdocInterfaceRe)) { - const [, jsdoc, name] = match; - if (!name.endsWith('Action')) continue; - - const afterDecl = source.slice(match.index! + match[0].length); - const typeMatch = afterDecl.match(/type:\s*ActionType\.(\w+)/); - if (!typeMatch) continue; - - if (jsdoc.includes('@clientDispatchable')) { - clientDispatchableTypes.add(typeMatch[1]); - } - } - - const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; - const enumMap = new Map(); - for (const match of source.matchAll(enumValueRe)) { - enumMap.set(match[1], match[2]); - } - - for (const [memberName, stringValue] of enumMap) { - if (!(stringValue in IS_CLIENT_DISPATCHABLE)) continue; - const expected = clientDispatchableTypes.has(memberName); - const actual = IS_CLIENT_DISPATCHABLE[stringValue as keyof typeof IS_CLIENT_DISPATCHABLE]; - assert.equal( - actual, - expected, - `IS_CLIENT_DISPATCHABLE['${stringValue}'] should be ${expected} (ActionType.${memberName})`, - ); - } - }); - - it('covers every ActionType enum member', () => { - const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; - const allValues: string[] = []; - for (const match of readChannelSources('actions.ts').matchAll(enumValueRe)) { - allValues.push(match[2]); - } - - const mapKeys = Object.keys(IS_CLIENT_DISPATCHABLE); - const missing = allValues.filter(v => !mapKeys.includes(v)); - assert.deepStrictEqual(missing, [], `Missing from IS_CLIENT_DISPATCHABLE: ${missing.join(', ')}`); - - const extra = mapKeys.filter(v => !allValues.includes(v)); - assert.deepStrictEqual(extra, [], `Extra in IS_CLIENT_DISPATCHABLE: ${extra.join(', ')}`); - }); -}); - -// ─── Dispatch Validation ───────────────────────────────────────────────────── - -describe('isClientDispatchable', () => { - it('returns true for client-dispatchable actions', () => { - const action = { type: ActionType.SessionTurnStarted, turnId: 't', message: { text: 'Hello', origin: { kind: MessageKind.User } } } as const; - assert.equal(isClientDispatchable(action), true); - }); - - it('returns false for server-only actions', () => { - const action = { type: ActionType.SessionReady, session: 'x' } as const; - assert.equal(isClientDispatchable(action), false); - }); -}); - -// ─── Immutability Checks ───────────────────────────────────────────────────── -// -// Verifying that the reducer does not mutate the input state requires -// identity checks (===), which can't be expressed in JSON fixtures. - -describe('reducer immutability', () => { - it('rootReducer does not mutate original state', () => { - const state: RootState = { agents: [] }; - const agents = [{ provider: 'x', displayName: 'X', description: 'x', models: [] }]; - rootReducer(state, { type: ActionType.RootAgentsChanged, agents }); - assert.deepStrictEqual(state.agents, []); - }); - - it('sessionReducer does not mutate original turns array', () => { - const turn1 = { id: 't1', message: { text: 'First', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const turn2 = { id: 't2', message: { text: 'Second', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const turn3 = { id: 't3', message: { text: 'Third', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const state: SessionState = { - summary: { resource: 'x', provider: 'copilot', title: 'T', status: SessionStatus.Idle, createdAt: 1000, modifiedAt: 1000, project: { uri: 'file:///test-project', displayName: 'Test Project' } }, - lifecycle: SessionLifecycle.Ready, - turns: [turn1, turn2, turn3], - }; - const original = [...state.turns]; - sessionReducer(state, { type: ActionType.SessionTruncated, turnId: 't1' }); - assert.deepStrictEqual(state.turns, original); - }); -}); -/** - * Reducer unit tests — driven by JSON fixtures for cross-language parity. - * - * Fixture format: { description, reducer, initial, actions, expected } - * Fixtures live in types/test-cases/reducers/*.json and can be consumed by - * any language implementation to verify reducer parity. - * - * Tests that are inherently JS-specific (source-code parsing, identity checks) - * remain as manual test cases below the fixture-driven tests. - * - * Run: npx tsx --test types/reducers.test.ts - */ - - - -const root = resolve(dirname(fileURLToPath(import.meta.url))); - -function readSource(file: string): string { - return readFileSync(resolve(root, file), 'utf-8'); -} - -/** - * Reads and concatenates every canonical per-channel source file matching - * `baseName` (e.g. `actions.ts`) under `types/common/` and - * `types/channels-*\/`. Used after the channel-organized refactor so the - * parsing in this test sees the union of declarations split across channels. - */ -function readChannelSources(baseName: string): string { - const dirs = [ - 'common', - 'channels-root', - 'channels-session', - 'channels-terminal', - 'channels-changeset', - 'channels-resource-watch', - ]; - return dirs - .map(dir => { - const p = resolve(root, dir, baseName); - try { - return readFileSync(p, 'utf-8'); - } catch { - return ''; - } - }) - .join('\n'); -} - -// ─── Fixture Loading ───────────────────────────────────────────────────────── - -interface Fixture { - description: string; - reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; - initial: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; - actions: unknown[]; - expected: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; -} - -/** - * Recursively replaces JSON `null` with `undefined` to match TypeScript - * reducer output, which uses `undefined` for absent optional fields. - */ -function nullToUndefined(value: T): T { - if (value === null) return undefined as unknown as T; - if (Array.isArray(value)) return value.map(nullToUndefined) as unknown as T; - if (typeof value === 'object') { - const result: Record = {}; - for (const [k, v] of Object.entries(value as Record)) { - result[k] = nullToUndefined(v); - } - return result as T; - } - return value; -} - -const fixtureDir = resolve(root, 'test-cases', 'reducers'); -const fixtureFiles = readdirSync(fixtureDir).filter(f => f.endsWith('.json')).sort(); - -const fixtures: Fixture[] = fixtureFiles.map(f => { - const raw = JSON.parse(readFileSync(resolve(fixtureDir, f), 'utf-8')); - return nullToUndefined(raw) as Fixture; -}); - -// ─── Fixture-Driven Reducer Tests ──────────────────────────────────────────── - -/** - * The reducers call Date.now() for modifiedAt timestamps. - * We mock it to a fixed value (9999) matching what was used during - * fixture generation, so expected values match exactly. - */ -const MOCK_NOW = 9999; -let originalDateNow: typeof Date.now; - -describe('reducer fixtures', () => { - beforeEach(() => { - originalDateNow = Date.now; - Date.now = () => MOCK_NOW; - }); - - afterEach(() => { - Date.now = originalDateNow; - }); - - for (const fixture of fixtures) { - it(fixture.description, () => { - let state = fixture.initial; - for (const action of fixture.actions) { - if (fixture.reducer === 'root') { - state = rootReducer(state as RootState, action as any); - } else if (fixture.reducer === 'terminal') { - state = terminalReducer(state as TerminalState, action as any); - } else if (fixture.reducer === 'changeset') { - state = changesetReducer(state as ChangesetState, action as any); - } else if (fixture.reducer === 'resourceWatch') { - state = resourceWatchReducer(state as ResourceWatchState, action as any); - } else { - state = sessionReducer(state as SessionState, action as any); - } - } - assert.deepStrictEqual(state, fixture.expected); - }); - } -}); - -// ─── IS_CLIENT_DISPATCHABLE validation ─────────────────────────────────────── -// -// These tests parse TypeScript source, so they must remain JS-only. - -describe('IS_CLIENT_DISPATCHABLE', () => { - it('matches @clientDispatchable annotations in actions.ts', () => { - const source = readChannelSources('actions.ts'); - - const jsdocInterfaceRe = /\/\*\*([\s\S]*?)\*\/\s*export\s+(?:interface|type)\s+(\w+)/g; - const clientDispatchableTypes = new Set(); - - for (const match of source.matchAll(jsdocInterfaceRe)) { - const [, jsdoc, name] = match; - if (!name.endsWith('Action')) continue; - - const afterDecl = source.slice(match.index! + match[0].length); - const typeMatch = afterDecl.match(/type:\s*ActionType\.(\w+)/); - if (!typeMatch) continue; - - if (jsdoc.includes('@clientDispatchable')) { - clientDispatchableTypes.add(typeMatch[1]); - } - } - - const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; - const enumMap = new Map(); - for (const match of source.matchAll(enumValueRe)) { - enumMap.set(match[1], match[2]); - } - - for (const [memberName, stringValue] of enumMap) { - if (!(stringValue in IS_CLIENT_DISPATCHABLE)) continue; - const expected = clientDispatchableTypes.has(memberName); - const actual = IS_CLIENT_DISPATCHABLE[stringValue as keyof typeof IS_CLIENT_DISPATCHABLE]; - assert.equal( - actual, - expected, - `IS_CLIENT_DISPATCHABLE['${stringValue}'] should be ${expected} (ActionType.${memberName})`, - ); - } - }); - - it('covers every ActionType enum member', () => { - const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; - const allValues: string[] = []; - for (const match of readChannelSources('actions.ts').matchAll(enumValueRe)) { - allValues.push(match[2]); - } - - const mapKeys = Object.keys(IS_CLIENT_DISPATCHABLE); - const missing = allValues.filter(v => !mapKeys.includes(v)); - assert.deepStrictEqual(missing, [], `Missing from IS_CLIENT_DISPATCHABLE: ${missing.join(', ')}`); - - const extra = mapKeys.filter(v => !allValues.includes(v)); - assert.deepStrictEqual(extra, [], `Extra in IS_CLIENT_DISPATCHABLE: ${extra.join(', ')}`); - }); -}); - -// ─── Dispatch Validation ───────────────────────────────────────────────────── - -describe('isClientDispatchable', () => { - it('returns true for client-dispatchable actions', () => { - const action = { type: ActionType.SessionTurnStarted, turnId: 't', message: { text: 'Hello', origin: { kind: MessageKind.User } } } as const; - assert.equal(isClientDispatchable(action), true); - }); - - it('returns false for server-only actions', () => { - const action = { type: ActionType.SessionReady, session: 'x' } as const; - assert.equal(isClientDispatchable(action), false); - }); -}); - -// ─── Immutability Checks ───────────────────────────────────────────────────── -// -// Verifying that the reducer does not mutate the input state requires -// identity checks (===), which can't be expressed in JSON fixtures. - -describe('reducer immutability', () => { - it('rootReducer does not mutate original state', () => { - const state: RootState = { agents: [] }; - const agents = [{ provider: 'x', displayName: 'X', description: 'x', models: [] }]; - rootReducer(state, { type: ActionType.RootAgentsChanged, agents }); - assert.deepStrictEqual(state.agents, []); - }); - - it('sessionReducer does not mutate original turns array', () => { - const turn1 = { id: 't1', message: { text: 'First', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const turn2 = { id: 't2', message: { text: 'Second', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const turn3 = { id: 't3', message: { text: 'Third', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const state: SessionState = { - summary: { resource: 'x', provider: 'copilot', title: 'T', status: SessionStatus.Idle, createdAt: 1000, modifiedAt: 1000, project: { uri: 'file:///test-project', displayName: 'Test Project' } }, - lifecycle: SessionLifecycle.Ready, - turns: [turn1, turn2, turn3], - }; - const original = [...state.turns]; - sessionReducer(state, { type: ActionType.SessionTruncated, turnId: 't1' }); - assert.deepStrictEqual(state.turns, original); - }); -}); -/** - * Reducer unit tests — driven by JSON fixtures for cross-language parity. - * - * Fixture format: { description, reducer, initial, actions, expected } - * Fixtures live in types/test-cases/reducers/*.json and can be consumed by - * any language implementation to verify reducer parity. - * - * Tests that are inherently JS-specific (source-code parsing, identity checks) - * remain as manual test cases below the fixture-driven tests. - * - * Run: npx tsx --test types/reducers.test.ts - */ - - - -const root = resolve(dirname(fileURLToPath(import.meta.url))); - -function readSource(file: string): string { - return readFileSync(resolve(root, file), 'utf-8'); -} - /** * Reads and concatenates every canonical per-channel source file matching * `baseName` (e.g. `actions.ts`) under `types/common/` and @@ -724,6 +51,7 @@ function readChannelSources(baseName: string): string { 'channels-session', 'channels-terminal', 'channels-changeset', + 'channels-comments', 'channels-resource-watch', ]; return dirs @@ -740,235 +68,14 @@ function readChannelSources(baseName: string): string { // ─── Fixture Loading ───────────────────────────────────────────────────────── -interface Fixture { - description: string; - reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; - initial: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; - actions: unknown[]; - expected: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; -} - -/** - * Recursively replaces JSON `null` with `undefined` to match TypeScript - * reducer output, which uses `undefined` for absent optional fields. - */ -function nullToUndefined(value: T): T { - if (value === null) return undefined as unknown as T; - if (Array.isArray(value)) return value.map(nullToUndefined) as unknown as T; - if (typeof value === 'object') { - const result: Record = {}; - for (const [k, v] of Object.entries(value as Record)) { - result[k] = nullToUndefined(v); - } - return result as T; - } - return value; -} - -const fixtureDir = resolve(root, 'test-cases', 'reducers'); -const fixtureFiles = readdirSync(fixtureDir).filter(f => f.endsWith('.json')).sort(); - -const fixtures: Fixture[] = fixtureFiles.map(f => { - const raw = JSON.parse(readFileSync(resolve(fixtureDir, f), 'utf-8')); - return nullToUndefined(raw) as Fixture; -}); - -// ─── Fixture-Driven Reducer Tests ──────────────────────────────────────────── - -/** - * The reducers call Date.now() for modifiedAt timestamps. - * We mock it to a fixed value (9999) matching what was used during - * fixture generation, so expected values match exactly. - */ -const MOCK_NOW = 9999; -let originalDateNow: typeof Date.now; - -describe('reducer fixtures', () => { - beforeEach(() => { - originalDateNow = Date.now; - Date.now = () => MOCK_NOW; - }); - - afterEach(() => { - Date.now = originalDateNow; - }); - - for (const fixture of fixtures) { - it(fixture.description, () => { - let state = fixture.initial; - for (const action of fixture.actions) { - if (fixture.reducer === 'root') { - state = rootReducer(state as RootState, action as any); - } else if (fixture.reducer === 'terminal') { - state = terminalReducer(state as TerminalState, action as any); - } else if (fixture.reducer === 'changeset') { - state = changesetReducer(state as ChangesetState, action as any); - } else if (fixture.reducer === 'resourceWatch') { - state = resourceWatchReducer(state as ResourceWatchState, action as any); - } else { - state = sessionReducer(state as SessionState, action as any); - } - } - assert.deepStrictEqual(state, fixture.expected); - }); - } -}); - -// ─── IS_CLIENT_DISPATCHABLE validation ─────────────────────────────────────── -// -// These tests parse TypeScript source, so they must remain JS-only. - -describe('IS_CLIENT_DISPATCHABLE', () => { - it('matches @clientDispatchable annotations in actions.ts', () => { - const source = readChannelSources('actions.ts'); - - const jsdocInterfaceRe = /\/\*\*([\s\S]*?)\*\/\s*export\s+(?:interface|type)\s+(\w+)/g; - const clientDispatchableTypes = new Set(); - - for (const match of source.matchAll(jsdocInterfaceRe)) { - const [, jsdoc, name] = match; - if (!name.endsWith('Action')) continue; - - const afterDecl = source.slice(match.index! + match[0].length); - const typeMatch = afterDecl.match(/type:\s*ActionType\.(\w+)/); - if (!typeMatch) continue; - - if (jsdoc.includes('@clientDispatchable')) { - clientDispatchableTypes.add(typeMatch[1]); - } - } - - const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; - const enumMap = new Map(); - for (const match of source.matchAll(enumValueRe)) { - enumMap.set(match[1], match[2]); - } - - for (const [memberName, stringValue] of enumMap) { - if (!(stringValue in IS_CLIENT_DISPATCHABLE)) continue; - const expected = clientDispatchableTypes.has(memberName); - const actual = IS_CLIENT_DISPATCHABLE[stringValue as keyof typeof IS_CLIENT_DISPATCHABLE]; - assert.equal( - actual, - expected, - `IS_CLIENT_DISPATCHABLE['${stringValue}'] should be ${expected} (ActionType.${memberName})`, - ); - } - }); - - it('covers every ActionType enum member', () => { - const enumValueRe = /(\w+)\s*=\s*'([^']+)'/g; - const allValues: string[] = []; - for (const match of readChannelSources('actions.ts').matchAll(enumValueRe)) { - allValues.push(match[2]); - } - - const mapKeys = Object.keys(IS_CLIENT_DISPATCHABLE); - const missing = allValues.filter(v => !mapKeys.includes(v)); - assert.deepStrictEqual(missing, [], `Missing from IS_CLIENT_DISPATCHABLE: ${missing.join(', ')}`); - - const extra = mapKeys.filter(v => !allValues.includes(v)); - assert.deepStrictEqual(extra, [], `Extra in IS_CLIENT_DISPATCHABLE: ${extra.join(', ')}`); - }); -}); - -// ─── Dispatch Validation ───────────────────────────────────────────────────── - -describe('isClientDispatchable', () => { - it('returns true for client-dispatchable actions', () => { - const action = { type: ActionType.SessionTurnStarted, turnId: 't', message: { text: 'Hello', origin: { kind: MessageKind.User } } } as const; - assert.equal(isClientDispatchable(action), true); - }); - - it('returns false for server-only actions', () => { - const action = { type: ActionType.SessionReady, session: 'x' } as const; - assert.equal(isClientDispatchable(action), false); - }); -}); - -// ─── Immutability Checks ───────────────────────────────────────────────────── -// -// Verifying that the reducer does not mutate the input state requires -// identity checks (===), which can't be expressed in JSON fixtures. - -describe('reducer immutability', () => { - it('rootReducer does not mutate original state', () => { - const state: RootState = { agents: [] }; - const agents = [{ provider: 'x', displayName: 'X', description: 'x', models: [] }]; - rootReducer(state, { type: ActionType.RootAgentsChanged, agents }); - assert.deepStrictEqual(state.agents, []); - }); - - it('sessionReducer does not mutate original turns array', () => { - const turn1 = { id: 't1', message: { text: 'First', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const turn2 = { id: 't2', message: { text: 'Second', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const turn3 = { id: 't3', message: { text: 'Third', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const state: SessionState = { - summary: { resource: 'x', provider: 'copilot', title: 'T', status: SessionStatus.Idle, createdAt: 1000, modifiedAt: 1000, project: { uri: 'file:///test-project', displayName: 'Test Project' } }, - lifecycle: SessionLifecycle.Ready, - turns: [turn1, turn2, turn3], - }; - const original = [...state.turns]; - sessionReducer(state, { type: ActionType.SessionTruncated, turnId: 't1' }); - assert.deepStrictEqual(state.turns, original); - }); -}); -/** - * Reducer unit tests — driven by JSON fixtures for cross-language parity. - * - * Fixture format: { description, reducer, initial, actions, expected } - * Fixtures live in types/test-cases/reducers/*.json and can be consumed by - * any language implementation to verify reducer parity. - * - * Tests that are inherently JS-specific (source-code parsing, identity checks) - * remain as manual test cases below the fixture-driven tests. - * - * Run: npx tsx --test types/reducers.test.ts - */ - - - -const root = resolve(dirname(fileURLToPath(import.meta.url))); - -function readSource(file: string): string { - return readFileSync(resolve(root, file), 'utf-8'); -} - -/** - * Reads and concatenates every canonical per-channel source file matching - * `baseName` (e.g. `actions.ts`) under `types/common/` and - * `types/channels-*\/`. Used after the channel-organized refactor so the - * parsing in this test sees the union of declarations split across channels. - */ -function readChannelSources(baseName: string): string { - const dirs = [ - 'common', - 'channels-root', - 'channels-session', - 'channels-terminal', - 'channels-changeset', - 'channels-resource-watch', - ]; - return dirs - .map(dir => { - const p = resolve(root, dir, baseName); - try { - return readFileSync(p, 'utf-8'); - } catch { - return ''; - } - }) - .join('\n'); -} - -// ─── Fixture Loading ───────────────────────────────────────────────────────── +type FixtureState = RootState | SessionState | TerminalState | ChangesetState | CommentsState | ResourceWatchState; interface Fixture { description: string; - reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'resourceWatch'; - initial: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; + reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'comments' | 'resourceWatch'; + initial: FixtureState; actions: unknown[]; - expected: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState; + expected: FixtureState; } /** @@ -1026,6 +133,8 @@ describe('reducer fixtures', () => { state = terminalReducer(state as TerminalState, action as any); } else if (fixture.reducer === 'changeset') { state = changesetReducer(state as ChangesetState, action as any); + } else if (fixture.reducer === 'comments') { + state = commentsReducer(state as CommentsState, action as any); } else if (fixture.reducer === 'resourceWatch') { state = resourceWatchReducer(state as ResourceWatchState, action as any); } else { diff --git a/types/test-cases/reducers/300-comments-threadset-appends-new-thread.json b/types/test-cases/reducers/300-comments-threadset-appends-new-thread.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/301-comments-threadset-replaces-existing-thread.json b/types/test-cases/reducers/301-comments-threadset-replaces-existing-thread.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/302-comments-threadset-empty-comments-removes-thread.json b/types/test-cases/reducers/302-comments-threadset-empty-comments-removes-thread.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/303-comments-threadset-empty-unknown-id-is-noop.json b/types/test-cases/reducers/303-comments-threadset-empty-unknown-id-is-noop.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/304-comments-threadremoved-drops-matching-thread.json b/types/test-cases/reducers/304-comments-threadremoved-drops-matching-thread.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/305-comments-threadremoved-unknown-id-is-noop.json b/types/test-cases/reducers/305-comments-threadremoved-unknown-id-is-noop.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/306-comments-commentset-appends-new-comment.json b/types/test-cases/reducers/306-comments-commentset-appends-new-comment.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/307-comments-commentset-edits-existing-comment.json b/types/test-cases/reducers/307-comments-commentset-edits-existing-comment.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/308-comments-commentset-unknown-thread-is-noop.json b/types/test-cases/reducers/308-comments-commentset-unknown-thread-is-noop.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/309-comments-commentremoved-drops-comment.json b/types/test-cases/reducers/309-comments-commentremoved-drops-comment.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/310-comments-commentremoved-last-comment-removes-thread.json b/types/test-cases/reducers/310-comments-commentremoved-last-comment-removes-thread.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/311-comments-commentremoved-unknown-thread-is-noop.json b/types/test-cases/reducers/311-comments-commentremoved-unknown-thread-is-noop.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/312-comments-commentremoved-unknown-comment-id-is-noop.json b/types/test-cases/reducers/312-comments-commentremoved-unknown-comment-id-is-noop.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/313-comments-cleared-drops-all-threads.json b/types/test-cases/reducers/313-comments-cleared-drops-all-threads.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/314-comments-cleared-empty-is-noop.json b/types/test-cases/reducers/314-comments-cleared-empty-is-noop.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/315-comments-unknown-action-type-is-noop.json b/types/test-cases/reducers/315-comments-unknown-action-type-is-noop.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/316-session-commentssummarychanged-sets-comments.json b/types/test-cases/reducers/316-session-commentssummarychanged-sets-comments.json deleted file mode 100644 index e69de29b..00000000 diff --git a/types/test-cases/reducers/317-session-commentssummarychanged-clears-comments.json b/types/test-cases/reducers/317-session-commentssummarychanged-clears-comments.json deleted file mode 100644 index e69de29b..00000000 From 1b7a54959290f3e0f82501875462a14ce0116843 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 5 Jun 2026 00:27:40 +0200 Subject: [PATCH 05/11] fix conflicts --- clients/go/ahptypes/actions.generated.go | 336 +++++++++--------- clients/go/ahptypes/commands.generated.go | 58 +-- clients/go/ahptypes/errors.generated.go | 20 +- .../go/ahptypes/notifications.generated.go | 28 +- clients/go/ahptypes/state.generated.go | 310 ++++++++-------- .../microsoft/agenthostprotocol/Reducers.kt | 8 + .../crates/ahp/src/multi_host_state_mirror.rs | 4 +- .../AHPStateMirror.swift | 14 +- .../MultiHostStateMirror.swift | 19 +- 9 files changed, 414 insertions(+), 383 deletions(-) diff --git a/clients/go/ahptypes/actions.generated.go b/clients/go/ahptypes/actions.generated.go index 218e1526..4906a63e 100644 --- a/clients/go/ahptypes/actions.generated.go +++ b/clients/go/ahptypes/actions.generated.go @@ -19,82 +19,82 @@ var _ = json.RawMessage(nil) type ActionType string const ( - ActionTypeRootAgentsChanged ActionType = "root/agentsChanged" - ActionTypeRootActiveSessionsChanged ActionType = "root/activeSessionsChanged" - ActionTypeSessionReady ActionType = "session/ready" - ActionTypeSessionCreationFailed ActionType = "session/creationFailed" - ActionTypeSessionTurnStarted ActionType = "session/turnStarted" - ActionTypeSessionDelta ActionType = "session/delta" - ActionTypeSessionResponsePart ActionType = "session/responsePart" - ActionTypeSessionToolCallStart ActionType = "session/toolCallStart" - ActionTypeSessionToolCallDelta ActionType = "session/toolCallDelta" - ActionTypeSessionToolCallReady ActionType = "session/toolCallReady" - ActionTypeSessionToolCallConfirmed ActionType = "session/toolCallConfirmed" - ActionTypeSessionToolCallComplete ActionType = "session/toolCallComplete" - ActionTypeSessionToolCallResultConfirmed ActionType = "session/toolCallResultConfirmed" - ActionTypeSessionToolCallContentChanged ActionType = "session/toolCallContentChanged" - ActionTypeSessionTurnComplete ActionType = "session/turnComplete" - ActionTypeSessionTurnCancelled ActionType = "session/turnCancelled" - ActionTypeSessionError ActionType = "session/error" - ActionTypeSessionTitleChanged ActionType = "session/titleChanged" - ActionTypeSessionUsage ActionType = "session/usage" - ActionTypeSessionReasoning ActionType = "session/reasoning" - ActionTypeSessionModelChanged ActionType = "session/modelChanged" - ActionTypeSessionAgentChanged ActionType = "session/agentChanged" - ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged" - ActionTypeSessionActiveClientChanged ActionType = "session/activeClientChanged" - ActionTypeSessionActiveClientToolsChanged ActionType = "session/activeClientToolsChanged" - ActionTypeSessionPendingMessageSet ActionType = "session/pendingMessageSet" - ActionTypeSessionPendingMessageRemoved ActionType = "session/pendingMessageRemoved" - ActionTypeSessionQueuedMessagesReordered ActionType = "session/queuedMessagesReordered" - ActionTypeSessionInputRequested ActionType = "session/inputRequested" - ActionTypeSessionInputAnswerChanged ActionType = "session/inputAnswerChanged" - ActionTypeSessionInputCompleted ActionType = "session/inputCompleted" - ActionTypeSessionCustomizationsChanged ActionType = "session/customizationsChanged" - ActionTypeSessionCustomizationToggled ActionType = "session/customizationToggled" - ActionTypeSessionCustomizationUpdated ActionType = "session/customizationUpdated" - ActionTypeSessionCustomizationRemoved ActionType = "session/customizationRemoved" - ActionTypeSessionMcpServerStateChanged ActionType = "session/mcpServerStateChanged" - ActionTypeSessionTruncated ActionType = "session/truncated" - 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" - ActionTypeChangesetFileSet ActionType = "changeset/fileSet" - ActionTypeChangesetFileRemoved ActionType = "changeset/fileRemoved" - ActionTypeChangesetOperationsChanged ActionType = "changeset/operationsChanged" - ActionTypeChangesetOperationStatusChanged ActionType = "changeset/operationStatusChanged" - ActionTypeChangesetCleared ActionType = "changeset/cleared" - ActionTypeCommentsThreadSet ActionType = "comments/threadSet" - ActionTypeCommentsThreadRemoved ActionType = "comments/threadRemoved" - ActionTypeCommentsCommentSet ActionType = "comments/commentSet" - ActionTypeCommentsCommentRemoved ActionType = "comments/commentRemoved" - ActionTypeCommentsCleared ActionType = "comments/cleared" - ActionTypeRootTerminalsChanged ActionType = "root/terminalsChanged" - ActionTypeRootConfigChanged ActionType = "root/configChanged" - ActionTypeTerminalData ActionType = "terminal/data" - ActionTypeTerminalInput ActionType = "terminal/input" - ActionTypeTerminalResized ActionType = "terminal/resized" - ActionTypeTerminalClaimed ActionType = "terminal/claimed" - ActionTypeTerminalTitleChanged ActionType = "terminal/titleChanged" - ActionTypeTerminalCwdChanged ActionType = "terminal/cwdChanged" - ActionTypeTerminalExited ActionType = "terminal/exited" - ActionTypeTerminalCleared ActionType = "terminal/cleared" + ActionTypeRootAgentsChanged ActionType = "root/agentsChanged" + ActionTypeRootActiveSessionsChanged ActionType = "root/activeSessionsChanged" + ActionTypeSessionReady ActionType = "session/ready" + ActionTypeSessionCreationFailed ActionType = "session/creationFailed" + ActionTypeSessionTurnStarted ActionType = "session/turnStarted" + ActionTypeSessionDelta ActionType = "session/delta" + ActionTypeSessionResponsePart ActionType = "session/responsePart" + ActionTypeSessionToolCallStart ActionType = "session/toolCallStart" + ActionTypeSessionToolCallDelta ActionType = "session/toolCallDelta" + ActionTypeSessionToolCallReady ActionType = "session/toolCallReady" + ActionTypeSessionToolCallConfirmed ActionType = "session/toolCallConfirmed" + ActionTypeSessionToolCallComplete ActionType = "session/toolCallComplete" + ActionTypeSessionToolCallResultConfirmed ActionType = "session/toolCallResultConfirmed" + ActionTypeSessionToolCallContentChanged ActionType = "session/toolCallContentChanged" + ActionTypeSessionTurnComplete ActionType = "session/turnComplete" + ActionTypeSessionTurnCancelled ActionType = "session/turnCancelled" + ActionTypeSessionError ActionType = "session/error" + ActionTypeSessionTitleChanged ActionType = "session/titleChanged" + ActionTypeSessionUsage ActionType = "session/usage" + ActionTypeSessionReasoning ActionType = "session/reasoning" + ActionTypeSessionModelChanged ActionType = "session/modelChanged" + ActionTypeSessionAgentChanged ActionType = "session/agentChanged" + ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged" + ActionTypeSessionActiveClientChanged ActionType = "session/activeClientChanged" + ActionTypeSessionActiveClientToolsChanged ActionType = "session/activeClientToolsChanged" + ActionTypeSessionPendingMessageSet ActionType = "session/pendingMessageSet" + ActionTypeSessionPendingMessageRemoved ActionType = "session/pendingMessageRemoved" + ActionTypeSessionQueuedMessagesReordered ActionType = "session/queuedMessagesReordered" + ActionTypeSessionInputRequested ActionType = "session/inputRequested" + ActionTypeSessionInputAnswerChanged ActionType = "session/inputAnswerChanged" + ActionTypeSessionInputCompleted ActionType = "session/inputCompleted" + ActionTypeSessionCustomizationsChanged ActionType = "session/customizationsChanged" + ActionTypeSessionCustomizationToggled ActionType = "session/customizationToggled" + ActionTypeSessionCustomizationUpdated ActionType = "session/customizationUpdated" + ActionTypeSessionCustomizationRemoved ActionType = "session/customizationRemoved" + ActionTypeSessionMcpServerStateChanged ActionType = "session/mcpServerStateChanged" + ActionTypeSessionTruncated ActionType = "session/truncated" + 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" + ActionTypeChangesetFileSet ActionType = "changeset/fileSet" + ActionTypeChangesetFileRemoved ActionType = "changeset/fileRemoved" + ActionTypeChangesetOperationsChanged ActionType = "changeset/operationsChanged" + ActionTypeChangesetOperationStatusChanged ActionType = "changeset/operationStatusChanged" + ActionTypeChangesetCleared ActionType = "changeset/cleared" + ActionTypeCommentsThreadSet ActionType = "comments/threadSet" + ActionTypeCommentsThreadRemoved ActionType = "comments/threadRemoved" + ActionTypeCommentsCommentSet ActionType = "comments/commentSet" + ActionTypeCommentsCommentRemoved ActionType = "comments/commentRemoved" + ActionTypeCommentsCleared ActionType = "comments/cleared" + ActionTypeRootTerminalsChanged ActionType = "root/terminalsChanged" + ActionTypeRootConfigChanged ActionType = "root/configChanged" + ActionTypeTerminalData ActionType = "terminal/data" + ActionTypeTerminalInput ActionType = "terminal/input" + ActionTypeTerminalResized ActionType = "terminal/resized" + ActionTypeTerminalClaimed ActionType = "terminal/claimed" + ActionTypeTerminalTitleChanged ActionType = "terminal/titleChanged" + ActionTypeTerminalCwdChanged ActionType = "terminal/cwdChanged" + ActionTypeTerminalExited ActionType = "terminal/exited" + ActionTypeTerminalCleared ActionType = "terminal/cleared" ActionTypeTerminalCommandDetectionAvailable ActionType = "terminal/commandDetectionAvailable" - ActionTypeTerminalCommandExecuted ActionType = "terminal/commandExecuted" - ActionTypeTerminalCommandFinished ActionType = "terminal/commandFinished" - ActionTypeResourceWatchChanged ActionType = "resourceWatch/changed" + ActionTypeTerminalCommandExecuted ActionType = "terminal/commandExecuted" + ActionTypeTerminalCommandFinished ActionType = "terminal/commandFinished" + ActionTypeResourceWatchChanged ActionType = "resourceWatch/changed" ) // ─── Action Envelope ───────────────────────────────────────────────── // Identifies the client that originally dispatched an action. type ActionOrigin struct { - ClientId string `json:"clientId"` - ClientSeq int64 `json:"clientSeq"` + ClientId string `json:"clientId"` + ClientSeq int64 `json:"clientSeq"` } // ActionEnvelope wraps every action with the channel URI it @@ -203,7 +203,7 @@ type SessionToolCallStartAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Internal tool name (for debugging/logging) ToolName string `json:"toolName"` // Human-readable tool name @@ -226,7 +226,7 @@ type SessionToolCallDeltaAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Partial parameter content to append Content string `json:"content"` // Updated progress message @@ -258,7 +258,7 @@ type SessionToolCallReadyAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Message describing what the tool will do or what confirmation is needed InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input @@ -281,17 +281,17 @@ type SessionToolCallReadyAction struct { // SessionToolCallConfirmedAction is the client approves or denies a // pending tool call (merged approved + denied variants on the wire). type SessionToolCallConfirmedAction struct { - Type ActionType `json:"type"` - TurnId string `json:"turnId"` - ToolCallId string `json:"toolCallId"` - Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Approved bool `json:"approved"` - Confirmed *ToolCallConfirmationReason `json:"confirmed,omitempty"` - Reason *ToolCallCancellationReason `json:"reason,omitempty"` - EditedToolInput *string `json:"editedToolInput,omitempty"` - UserSuggestion *Message `json:"userSuggestion,omitempty"` - ReasonMessage *StringOrMarkdown `json:"reasonMessage,omitempty"` - SelectedOptionId *string `json:"selectedOptionId,omitempty"` + Type ActionType `json:"type"` + TurnId string `json:"turnId"` + ToolCallId string `json:"toolCallId"` + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Approved bool `json:"approved"` + Confirmed *ToolCallConfirmationReason `json:"confirmed,omitempty"` + Reason *ToolCallCancellationReason `json:"reason,omitempty"` + EditedToolInput *string `json:"editedToolInput,omitempty"` + UserSuggestion *Message `json:"userSuggestion,omitempty"` + ReasonMessage *StringOrMarkdown `json:"reasonMessage,omitempty"` + SelectedOptionId *string `json:"selectedOptionId,omitempty"` } // Tool execution finished. Transitions to `completed` or `pending-result-confirmation` @@ -316,7 +316,7 @@ type SessionToolCallCompleteAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Execution result Result ToolCallResult `json:"result"` // If true, the result requires client approval before finalizing @@ -338,7 +338,7 @@ type SessionToolCallResultConfirmedAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Whether the result was approved Approved bool `json:"approved"` } @@ -608,10 +608,10 @@ type SessionCustomizationToggledAction struct { // // The reducer locates the existing entry by `customization.id`: // -// - If found, the entry is replaced entirely with `customization`, -// including its `children` array. To preserve existing children, the -// host must include them on the payload. -// - If not found, the entry is appended. +// - If found, the entry is replaced entirely with `customization`, +// including its `children` array. To preserve existing children, the +// host must include them on the payload. +// - If not found, the entry is appended. type SessionCustomizationUpdatedAction struct { Type ActionType `json:"type"` // The customization to upsert (matched by `customization.id`). @@ -720,7 +720,7 @@ type SessionToolCallContentChangedAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // The current partial content for the running tool call Content []ToolResultContent `json:"content"` } @@ -786,12 +786,12 @@ type ChangesetOperationStatusChangedAction struct { // Drop every file from the changeset. // // Two cases use this: -// 1. The underlying source moved (branch switched, fork point invalidated, -// …) and the server is recomputing from scratch — subsequent -// {@link ChangesetFileSetAction} entries will repopulate it. -// 2. The owning session has ended and the URI is becoming -// un-subscribable — the server will unsubscribe all clients shortly -// after dispatching this action. +// 1. The underlying source moved (branch switched, fork point invalidated, +// …) and the server is recomputing from scratch — subsequent +// {@link ChangesetFileSetAction} entries will repopulate it. +// 2. The owning session has ended and the URI is becoming +// un-subscribable — the server will unsubscribe all clients shortly +// after dispatching this action. // // Clients SHOULD release any references on receipt and SHOULD NOT // distinguish the two cases from the action alone — instead, react to @@ -816,11 +816,11 @@ type CommentsThreadSetAction struct { // Remove a {@link CommentThread} from the channel by its id. // // The server emits this in two cases: -// 1. The client explicitly invoked -// {@link DeleteCommentThreadParams | `deleteCommentThread`}. -// 2. The client invoked {@link DeleteCommentParams | `deleteComment`} on -// the last remaining comment in the thread — the protocol collapses -// the thread rather than leaving an empty one behind. +// 1. The client explicitly invoked +// {@link DeleteCommentThreadParams | `deleteCommentThread`}. +// 2. The client invoked {@link DeleteCommentParams | `deleteComment`} on +// the last remaining comment in the thread — the protocol collapses +// the thread rather than leaving an empty one behind. type CommentsThreadRemovedAction struct { Type ActionType `json:"type"` // The {@link CommentThread.id} of the thread to remove. @@ -1019,74 +1019,74 @@ type StateAction struct { // concrete variant of StateAction. type isStateAction interface{ isStateAction() } -func (*RootAgentsChangedAction) isStateAction() {} -func (*RootActiveSessionsChangedAction) isStateAction() {} -func (*RootConfigChangedAction) isStateAction() {} -func (*SessionReadyAction) isStateAction() {} -func (*SessionCreationFailedAction) isStateAction() {} -func (*SessionTurnStartedAction) isStateAction() {} -func (*SessionDeltaAction) isStateAction() {} -func (*SessionResponsePartAction) isStateAction() {} -func (*SessionToolCallStartAction) isStateAction() {} -func (*SessionToolCallDeltaAction) isStateAction() {} -func (*SessionToolCallReadyAction) isStateAction() {} -func (*SessionToolCallConfirmedAction) isStateAction() {} -func (*SessionToolCallCompleteAction) isStateAction() {} -func (*SessionToolCallResultConfirmedAction) isStateAction() {} -func (*SessionTurnCompleteAction) isStateAction() {} -func (*SessionTurnCancelledAction) isStateAction() {} -func (*SessionErrorAction) isStateAction() {} -func (*SessionTitleChangedAction) isStateAction() {} -func (*SessionUsageAction) isStateAction() {} -func (*SessionReasoningAction) isStateAction() {} -func (*SessionModelChangedAction) isStateAction() {} -func (*SessionAgentChangedAction) isStateAction() {} -func (*SessionIsReadChangedAction) isStateAction() {} -func (*SessionIsArchivedChangedAction) isStateAction() {} -func (*SessionActivityChangedAction) isStateAction() {} -func (*SessionChangesetsChangedAction) isStateAction() {} -func (*SessionServerToolsChangedAction) isStateAction() {} -func (*SessionActiveClientChangedAction) isStateAction() {} -func (*SessionActiveClientToolsChangedAction) isStateAction() {} -func (*SessionPendingMessageSetAction) isStateAction() {} -func (*SessionPendingMessageRemovedAction) isStateAction() {} -func (*SessionQueuedMessagesReorderedAction) isStateAction() {} -func (*SessionInputRequestedAction) isStateAction() {} -func (*SessionInputAnswerChangedAction) isStateAction() {} -func (*SessionInputCompletedAction) isStateAction() {} -func (*SessionCustomizationsChangedAction) isStateAction() {} -func (*SessionCustomizationToggledAction) isStateAction() {} -func (*SessionCustomizationUpdatedAction) isStateAction() {} -func (*SessionCustomizationRemovedAction) isStateAction() {} -func (*SessionMcpServerStateChangedAction) isStateAction() {} -func (*SessionTruncatedAction) isStateAction() {} -func (*SessionConfigChangedAction) isStateAction() {} -func (*SessionMetaChangedAction) isStateAction() {} -func (*SessionToolCallContentChangedAction) isStateAction() {} -func (*ChangesetStatusChangedAction) isStateAction() {} -func (*ChangesetFileSetAction) isStateAction() {} -func (*ChangesetFileRemovedAction) isStateAction() {} -func (*ChangesetOperationsChangedAction) isStateAction() {} -func (*ChangesetOperationStatusChangedAction) isStateAction() {} -func (*ChangesetClearedAction) isStateAction() {} -func (*CommentsThreadSetAction) isStateAction() {} -func (*CommentsThreadRemovedAction) isStateAction() {} -func (*CommentsCommentSetAction) isStateAction() {} -func (*CommentsCommentRemovedAction) isStateAction() {} -func (*CommentsClearedAction) isStateAction() {} -func (*RootTerminalsChangedAction) isStateAction() {} -func (*TerminalDataAction) isStateAction() {} -func (*TerminalInputAction) isStateAction() {} -func (*TerminalResizedAction) isStateAction() {} -func (*TerminalClaimedAction) isStateAction() {} -func (*TerminalTitleChangedAction) isStateAction() {} -func (*TerminalCwdChangedAction) isStateAction() {} -func (*TerminalExitedAction) isStateAction() {} -func (*TerminalClearedAction) isStateAction() {} +func (*RootAgentsChangedAction) isStateAction() {} +func (*RootActiveSessionsChangedAction) isStateAction() {} +func (*RootConfigChangedAction) isStateAction() {} +func (*SessionReadyAction) isStateAction() {} +func (*SessionCreationFailedAction) isStateAction() {} +func (*SessionTurnStartedAction) isStateAction() {} +func (*SessionDeltaAction) isStateAction() {} +func (*SessionResponsePartAction) isStateAction() {} +func (*SessionToolCallStartAction) isStateAction() {} +func (*SessionToolCallDeltaAction) isStateAction() {} +func (*SessionToolCallReadyAction) isStateAction() {} +func (*SessionToolCallConfirmedAction) isStateAction() {} +func (*SessionToolCallCompleteAction) isStateAction() {} +func (*SessionToolCallResultConfirmedAction) isStateAction() {} +func (*SessionTurnCompleteAction) isStateAction() {} +func (*SessionTurnCancelledAction) isStateAction() {} +func (*SessionErrorAction) isStateAction() {} +func (*SessionTitleChangedAction) isStateAction() {} +func (*SessionUsageAction) isStateAction() {} +func (*SessionReasoningAction) isStateAction() {} +func (*SessionModelChangedAction) isStateAction() {} +func (*SessionAgentChangedAction) isStateAction() {} +func (*SessionIsReadChangedAction) isStateAction() {} +func (*SessionIsArchivedChangedAction) isStateAction() {} +func (*SessionActivityChangedAction) isStateAction() {} +func (*SessionChangesetsChangedAction) isStateAction() {} +func (*SessionServerToolsChangedAction) isStateAction() {} +func (*SessionActiveClientChangedAction) isStateAction() {} +func (*SessionActiveClientToolsChangedAction) isStateAction() {} +func (*SessionPendingMessageSetAction) isStateAction() {} +func (*SessionPendingMessageRemovedAction) isStateAction() {} +func (*SessionQueuedMessagesReorderedAction) isStateAction() {} +func (*SessionInputRequestedAction) isStateAction() {} +func (*SessionInputAnswerChangedAction) isStateAction() {} +func (*SessionInputCompletedAction) isStateAction() {} +func (*SessionCustomizationsChangedAction) isStateAction() {} +func (*SessionCustomizationToggledAction) isStateAction() {} +func (*SessionCustomizationUpdatedAction) isStateAction() {} +func (*SessionCustomizationRemovedAction) isStateAction() {} +func (*SessionMcpServerStateChangedAction) isStateAction() {} +func (*SessionTruncatedAction) isStateAction() {} +func (*SessionConfigChangedAction) isStateAction() {} +func (*SessionMetaChangedAction) isStateAction() {} +func (*SessionToolCallContentChangedAction) isStateAction() {} +func (*ChangesetStatusChangedAction) isStateAction() {} +func (*ChangesetFileSetAction) isStateAction() {} +func (*ChangesetFileRemovedAction) isStateAction() {} +func (*ChangesetOperationsChangedAction) isStateAction() {} +func (*ChangesetOperationStatusChangedAction) isStateAction() {} +func (*ChangesetClearedAction) isStateAction() {} +func (*CommentsThreadSetAction) isStateAction() {} +func (*CommentsThreadRemovedAction) isStateAction() {} +func (*CommentsCommentSetAction) isStateAction() {} +func (*CommentsCommentRemovedAction) isStateAction() {} +func (*CommentsClearedAction) isStateAction() {} +func (*RootTerminalsChangedAction) isStateAction() {} +func (*TerminalDataAction) isStateAction() {} +func (*TerminalInputAction) isStateAction() {} +func (*TerminalResizedAction) isStateAction() {} +func (*TerminalClaimedAction) isStateAction() {} +func (*TerminalTitleChangedAction) isStateAction() {} +func (*TerminalCwdChangedAction) isStateAction() {} +func (*TerminalExitedAction) isStateAction() {} +func (*TerminalClearedAction) isStateAction() {} func (*TerminalCommandDetectionAvailableAction) isStateAction() {} -func (*TerminalCommandExecutedAction) isStateAction() {} -func (*TerminalCommandFinishedAction) isStateAction() {} -func (*ResourceWatchChangedAction) isStateAction() {} +func (*TerminalCommandExecutedAction) isStateAction() {} +func (*TerminalCommandFinishedAction) isStateAction() {} +func (*ResourceWatchChangedAction) isStateAction() {} // StateActionUnknown carries an unrecognized StateAction variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type StateActionUnknown struct { diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go index 1ff86fe0..54f41f54 100644 --- a/clients/go/ahptypes/commands.generated.go +++ b/clients/go/ahptypes/commands.generated.go @@ -19,7 +19,7 @@ var _ = json.RawMessage(nil) type ReconnectResultType string const ( - ReconnectResultTypeReplay ReconnectResultType = "replay" + ReconnectResultTypeReplay ReconnectResultType = "replay" ReconnectResultTypeSnapshot ReconnectResultType = "snapshot" ) @@ -28,7 +28,7 @@ type ContentEncoding string const ( ContentEncodingBase64 ContentEncoding = "base64" - ContentEncodingUtf8 ContentEncoding = "utf-8" + ContentEncodingUtf8 ContentEncoding = "utf-8" ) // The kind of completion items being requested. @@ -45,36 +45,36 @@ const ( type ResourceType string const ( - ResourceTypeFile ResourceType = "file" + ResourceTypeFile ResourceType = "file" ResourceTypeDirectory ResourceType = "directory" - ResourceTypeSymlink ResourceType = "symlink" + ResourceTypeSymlink ResourceType = "symlink" ) // How {@link ResourceWriteParams.data} is placed within the target file. // // Each mode interprets {@link ResourceWriteParams.position} differently: // -// - `truncate` (default): rooted at the **start** of the file. The file is -// truncated at `position` (0 by default) and `data` is written from that -// offset, so the resulting file is `existing[0..position] + data`. With -// `position` omitted this is a full overwrite. -// - `append`: rooted at the **end** of the file. `position` counts bytes -// backwards from EOF, so `position: 0` (the default) writes at EOF — -// POSIX append — and `position: 5` inserts `data` 5 bytes before the -// current EOF, shifting those trailing 5 bytes after the inserted region. -// The server MUST evaluate the effective EOF and write atomically with -// respect to other appenders so concurrent `append` writes do not -// clobber each other. -// - `insert`: rooted at the **start** of the file. `position` (0 by default) -// is the byte offset at which `data` is spliced in; bytes at or after -// `position` are shifted right by `data.length`. `insert` always grows -// the file — use `truncate` to overwrite bytes in place. +// - `truncate` (default): rooted at the **start** of the file. The file is +// truncated at `position` (0 by default) and `data` is written from that +// offset, so the resulting file is `existing[0..position] + data`. With +// `position` omitted this is a full overwrite. +// - `append`: rooted at the **end** of the file. `position` counts bytes +// backwards from EOF, so `position: 0` (the default) writes at EOF — +// POSIX append — and `position: 5` inserts `data` 5 bytes before the +// current EOF, shifting those trailing 5 bytes after the inserted region. +// The server MUST evaluate the effective EOF and write atomically with +// respect to other appenders so concurrent `append` writes do not +// clobber each other. +// - `insert`: rooted at the **start** of the file. `position` (0 by default) +// is the byte offset at which `data` is spliced in; bytes at or after +// `position` are shifted right by `data.length`. `insert` always grows +// the file — use `truncate` to overwrite bytes in place. type ResourceWriteMode string const ( ResourceWriteModeTruncate ResourceWriteMode = "truncate" - ResourceWriteModeAppend ResourceWriteMode = "append" - ResourceWriteModeInsert ResourceWriteMode = "insert" + ResourceWriteModeAppend ResourceWriteMode = "append" + ResourceWriteModeInsert ResourceWriteMode = "insert" ) // ─── Command Payloads ───────────────────────────────────────────────── @@ -801,9 +801,9 @@ type CompletionsParams struct { // A single completion item returned by the `completions` command. // // When the user accepts an item, the client SHOULD: -// 1. Replace the range `[rangeStart, rangeEnd)` in the input with `insertText` -// (or insert `insertText` at the cursor when the range is omitted). -// 2. Associate the item's `attachment` with the resulting {@link Message}. +// 1. Replace the range `[rangeStart, rangeEnd)` in the input with `insertText` +// (or insert `insertText` at the cursor when the range is omitted). +// 2. Associate the item's `attachment` with the resulting {@link Message}. type CompletionItem struct { // The text inserted into the input when this item is accepted. InsertText string `json:"insertText"` @@ -1003,7 +1003,7 @@ type ReconnectResult struct { // concrete variant of ReconnectResult. type isReconnectResult interface{ isReconnectResult() } -func (*ReconnectReplayResult) isReconnectResult() {} +func (*ReconnectReplayResult) isReconnectResult() {} func (*ReconnectSnapshotResult) isReconnectResult() {} // UnmarshalJSON decodes the variant indicated by the "type" discriminator. @@ -1061,10 +1061,10 @@ func (*ChangesetOperationResourceTarget) isChangesetOperationTarget() {} // ChangesetOperationRangeTarget targets a range within a resource. type ChangesetOperationRangeTarget struct { - Kind string `json:"kind"` - Resource URI `json:"resource"` - Side *string `json:"side,omitempty"` - Range ChangesetOperationTargetRange `json:"range"` + Kind string `json:"kind"` + Resource URI `json:"resource"` + Side *string `json:"side,omitempty"` + Range ChangesetOperationTargetRange `json:"range"` } func (*ChangesetOperationRangeTarget) isChangesetOperationTarget() {} diff --git a/clients/go/ahptypes/errors.generated.go b/clients/go/ahptypes/errors.generated.go index b5a22f4b..3b3e8f82 100644 --- a/clients/go/ahptypes/errors.generated.go +++ b/clients/go/ahptypes/errors.generated.go @@ -26,16 +26,16 @@ const ( // AHP application-specific error codes (above the JSON-RPC reserved // range). const ( - ErrorCodeSessionNotFound int32 = -32001 - ErrorCodeProviderNotFound int32 = -32002 - ErrorCodeSessionAlreadyExists int32 = -32003 - ErrorCodeTurnInProgress int32 = -32004 - ErrorCodeUnsupportedProtocolVersion int32 = -32005 - ErrorCodeContentNotFound int32 = -32006 - ErrorCodeAuthRequired int32 = -32007 - ErrorCodeNotFound int32 = -32008 - ErrorCodePermissionDenied int32 = -32009 - ErrorCodeAlreadyExists int32 = -32010 + ErrorCodeSessionNotFound int32 = -32001 + ErrorCodeProviderNotFound int32 = -32002 + ErrorCodeSessionAlreadyExists int32 = -32003 + ErrorCodeTurnInProgress int32 = -32004 + ErrorCodeUnsupportedProtocolVersion int32 = -32005 + ErrorCodeContentNotFound int32 = -32006 + ErrorCodeAuthRequired int32 = -32007 + ErrorCodeNotFound int32 = -32008 + ErrorCodePermissionDenied int32 = -32009 + ErrorCodeAlreadyExists int32 = -32010 ) // AhpErrorCode is the type alias used by AHP application error codes. diff --git a/clients/go/ahptypes/notifications.generated.go b/clients/go/ahptypes/notifications.generated.go index 7457347b..625be761 100644 --- a/clients/go/ahptypes/notifications.generated.go +++ b/clients/go/ahptypes/notifications.generated.go @@ -59,20 +59,20 @@ type SessionRemovedParams struct { // // Semantics: // -// - Only fields present in `changes` have new values; omitted fields are -// unchanged on the client's cached summary. -// - Identity fields (`resource`, `provider`, `createdAt`) never change and -// are not carried. -// - Like all protocol notifications, this is ephemeral: it is **not** -// replayed on reconnect. On reconnect, clients should re-fetch the full -// catalog via `listSessions()` as usual. -// - The server SHOULD emit this notification whenever any mutable field on -// {@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) at their discretion. -// - Clients that have no cached entry for `session` MAY ignore the -// notification; it is not a substitute for `root/sessionAdded`. +// - Only fields present in `changes` have new values; omitted fields are +// unchanged on the client's cached summary. +// - Identity fields (`resource`, `provider`, `createdAt`) never change and +// are not carried. +// - Like all protocol notifications, this is ephemeral: it is **not** +// replayed on reconnect. On reconnect, clients should re-fetch the full +// catalog via `listSessions()` as usual. +// - The server SHOULD emit this notification whenever any mutable field on +// {@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) 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 { // Channel URI this notification belongs to (the root channel) Channel URI `json:"channel"` diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 186c50d4..f82c9383 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -19,8 +19,8 @@ var _ = json.RawMessage(nil) type PolicyState string const ( - PolicyStateEnabled PolicyState = "enabled" - PolicyStateDisabled PolicyState = "disabled" + PolicyStateEnabled PolicyState = "enabled" + PolicyStateDisabled PolicyState = "disabled" PolicyStateUnconfigured PolicyState = "unconfigured" ) @@ -38,8 +38,8 @@ const ( type SessionLifecycle string const ( - SessionLifecycleCreating SessionLifecycle = "creating" - SessionLifecycleReady SessionLifecycle = "ready" + SessionLifecycleCreating SessionLifecycle = "creating" + SessionLifecycleReady SessionLifecycle = "ready" SessionLifecycleCreationFailed SessionLifecycle = "creationFailed" ) @@ -75,19 +75,19 @@ func (s SessionStatus) Or(other SessionStatus) SessionStatus { return s | other type SessionInputAnswerState string const ( - SessionInputAnswerStateDraft SessionInputAnswerState = "draft" + SessionInputAnswerStateDraft SessionInputAnswerState = "draft" SessionInputAnswerStateSubmitted SessionInputAnswerState = "submitted" - SessionInputAnswerStateSkipped SessionInputAnswerState = "skipped" + SessionInputAnswerStateSkipped SessionInputAnswerState = "skipped" ) // Answer value kind. type SessionInputAnswerValueKind string const ( - SessionInputAnswerValueKindText SessionInputAnswerValueKind = "text" - SessionInputAnswerValueKindNumber SessionInputAnswerValueKind = "number" - SessionInputAnswerValueKindBoolean SessionInputAnswerValueKind = "boolean" - SessionInputAnswerValueKindSelected SessionInputAnswerValueKind = "selected" + SessionInputAnswerValueKindText SessionInputAnswerValueKind = "text" + SessionInputAnswerValueKindNumber SessionInputAnswerValueKind = "number" + SessionInputAnswerValueKindBoolean SessionInputAnswerValueKind = "boolean" + SessionInputAnswerValueKindSelected SessionInputAnswerValueKind = "selected" SessionInputAnswerValueKindSelectedMany SessionInputAnswerValueKind = "selected-many" ) @@ -95,30 +95,30 @@ const ( type SessionInputQuestionKind string const ( - SessionInputQuestionKindText SessionInputQuestionKind = "text" - SessionInputQuestionKindNumber SessionInputQuestionKind = "number" - SessionInputQuestionKindInteger SessionInputQuestionKind = "integer" - SessionInputQuestionKindBoolean SessionInputQuestionKind = "boolean" + SessionInputQuestionKindText SessionInputQuestionKind = "text" + SessionInputQuestionKindNumber SessionInputQuestionKind = "number" + SessionInputQuestionKindInteger SessionInputQuestionKind = "integer" + SessionInputQuestionKindBoolean SessionInputQuestionKind = "boolean" SessionInputQuestionKindSingleSelect SessionInputQuestionKind = "single-select" - SessionInputQuestionKindMultiSelect SessionInputQuestionKind = "multi-select" + SessionInputQuestionKindMultiSelect SessionInputQuestionKind = "multi-select" ) // How a client completed an input request. type SessionInputResponseKind string const ( - SessionInputResponseKindAccept SessionInputResponseKind = "accept" + SessionInputResponseKindAccept SessionInputResponseKind = "accept" SessionInputResponseKindDecline SessionInputResponseKind = "decline" - SessionInputResponseKindCancel SessionInputResponseKind = "cancel" + SessionInputResponseKindCancel SessionInputResponseKind = "cancel" ) // How a turn ended. type TurnState string const ( - TurnStateComplete TurnState = "complete" + TurnStateComplete TurnState = "complete" TurnStateCancelled TurnState = "cancelled" - TurnStateError TurnState = "error" + TurnStateError TurnState = "error" ) // Discriminant for {@link MessageAttachment} variants. @@ -137,10 +137,10 @@ const ( type ResponsePartKind string const ( - ResponsePartKindMarkdown ResponsePartKind = "markdown" - ResponsePartKindContentRef ResponsePartKind = "contentRef" - ResponsePartKindToolCall ResponsePartKind = "toolCall" - ResponsePartKindReasoning ResponsePartKind = "reasoning" + ResponsePartKindMarkdown ResponsePartKind = "markdown" + ResponsePartKindContentRef ResponsePartKind = "contentRef" + ResponsePartKindToolCall ResponsePartKind = "toolCall" + ResponsePartKindReasoning ResponsePartKind = "reasoning" ResponsePartKindSystemNotification ResponsePartKind = "systemNotification" ) @@ -148,12 +148,12 @@ const ( type ToolCallStatus string const ( - ToolCallStatusStreaming ToolCallStatus = "streaming" - ToolCallStatusPendingConfirmation ToolCallStatus = "pending-confirmation" - ToolCallStatusRunning ToolCallStatus = "running" + ToolCallStatusStreaming ToolCallStatus = "streaming" + ToolCallStatusPendingConfirmation ToolCallStatus = "pending-confirmation" + ToolCallStatusRunning ToolCallStatus = "running" ToolCallStatusPendingResultConfirmation ToolCallStatus = "pending-result-confirmation" - ToolCallStatusCompleted ToolCallStatus = "completed" - ToolCallStatusCancelled ToolCallStatus = "cancelled" + ToolCallStatusCompleted ToolCallStatus = "completed" + ToolCallStatusCancelled ToolCallStatus = "cancelled" ) // How a tool call was confirmed for execution. @@ -164,17 +164,17 @@ const ( type ToolCallConfirmationReason string const ( - ToolCallConfirmationReasonNotNeeded ToolCallConfirmationReason = "not-needed" + ToolCallConfirmationReasonNotNeeded ToolCallConfirmationReason = "not-needed" ToolCallConfirmationReasonUserAction ToolCallConfirmationReason = "user-action" - ToolCallConfirmationReasonSetting ToolCallConfirmationReason = "setting" + ToolCallConfirmationReasonSetting ToolCallConfirmationReason = "setting" ) // Why a tool call was cancelled. type ToolCallCancellationReason string const ( - ToolCallCancellationReasonDenied ToolCallCancellationReason = "denied" - ToolCallCancellationReasonSkipped ToolCallCancellationReason = "skipped" + ToolCallCancellationReasonDenied ToolCallCancellationReason = "denied" + ToolCallCancellationReasonSkipped ToolCallCancellationReason = "skipped" ToolCallCancellationReasonResultDenied ToolCallCancellationReason = "result-denied" ) @@ -183,26 +183,26 @@ type ConfirmationOptionKind string const ( ConfirmationOptionKindApprove ConfirmationOptionKind = "approve" - ConfirmationOptionKindDeny ConfirmationOptionKind = "deny" + ConfirmationOptionKindDeny ConfirmationOptionKind = "deny" ) type ToolCallContributorKind string const ( ToolCallContributorKindClient ToolCallContributorKind = "client" - ToolCallContributorKindMCP ToolCallContributorKind = "mcp" + ToolCallContributorKindMCP ToolCallContributorKind = "mcp" ) // Discriminant for tool result content types. type ToolResultContentType string const ( - ToolResultContentTypeText ToolResultContentType = "text" + ToolResultContentTypeText ToolResultContentType = "text" ToolResultContentTypeEmbeddedResource ToolResultContentType = "embeddedResource" - ToolResultContentTypeResource ToolResultContentType = "resource" - ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" - ToolResultContentTypeTerminal ToolResultContentType = "terminal" - ToolResultContentTypeSubagent ToolResultContentType = "subagent" + ToolResultContentTypeResource ToolResultContentType = "resource" + ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" + ToolResultContentTypeTerminal ToolResultContentType = "terminal" + ToolResultContentTypeSubagent ToolResultContentType = "subagent" ) // Discriminant for the kind of customization. @@ -217,13 +217,13 @@ const ( type CustomizationType string const ( - CustomizationTypePlugin CustomizationType = "plugin" + CustomizationTypePlugin CustomizationType = "plugin" CustomizationTypeDirectory CustomizationType = "directory" - CustomizationTypeAgent CustomizationType = "agent" - CustomizationTypeSkill CustomizationType = "skill" - CustomizationTypePrompt CustomizationType = "prompt" - CustomizationTypeRule CustomizationType = "rule" - CustomizationTypeHook CustomizationType = "hook" + CustomizationTypeAgent CustomizationType = "agent" + CustomizationTypeSkill CustomizationType = "skill" + CustomizationTypePrompt CustomizationType = "prompt" + CustomizationTypeRule CustomizationType = "rule" + CustomizationTypeHook CustomizationType = "hook" CustomizationTypeMcpServer CustomizationType = "mcpServer" ) @@ -231,17 +231,17 @@ const ( type CustomizationLoadStatus string const ( - CustomizationLoadStatusLoading CustomizationLoadStatus = "loading" - CustomizationLoadStatusLoaded CustomizationLoadStatus = "loaded" + CustomizationLoadStatusLoading CustomizationLoadStatus = "loading" + CustomizationLoadStatusLoaded CustomizationLoadStatus = "loaded" CustomizationLoadStatusDegraded CustomizationLoadStatus = "degraded" - CustomizationLoadStatusError CustomizationLoadStatus = "error" + CustomizationLoadStatusError CustomizationLoadStatus = "error" ) // Discriminant for terminal claim kinds. type TerminalClaimKind string const ( - TerminalClaimKindClient TerminalClaimKind = "client" + TerminalClaimKindClient TerminalClaimKind = "client" TerminalClaimKindSession TerminalClaimKind = "session" ) @@ -342,7 +342,7 @@ const ( type ResourceChangeType string const ( - ResourceChangeTypeAdded ResourceChangeType = "added" + ResourceChangeTypeAdded ResourceChangeType = "added" ResourceChangeTypeUpdated ResourceChangeType = "updated" ResourceChangeTypeDeleted ResourceChangeType = "deleted" ) @@ -842,30 +842,30 @@ type SessionInputOption struct { // Value captured for one answer. type SessionInputTextAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` } type SessionInputNumberAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value float64 `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value float64 `json:"value"` } type SessionInputBooleanAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value bool `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value bool `json:"value"` } type SessionInputSelectedAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` // Free-form text entered instead of selecting an option FreeformValues []string `json:"freeformValues,omitempty"` } type SessionInputSelectedManyAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value []string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value []string `json:"value"` // Free-form text entered in addition to selected options FreeformValues []string `json:"freeformValues,omitempty"` } @@ -893,8 +893,8 @@ type SessionInputTextQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Format hint for text questions, such as `email`, `uri`, `date`, or `date-time` Format *string `json:"format,omitempty"` // Minimum string length @@ -914,8 +914,8 @@ type SessionInputNumberQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Minimum value Min *float64 `json:"min,omitempty"` // Maximum value @@ -933,8 +933,8 @@ type SessionInputBooleanQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Default boolean value DefaultValue *bool `json:"defaultValue,omitempty"` } @@ -948,8 +948,8 @@ type SessionInputSingleSelectQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Options the user may select from Options []SessionInputOption `json:"options"` // Whether the user may enter text instead of selecting an option @@ -965,8 +965,8 @@ type SessionInputMultiSelectQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Options the user may select from Options []SessionInputOption `json:"options"` // Whether the user may enter text in addition to selecting options @@ -1258,8 +1258,8 @@ type ToolCallStreamingState struct { // This MAY include a `ui` field corresponding to the MCP Apps (SEP-1865) // `McpUiToolMeta` found in MCP tool calls, which may be used in combination // with the {@link contributor} to serve MCP Apps. - Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Status ToolCallStatus `json:"status"` + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Status ToolCallStatus `json:"status"` // Partial parameters accumulated so far PartialInput *string `json:"partialInput,omitempty"` // Progress message shown while parameters are streaming @@ -1286,8 +1286,8 @@ type ToolCallPendingConfirmationState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // Short title for the confirmation prompt (e.g. `"Run in terminal"`, `"Write file"`) ConfirmationTitle *StringOrMarkdown `json:"confirmationTitle,omitempty"` // File edits that this tool call will perform, for preview before confirmation @@ -1320,8 +1320,8 @@ type ToolCallRunningState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1366,8 +1366,8 @@ type ToolCallPendingResultConfirmationState struct { // This mirrors the `structuredContent` field of MCP `CallToolResult`. StructuredContent map[string]json.RawMessage `json:"structuredContent,omitempty"` // Error details if the tool failed - Error *json.RawMessage `json:"error,omitempty"` - Status ToolCallStatus `json:"status"` + Error *json.RawMessage `json:"error,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1407,8 +1407,8 @@ type ToolCallCompletedState struct { // This mirrors the `structuredContent` field of MCP `CallToolResult`. StructuredContent map[string]json.RawMessage `json:"structuredContent,omitempty"` // Error details if the tool failed - Error *json.RawMessage `json:"error,omitempty"` - Status ToolCallStatus `json:"status"` + Error *json.RawMessage `json:"error,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1434,8 +1434,8 @@ type ToolCallCancelledState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // Why the tool was cancelled Reason ToolCallCancellationReason `json:"reason"` // Optional message explaining the cancellation @@ -1517,8 +1517,8 @@ type ToolResultResourceContent struct { // Approximate size in bytes SizeHint *int64 `json:"sizeHint,omitempty"` // Content MIME type - ContentType *string `json:"contentType,omitempty"` - Type ToolResultContentType `json:"type"` + ContentType *string `json:"contentType,omitempty"` + Type ToolResultContentType `json:"type"` } // Describes a file modification performed by a tool. @@ -1528,7 +1528,7 @@ type ToolResultFileEditContent struct { // The file state after the edit. Absent for file deletions. After *json.RawMessage `json:"after,omitempty"` // Optional diff display metadata - Diff *json.RawMessage `json:"diff,omitempty"` + Diff *json.RawMessage `json:"diff,omitempty"` Type ToolResultContentType `json:"type"` } @@ -1621,7 +1621,7 @@ type PluginCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` } // A {@link PluginCustomization} as published by a client. Extends the @@ -1669,7 +1669,7 @@ type ClientPluginCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` // Opaque version token used by the host to detect changes. Nonce *string `json:"nonce,omitempty"` } @@ -1719,7 +1719,7 @@ type DirectoryCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` // Which child customization type this directory holds. Contents CustomizationType `json:"contents"` // Whether clients may write into this directory. @@ -1752,8 +1752,8 @@ type AgentCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description of what the agent specializes in and when to // invoke it. Sourced from the agent file's frontmatter `description`. Description *string `json:"description,omitempty"` @@ -1790,8 +1790,8 @@ type SkillCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description used for help text and auto-invocation matching. // Sourced from the skill's frontmatter `description`. Description *string `json:"description,omitempty"` @@ -1823,8 +1823,8 @@ type PromptCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description of what the prompt does. Description *string `json:"description,omitempty"` } @@ -1859,8 +1859,8 @@ type RuleCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Description of what the rule enforces. Description *string `json:"description,omitempty"` // When `true`, the rule is always active (subject to `globs` if any). @@ -1894,8 +1894,8 @@ type HookCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` } // An MCP server contributed by a plugin or directory. @@ -1927,8 +1927,8 @@ type McpServerCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Whether this MCP server is currently enabled. Enabled bool `json:"enabled"` // Current lifecycle state of the MCP server. @@ -1985,20 +1985,20 @@ type McpServerCustomizationApps struct { // An agent host MUST only advertise a capability when it actually accepts the // corresponding methods/notifications on the `mcp://` channel: // -// - {@link serverTools}: host proxies `tools/list` and `tools/call` to -// the MCP server. When `listChanged` is `true`, the host also forwards -// `notifications/tools/list_changed`. -// - {@link serverResources}: host proxies `resources/read`, -// `resources/list`, and `resources/templates/list` to the MCP server. -// When `listChanged` is `true`, the host also forwards -// `notifications/resources/list_changed`. -// - {@link logging}: host accepts `notifications/message` log entries -// from the App and forwards them via `mcpNotification` (and forwards -// `logging/setLevel` calls to the server). -// - {@link sampling}: host serves `sampling/createMessage` via -// `mcpMethodCall`. When `sampling.tools` is present, the host also -// accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks -// inside `CreateMessageRequest`. +// - {@link serverTools}: host proxies `tools/list` and `tools/call` to +// the MCP server. When `listChanged` is `true`, the host also forwards +// `notifications/tools/list_changed`. +// - {@link serverResources}: host proxies `resources/read`, +// `resources/list`, and `resources/templates/list` to the MCP server. +// When `listChanged` is `true`, the host also forwards +// `notifications/resources/list_changed`. +// - {@link logging}: host accepts `notifications/message` log entries +// from the App and forwards them via `mcpNotification` (and forwards +// `logging/setLevel` calls to the server). +// - {@link sampling}: host serves `sampling/createMessage` via +// `mcpMethodCall`. When `sampling.tools` is present, the host also +// accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks +// inside `CreateMessageRequest`. type AhpMcpUiHostCapabilities struct { // Producer proxies the MCP `tools/*` methods to the upstream server. ServerTools *json.RawMessage `json:"serverTools,omitempty"` @@ -2521,10 +2521,10 @@ type ResponsePart struct { // concrete variant of ResponsePart. type isResponsePart interface{ isResponsePart() } -func (*MarkdownResponsePart) isResponsePart() {} -func (*ResourceResponsePart) isResponsePart() {} -func (*ToolCallResponsePart) isResponsePart() {} -func (*ReasoningResponsePart) isResponsePart() {} +func (*MarkdownResponsePart) isResponsePart() {} +func (*ResourceResponsePart) isResponsePart() {} +func (*ToolCallResponsePart) isResponsePart() {} +func (*ReasoningResponsePart) isResponsePart() {} func (*SystemNotificationResponsePart) isResponsePart() {} // ResponsePartUnknown carries an unrecognized ResponsePart variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2602,12 +2602,12 @@ type ToolCallState struct { // concrete variant of ToolCallState. type isToolCallState interface{ isToolCallState() } -func (*ToolCallStreamingState) isToolCallState() {} -func (*ToolCallPendingConfirmationState) isToolCallState() {} -func (*ToolCallRunningState) isToolCallState() {} +func (*ToolCallStreamingState) isToolCallState() {} +func (*ToolCallPendingConfirmationState) isToolCallState() {} +func (*ToolCallRunningState) isToolCallState() {} func (*ToolCallPendingResultConfirmationState) isToolCallState() {} -func (*ToolCallCompletedState) isToolCallState() {} -func (*ToolCallCancelledState) isToolCallState() {} +func (*ToolCallCompletedState) isToolCallState() {} +func (*ToolCallCancelledState) isToolCallState() {} // ToolCallStateUnknown carries an unrecognized ToolCallState variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type ToolCallStateUnknown struct { @@ -2690,7 +2690,7 @@ type TerminalClaim struct { // concrete variant of TerminalClaim. type isTerminalClaim interface{ isTerminalClaim() } -func (*TerminalClientClaim) isTerminalClaim() {} +func (*TerminalClientClaim) isTerminalClaim() {} func (*TerminalSessionClaim) isTerminalClaim() {} // TerminalClaimUnknown carries an unrecognized TerminalClaim variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2751,7 +2751,7 @@ type TerminalContentPart struct { type isTerminalContentPart interface{ isTerminalContentPart() } func (*TerminalUnclassifiedPart) isTerminalContentPart() {} -func (*TerminalCommandPart) isTerminalContentPart() {} +func (*TerminalCommandPart) isTerminalContentPart() {} // TerminalContentPartUnknown carries an unrecognized TerminalContentPart variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type TerminalContentPartUnknown struct { @@ -2810,11 +2810,11 @@ type SessionInputQuestion struct { // concrete variant of SessionInputQuestion. type isSessionInputQuestion interface{ isSessionInputQuestion() } -func (*SessionInputTextQuestion) isSessionInputQuestion() {} -func (*SessionInputNumberQuestion) isSessionInputQuestion() {} -func (*SessionInputBooleanQuestion) isSessionInputQuestion() {} +func (*SessionInputTextQuestion) isSessionInputQuestion() {} +func (*SessionInputNumberQuestion) isSessionInputQuestion() {} +func (*SessionInputBooleanQuestion) isSessionInputQuestion() {} func (*SessionInputSingleSelectQuestion) isSessionInputQuestion() {} -func (*SessionInputMultiSelectQuestion) isSessionInputQuestion() {} +func (*SessionInputMultiSelectQuestion) isSessionInputQuestion() {} // SessionInputQuestionUnknown carries an unrecognized SessionInputQuestion variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type SessionInputQuestionUnknown struct { @@ -2897,10 +2897,10 @@ type SessionInputAnswerValue struct { // concrete variant of SessionInputAnswerValue. type isSessionInputAnswerValue interface{ isSessionInputAnswerValue() } -func (*SessionInputTextAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputNumberAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputBooleanAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputSelectedAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputTextAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputNumberAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputBooleanAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputSelectedAnswerValue) isSessionInputAnswerValue() {} func (*SessionInputSelectedManyAnswerValue) isSessionInputAnswerValue() {} // SessionInputAnswerValueUnknown carries an unrecognized SessionInputAnswerValue variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2979,7 +2979,7 @@ type SessionInputAnswer struct { type isSessionInputAnswer interface{ isSessionInputAnswer() } func (*SessionInputAnswered) isSessionInputAnswer() {} -func (*SessionInputSkipped) isSessionInputAnswer() {} +func (*SessionInputSkipped) isSessionInputAnswer() {} // SessionInputAnswerUnknown carries an unrecognized SessionInputAnswer variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type SessionInputAnswerUnknown struct { @@ -3044,12 +3044,12 @@ type ToolResultContent struct { // concrete variant of ToolResultContent. type isToolResultContent interface{ isToolResultContent() } -func (*ToolResultTextContent) isToolResultContent() {} +func (*ToolResultTextContent) isToolResultContent() {} func (*ToolResultEmbeddedResourceContent) isToolResultContent() {} -func (*ToolResultResourceContent) isToolResultContent() {} -func (*ToolResultFileEditContent) isToolResultContent() {} -func (*ToolResultTerminalContent) isToolResultContent() {} -func (*ToolResultSubagentContent) isToolResultContent() {} +func (*ToolResultResourceContent) isToolResultContent() {} +func (*ToolResultFileEditContent) isToolResultContent() {} +func (*ToolResultTerminalContent) isToolResultContent() {} +func (*ToolResultSubagentContent) isToolResultContent() {} // ToolResultContentUnknown carries an unrecognized ToolResultContent variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type ToolResultContentUnknown struct { @@ -3132,9 +3132,9 @@ type MessageAttachment struct { // concrete variant of MessageAttachment. type isMessageAttachment interface{ isMessageAttachment() } -func (*SimpleMessageAttachment) isMessageAttachment() {} +func (*SimpleMessageAttachment) isMessageAttachment() {} func (*MessageEmbeddedResourceAttachment) isMessageAttachment() {} -func (*MessageResourceAttachment) isMessageAttachment() {} +func (*MessageResourceAttachment) isMessageAttachment() {} // MessageAttachmentUnknown carries an unrecognized MessageAttachment variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type MessageAttachmentUnknown struct { @@ -3199,7 +3199,7 @@ type Customization struct { // concrete variant of Customization. type isCustomization interface{ isCustomization() } -func (*PluginCustomization) isCustomization() {} +func (*PluginCustomization) isCustomization() {} func (*DirectoryCustomization) isCustomization() {} func (*McpServerCustomization) isCustomization() {} @@ -3266,11 +3266,11 @@ type ChildCustomization struct { // concrete variant of ChildCustomization. type isChildCustomization interface{ isChildCustomization() } -func (*AgentCustomization) isChildCustomization() {} -func (*SkillCustomization) isChildCustomization() {} -func (*PromptCustomization) isChildCustomization() {} -func (*RuleCustomization) isChildCustomization() {} -func (*HookCustomization) isChildCustomization() {} +func (*AgentCustomization) isChildCustomization() {} +func (*SkillCustomization) isChildCustomization() {} +func (*PromptCustomization) isChildCustomization() {} +func (*RuleCustomization) isChildCustomization() {} +func (*HookCustomization) isChildCustomization() {} func (*McpServerCustomization) isChildCustomization() {} // ChildCustomizationUnknown carries an unrecognized ChildCustomization variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -3354,10 +3354,10 @@ type CustomizationLoadState struct { // concrete variant of CustomizationLoadState. type isCustomizationLoadState interface{ isCustomizationLoadState() } -func (*CustomizationLoadingState) isCustomizationLoadState() {} -func (*CustomizationLoadedState) isCustomizationLoadState() {} +func (*CustomizationLoadingState) isCustomizationLoadState() {} +func (*CustomizationLoadedState) isCustomizationLoadState() {} func (*CustomizationDegradedState) isCustomizationLoadState() {} -func (*CustomizationErrorState) isCustomizationLoadState() {} +func (*CustomizationErrorState) isCustomizationLoadState() {} // CustomizationLoadStateUnknown carries an unrecognized CustomizationLoadState variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type CustomizationLoadStateUnknown struct { @@ -3428,11 +3428,11 @@ type McpServerState struct { // concrete variant of McpServerState. type isMcpServerState interface{ isMcpServerState() } -func (*McpServerStartingState) isMcpServerState() {} -func (*McpServerReadyState) isMcpServerState() {} +func (*McpServerStartingState) isMcpServerState() {} +func (*McpServerReadyState) isMcpServerState() {} func (*McpServerAuthRequiredState) isMcpServerState() {} -func (*McpServerErrorState) isMcpServerState() {} -func (*McpServerStoppedState) isMcpServerState() {} +func (*McpServerErrorState) isMcpServerState() {} +func (*McpServerStoppedState) isMcpServerState() {} // McpServerStateUnknown carries an unrecognized McpServerState variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type McpServerStateUnknown struct { @@ -3510,7 +3510,7 @@ type ToolCallContributor struct { type isToolCallContributor interface{ isToolCallContributor() } func (*ToolCallClientContributor) isToolCallContributor() {} -func (*ToolCallMcpContributor) isToolCallContributor() {} +func (*ToolCallMcpContributor) isToolCallContributor() {} // ToolCallContributorUnknown carries an unrecognized ToolCallContributor variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type ToolCallContributorUnknown struct { 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 1c015cf8..6ac20937 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt @@ -21,6 +21,9 @@ import com.microsoft.agenthostprotocol.generated.ChildCustomizationPrompt import com.microsoft.agenthostprotocol.generated.ChildCustomizationRule import com.microsoft.agenthostprotocol.generated.ChildCustomizationSkill import com.microsoft.agenthostprotocol.generated.ChildCustomizationUnknown +import com.microsoft.agenthostprotocol.generated.Comment +import com.microsoft.agenthostprotocol.generated.CommentThread +import com.microsoft.agenthostprotocol.generated.CommentsState import com.microsoft.agenthostprotocol.generated.ConfirmationOption import com.microsoft.agenthostprotocol.generated.Customization import com.microsoft.agenthostprotocol.generated.CustomizationDirectory @@ -52,6 +55,11 @@ import com.microsoft.agenthostprotocol.generated.StateActionChangesetFileSet import com.microsoft.agenthostprotocol.generated.StateActionChangesetOperationsChanged import com.microsoft.agenthostprotocol.generated.StateActionChangesetOperationStatusChanged import com.microsoft.agenthostprotocol.generated.StateActionChangesetStatusChanged +import com.microsoft.agenthostprotocol.generated.StateActionCommentsCleared +import com.microsoft.agenthostprotocol.generated.StateActionCommentsCommentRemoved +import com.microsoft.agenthostprotocol.generated.StateActionCommentsCommentSet +import com.microsoft.agenthostprotocol.generated.StateActionCommentsThreadRemoved +import com.microsoft.agenthostprotocol.generated.StateActionCommentsThreadSet import com.microsoft.agenthostprotocol.generated.StateActionRootActiveSessionsChanged import com.microsoft.agenthostprotocol.generated.StateActionRootAgentsChanged import com.microsoft.agenthostprotocol.generated.StateActionRootConfigChanged diff --git a/clients/rust/crates/ahp/src/multi_host_state_mirror.rs b/clients/rust/crates/ahp/src/multi_host_state_mirror.rs index 89560091..09adc03c 100644 --- a/clients/rust/crates/ahp/src/multi_host_state_mirror.rs +++ b/clients/rust/crates/ahp/src/multi_host_state_mirror.rs @@ -36,7 +36,9 @@ use std::collections::HashMap; use ahp_types::actions::ActionEnvelope; use ahp_types::common::ROOT_RESOURCE_URI; -use ahp_types::state::{ChangesetState, CommentsState, RootState, SessionState, SnapshotState, TerminalState}; +use ahp_types::state::{ + ChangesetState, CommentsState, RootState, SessionState, SnapshotState, TerminalState, +}; use crate::hosts::{HostId, HostSubscriptionEvent}; use crate::reducers::{apply_action_to_root, apply_action_to_session, apply_action_to_terminal}; diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift index 7b263f65..569d3f10 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift @@ -15,6 +15,7 @@ public actor AHPStateMirror { public private(set) var sessions: [String: SessionState] = [:] public private(set) var terminals: [String: TerminalState] = [:] public private(set) var changesets: [String: ChangesetState] = [:] + public private(set) var comments: [String: CommentsState] = [:] public init() {} @@ -48,10 +49,16 @@ public actor AHPStateMirror { // mutated only when fresh snapshots arrive. return } + if comments[channel] != nil { + // Comments are also seeded by `applySnapshot` and currently + // mutated only when fresh snapshots arrive. + return + } } - /// Seed the mirror from a `Snapshot` — root, session, or terminal as - /// the snapshot's `state` discriminator dictates. + /// Seed the mirror from a `Snapshot` — root, session, terminal, + /// changeset, or comments as the snapshot's `state` discriminator + /// dictates. public func applySnapshot(_ snapshot: Snapshot) { switch snapshot.state { case .root(let state): @@ -62,6 +69,8 @@ public actor AHPStateMirror { terminals[snapshot.resource] = state case .changeset(let state): changesets[snapshot.resource] = state + case .comments(let state): + comments[snapshot.resource] = state } } @@ -71,5 +80,6 @@ public actor AHPStateMirror { sessions.removeAll() terminals.removeAll() changesets.removeAll() + comments.removeAll() } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift index 1521fa86..b7f44657 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift @@ -47,6 +47,7 @@ public actor MultiHostStateMirror { public private(set) var sessions: [HostedResourceKey: SessionState] = [:] public private(set) var terminals: [HostedResourceKey: TerminalState] = [:] public private(set) var changesets: [HostedResourceKey: ChangesetState] = [:] + public private(set) var comments: [HostedResourceKey: CommentsState] = [:] public init() {} @@ -87,13 +88,18 @@ public actor MultiHostStateMirror { // mutated only when fresh snapshots arrive. return } + if comments[key] != nil { + // Comments are also seeded by `applySnapshot` and currently + // mutated only when fresh snapshots arrive. + return + } // No state for this `(host, channel)` yet — the reducer can't // initialise one; only `applySnapshot(host:snapshot:)` can. } /// Seed the mirror from a `Snapshot` scoped to `host` — root, - /// session, terminal, or changeset as the snapshot's `state` - /// discriminator dictates. + /// session, terminal, changeset, or comments as the snapshot's + /// `state` discriminator dictates. public func applySnapshot(host: HostId, snapshot: Snapshot) { let key = HostedResourceKey(hostId: host, uri: snapshot.resource) switch snapshot.state { @@ -105,17 +111,21 @@ public actor MultiHostStateMirror { terminals[key] = state case .changeset(let state): changesets[key] = state + case .comments(let state): + comments[key] = state } } /// Reset every slot for `host` — drops the root state, all sessions - /// keyed under that host, all terminals keyed under that host, and - /// all changesets keyed under that host. + /// keyed under that host, all terminals keyed under that host, all + /// changesets keyed under that host, and all comments keyed under + /// that host. public func reset(host: HostId) { rootStates.removeValue(forKey: host) sessions = sessions.filter { $0.key.hostId != host } terminals = terminals.filter { $0.key.hostId != host } changesets = changesets.filter { $0.key.hostId != host } + comments = comments.filter { $0.key.hostId != host } } /// Reset every host's state. @@ -124,5 +134,6 @@ public actor MultiHostStateMirror { sessions.removeAll() terminals.removeAll() changesets.removeAll() + comments.removeAll() } } From 99625b9df7414bee1bfa2401bc0e464feb202cb5 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 5 Jun 2026 11:36:44 +0200 Subject: [PATCH 06/11] fix test --- .../com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt index e8d19632..1078cac1 100644 --- a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt +++ b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt @@ -1,6 +1,7 @@ package com.microsoft.agenthostprotocol import com.microsoft.agenthostprotocol.generated.ChangesetState +import com.microsoft.agenthostprotocol.generated.CommentsState import com.microsoft.agenthostprotocol.generated.ResourceWatchState import com.microsoft.agenthostprotocol.generated.RootState import com.microsoft.agenthostprotocol.generated.SessionState From 3aefad1968216b2e23e3e48cb4064a5c869ec357 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 8 Jun 2026 11:02:57 +0200 Subject: [PATCH 07/11] resolved state and optional range --- CHANGELOG.md | 7 +- clients/go/ahptypes/actions.generated.go | 336 +++++++++--------- clients/go/ahptypes/commands.generated.go | 82 +++-- clients/go/ahptypes/errors.generated.go | 20 +- .../go/ahptypes/notifications.generated.go | 28 +- clients/go/ahptypes/state.generated.go | 326 ++++++++--------- .../generated/Commands.generated.kt | 11 +- .../generated/State.generated.kt | 11 +- clients/rust/crates/ahp-types/src/commands.rs | 26 +- clients/rust/crates/ahp-types/src/state.rs | 17 +- .../Generated/Commands.generated.swift | 13 +- .../Generated/State.generated.swift | 14 +- schema/actions.schema.json | 10 +- schema/commands.schema.json | 21 +- schema/errors.schema.json | 21 +- schema/notifications.schema.json | 10 +- schema/state.schema.json | 10 +- types/channels-comments/commands.ts | 26 +- types/channels-comments/state.ts | 20 +- ...comments-threadset-appends-new-thread.json | 12 +- ...ts-threadset-replaces-existing-thread.json | 13 +- ...s-threadremoved-drops-matching-thread.json | 3 + ...ments-commentset-appends-and-replaces.json | 2 + ...ts-commentset-unknown-thread-is-no-op.json | 2 + ...commentremoved-drops-matching-comment.json | 2 + .../216-comments-cleared-empties-threads.json | 1 + ...comments-unknown-action-type-is-no-op.json | 2 + 27 files changed, 574 insertions(+), 472 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28929dc5..1e19b9cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,8 +76,11 @@ Spec version: `0.3.0` carrying optional `additions`, `deletions`, and `files` counts so servers can advertise an at-a-glance view of a session's file-change footprint. - Added a new comments channel exposed on `ahp-session://comments`. - Threads anchor to a `(turnId, resource, range)` triple and always carry - at least one comment; new `createCommentThread`, `updateCommentThread`, + Threads anchor to a `(turnId, resource)` pair with an optional `range` + (omitted to anchor to the entire file), carry a `resolved` flag (newly + created threads start unresolved; clients flip it via + `updateCommentThread`), and always carry at least one comment; new + `createCommentThread`, `updateCommentThread`, `deleteCommentThread`, `addComment`, `editComment`, `deleteComment` commands drive mutations and echo as `comments/threadSet`, `comments/threadRemoved`, `comments/commentSet`, `comments/commentRemoved`, diff --git a/clients/go/ahptypes/actions.generated.go b/clients/go/ahptypes/actions.generated.go index 4906a63e..218e1526 100644 --- a/clients/go/ahptypes/actions.generated.go +++ b/clients/go/ahptypes/actions.generated.go @@ -19,82 +19,82 @@ var _ = json.RawMessage(nil) type ActionType string const ( - ActionTypeRootAgentsChanged ActionType = "root/agentsChanged" - ActionTypeRootActiveSessionsChanged ActionType = "root/activeSessionsChanged" - ActionTypeSessionReady ActionType = "session/ready" - ActionTypeSessionCreationFailed ActionType = "session/creationFailed" - ActionTypeSessionTurnStarted ActionType = "session/turnStarted" - ActionTypeSessionDelta ActionType = "session/delta" - ActionTypeSessionResponsePart ActionType = "session/responsePart" - ActionTypeSessionToolCallStart ActionType = "session/toolCallStart" - ActionTypeSessionToolCallDelta ActionType = "session/toolCallDelta" - ActionTypeSessionToolCallReady ActionType = "session/toolCallReady" - ActionTypeSessionToolCallConfirmed ActionType = "session/toolCallConfirmed" - ActionTypeSessionToolCallComplete ActionType = "session/toolCallComplete" - ActionTypeSessionToolCallResultConfirmed ActionType = "session/toolCallResultConfirmed" - ActionTypeSessionToolCallContentChanged ActionType = "session/toolCallContentChanged" - ActionTypeSessionTurnComplete ActionType = "session/turnComplete" - ActionTypeSessionTurnCancelled ActionType = "session/turnCancelled" - ActionTypeSessionError ActionType = "session/error" - ActionTypeSessionTitleChanged ActionType = "session/titleChanged" - ActionTypeSessionUsage ActionType = "session/usage" - ActionTypeSessionReasoning ActionType = "session/reasoning" - ActionTypeSessionModelChanged ActionType = "session/modelChanged" - ActionTypeSessionAgentChanged ActionType = "session/agentChanged" - ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged" - ActionTypeSessionActiveClientChanged ActionType = "session/activeClientChanged" - ActionTypeSessionActiveClientToolsChanged ActionType = "session/activeClientToolsChanged" - ActionTypeSessionPendingMessageSet ActionType = "session/pendingMessageSet" - ActionTypeSessionPendingMessageRemoved ActionType = "session/pendingMessageRemoved" - ActionTypeSessionQueuedMessagesReordered ActionType = "session/queuedMessagesReordered" - ActionTypeSessionInputRequested ActionType = "session/inputRequested" - ActionTypeSessionInputAnswerChanged ActionType = "session/inputAnswerChanged" - ActionTypeSessionInputCompleted ActionType = "session/inputCompleted" - ActionTypeSessionCustomizationsChanged ActionType = "session/customizationsChanged" - ActionTypeSessionCustomizationToggled ActionType = "session/customizationToggled" - ActionTypeSessionCustomizationUpdated ActionType = "session/customizationUpdated" - ActionTypeSessionCustomizationRemoved ActionType = "session/customizationRemoved" - ActionTypeSessionMcpServerStateChanged ActionType = "session/mcpServerStateChanged" - ActionTypeSessionTruncated ActionType = "session/truncated" - 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" - ActionTypeChangesetFileSet ActionType = "changeset/fileSet" - ActionTypeChangesetFileRemoved ActionType = "changeset/fileRemoved" - ActionTypeChangesetOperationsChanged ActionType = "changeset/operationsChanged" - ActionTypeChangesetOperationStatusChanged ActionType = "changeset/operationStatusChanged" - ActionTypeChangesetCleared ActionType = "changeset/cleared" - ActionTypeCommentsThreadSet ActionType = "comments/threadSet" - ActionTypeCommentsThreadRemoved ActionType = "comments/threadRemoved" - ActionTypeCommentsCommentSet ActionType = "comments/commentSet" - ActionTypeCommentsCommentRemoved ActionType = "comments/commentRemoved" - ActionTypeCommentsCleared ActionType = "comments/cleared" - ActionTypeRootTerminalsChanged ActionType = "root/terminalsChanged" - ActionTypeRootConfigChanged ActionType = "root/configChanged" - ActionTypeTerminalData ActionType = "terminal/data" - ActionTypeTerminalInput ActionType = "terminal/input" - ActionTypeTerminalResized ActionType = "terminal/resized" - ActionTypeTerminalClaimed ActionType = "terminal/claimed" - ActionTypeTerminalTitleChanged ActionType = "terminal/titleChanged" - ActionTypeTerminalCwdChanged ActionType = "terminal/cwdChanged" - ActionTypeTerminalExited ActionType = "terminal/exited" - ActionTypeTerminalCleared ActionType = "terminal/cleared" + ActionTypeRootAgentsChanged ActionType = "root/agentsChanged" + ActionTypeRootActiveSessionsChanged ActionType = "root/activeSessionsChanged" + ActionTypeSessionReady ActionType = "session/ready" + ActionTypeSessionCreationFailed ActionType = "session/creationFailed" + ActionTypeSessionTurnStarted ActionType = "session/turnStarted" + ActionTypeSessionDelta ActionType = "session/delta" + ActionTypeSessionResponsePart ActionType = "session/responsePart" + ActionTypeSessionToolCallStart ActionType = "session/toolCallStart" + ActionTypeSessionToolCallDelta ActionType = "session/toolCallDelta" + ActionTypeSessionToolCallReady ActionType = "session/toolCallReady" + ActionTypeSessionToolCallConfirmed ActionType = "session/toolCallConfirmed" + ActionTypeSessionToolCallComplete ActionType = "session/toolCallComplete" + ActionTypeSessionToolCallResultConfirmed ActionType = "session/toolCallResultConfirmed" + ActionTypeSessionToolCallContentChanged ActionType = "session/toolCallContentChanged" + ActionTypeSessionTurnComplete ActionType = "session/turnComplete" + ActionTypeSessionTurnCancelled ActionType = "session/turnCancelled" + ActionTypeSessionError ActionType = "session/error" + ActionTypeSessionTitleChanged ActionType = "session/titleChanged" + ActionTypeSessionUsage ActionType = "session/usage" + ActionTypeSessionReasoning ActionType = "session/reasoning" + ActionTypeSessionModelChanged ActionType = "session/modelChanged" + ActionTypeSessionAgentChanged ActionType = "session/agentChanged" + ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged" + ActionTypeSessionActiveClientChanged ActionType = "session/activeClientChanged" + ActionTypeSessionActiveClientToolsChanged ActionType = "session/activeClientToolsChanged" + ActionTypeSessionPendingMessageSet ActionType = "session/pendingMessageSet" + ActionTypeSessionPendingMessageRemoved ActionType = "session/pendingMessageRemoved" + ActionTypeSessionQueuedMessagesReordered ActionType = "session/queuedMessagesReordered" + ActionTypeSessionInputRequested ActionType = "session/inputRequested" + ActionTypeSessionInputAnswerChanged ActionType = "session/inputAnswerChanged" + ActionTypeSessionInputCompleted ActionType = "session/inputCompleted" + ActionTypeSessionCustomizationsChanged ActionType = "session/customizationsChanged" + ActionTypeSessionCustomizationToggled ActionType = "session/customizationToggled" + ActionTypeSessionCustomizationUpdated ActionType = "session/customizationUpdated" + ActionTypeSessionCustomizationRemoved ActionType = "session/customizationRemoved" + ActionTypeSessionMcpServerStateChanged ActionType = "session/mcpServerStateChanged" + ActionTypeSessionTruncated ActionType = "session/truncated" + 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" + ActionTypeChangesetFileSet ActionType = "changeset/fileSet" + ActionTypeChangesetFileRemoved ActionType = "changeset/fileRemoved" + ActionTypeChangesetOperationsChanged ActionType = "changeset/operationsChanged" + ActionTypeChangesetOperationStatusChanged ActionType = "changeset/operationStatusChanged" + ActionTypeChangesetCleared ActionType = "changeset/cleared" + ActionTypeCommentsThreadSet ActionType = "comments/threadSet" + ActionTypeCommentsThreadRemoved ActionType = "comments/threadRemoved" + ActionTypeCommentsCommentSet ActionType = "comments/commentSet" + ActionTypeCommentsCommentRemoved ActionType = "comments/commentRemoved" + ActionTypeCommentsCleared ActionType = "comments/cleared" + ActionTypeRootTerminalsChanged ActionType = "root/terminalsChanged" + ActionTypeRootConfigChanged ActionType = "root/configChanged" + ActionTypeTerminalData ActionType = "terminal/data" + ActionTypeTerminalInput ActionType = "terminal/input" + ActionTypeTerminalResized ActionType = "terminal/resized" + ActionTypeTerminalClaimed ActionType = "terminal/claimed" + ActionTypeTerminalTitleChanged ActionType = "terminal/titleChanged" + ActionTypeTerminalCwdChanged ActionType = "terminal/cwdChanged" + ActionTypeTerminalExited ActionType = "terminal/exited" + ActionTypeTerminalCleared ActionType = "terminal/cleared" ActionTypeTerminalCommandDetectionAvailable ActionType = "terminal/commandDetectionAvailable" - ActionTypeTerminalCommandExecuted ActionType = "terminal/commandExecuted" - ActionTypeTerminalCommandFinished ActionType = "terminal/commandFinished" - ActionTypeResourceWatchChanged ActionType = "resourceWatch/changed" + ActionTypeTerminalCommandExecuted ActionType = "terminal/commandExecuted" + ActionTypeTerminalCommandFinished ActionType = "terminal/commandFinished" + ActionTypeResourceWatchChanged ActionType = "resourceWatch/changed" ) // ─── Action Envelope ───────────────────────────────────────────────── // Identifies the client that originally dispatched an action. type ActionOrigin struct { - ClientId string `json:"clientId"` - ClientSeq int64 `json:"clientSeq"` + ClientId string `json:"clientId"` + ClientSeq int64 `json:"clientSeq"` } // ActionEnvelope wraps every action with the channel URI it @@ -203,7 +203,7 @@ type SessionToolCallStartAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Internal tool name (for debugging/logging) ToolName string `json:"toolName"` // Human-readable tool name @@ -226,7 +226,7 @@ type SessionToolCallDeltaAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Partial parameter content to append Content string `json:"content"` // Updated progress message @@ -258,7 +258,7 @@ type SessionToolCallReadyAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Message describing what the tool will do or what confirmation is needed InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input @@ -281,17 +281,17 @@ type SessionToolCallReadyAction struct { // SessionToolCallConfirmedAction is the client approves or denies a // pending tool call (merged approved + denied variants on the wire). type SessionToolCallConfirmedAction struct { - Type ActionType `json:"type"` - TurnId string `json:"turnId"` - ToolCallId string `json:"toolCallId"` - Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Approved bool `json:"approved"` - Confirmed *ToolCallConfirmationReason `json:"confirmed,omitempty"` - Reason *ToolCallCancellationReason `json:"reason,omitempty"` - EditedToolInput *string `json:"editedToolInput,omitempty"` - UserSuggestion *Message `json:"userSuggestion,omitempty"` - ReasonMessage *StringOrMarkdown `json:"reasonMessage,omitempty"` - SelectedOptionId *string `json:"selectedOptionId,omitempty"` + Type ActionType `json:"type"` + TurnId string `json:"turnId"` + ToolCallId string `json:"toolCallId"` + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Approved bool `json:"approved"` + Confirmed *ToolCallConfirmationReason `json:"confirmed,omitempty"` + Reason *ToolCallCancellationReason `json:"reason,omitempty"` + EditedToolInput *string `json:"editedToolInput,omitempty"` + UserSuggestion *Message `json:"userSuggestion,omitempty"` + ReasonMessage *StringOrMarkdown `json:"reasonMessage,omitempty"` + SelectedOptionId *string `json:"selectedOptionId,omitempty"` } // Tool execution finished. Transitions to `completed` or `pending-result-confirmation` @@ -316,7 +316,7 @@ type SessionToolCallCompleteAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Execution result Result ToolCallResult `json:"result"` // If true, the result requires client approval before finalizing @@ -338,7 +338,7 @@ type SessionToolCallResultConfirmedAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Whether the result was approved Approved bool `json:"approved"` } @@ -608,10 +608,10 @@ type SessionCustomizationToggledAction struct { // // The reducer locates the existing entry by `customization.id`: // -// - If found, the entry is replaced entirely with `customization`, -// including its `children` array. To preserve existing children, the -// host must include them on the payload. -// - If not found, the entry is appended. +// - If found, the entry is replaced entirely with `customization`, +// including its `children` array. To preserve existing children, the +// host must include them on the payload. +// - If not found, the entry is appended. type SessionCustomizationUpdatedAction struct { Type ActionType `json:"type"` // The customization to upsert (matched by `customization.id`). @@ -720,7 +720,7 @@ type SessionToolCallContentChangedAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // The current partial content for the running tool call Content []ToolResultContent `json:"content"` } @@ -786,12 +786,12 @@ type ChangesetOperationStatusChangedAction struct { // Drop every file from the changeset. // // Two cases use this: -// 1. The underlying source moved (branch switched, fork point invalidated, -// …) and the server is recomputing from scratch — subsequent -// {@link ChangesetFileSetAction} entries will repopulate it. -// 2. The owning session has ended and the URI is becoming -// un-subscribable — the server will unsubscribe all clients shortly -// after dispatching this action. +// 1. The underlying source moved (branch switched, fork point invalidated, +// …) and the server is recomputing from scratch — subsequent +// {@link ChangesetFileSetAction} entries will repopulate it. +// 2. The owning session has ended and the URI is becoming +// un-subscribable — the server will unsubscribe all clients shortly +// after dispatching this action. // // Clients SHOULD release any references on receipt and SHOULD NOT // distinguish the two cases from the action alone — instead, react to @@ -816,11 +816,11 @@ type CommentsThreadSetAction struct { // Remove a {@link CommentThread} from the channel by its id. // // The server emits this in two cases: -// 1. The client explicitly invoked -// {@link DeleteCommentThreadParams | `deleteCommentThread`}. -// 2. The client invoked {@link DeleteCommentParams | `deleteComment`} on -// the last remaining comment in the thread — the protocol collapses -// the thread rather than leaving an empty one behind. +// 1. The client explicitly invoked +// {@link DeleteCommentThreadParams | `deleteCommentThread`}. +// 2. The client invoked {@link DeleteCommentParams | `deleteComment`} on +// the last remaining comment in the thread — the protocol collapses +// the thread rather than leaving an empty one behind. type CommentsThreadRemovedAction struct { Type ActionType `json:"type"` // The {@link CommentThread.id} of the thread to remove. @@ -1019,74 +1019,74 @@ type StateAction struct { // concrete variant of StateAction. type isStateAction interface{ isStateAction() } -func (*RootAgentsChangedAction) isStateAction() {} -func (*RootActiveSessionsChangedAction) isStateAction() {} -func (*RootConfigChangedAction) isStateAction() {} -func (*SessionReadyAction) isStateAction() {} -func (*SessionCreationFailedAction) isStateAction() {} -func (*SessionTurnStartedAction) isStateAction() {} -func (*SessionDeltaAction) isStateAction() {} -func (*SessionResponsePartAction) isStateAction() {} -func (*SessionToolCallStartAction) isStateAction() {} -func (*SessionToolCallDeltaAction) isStateAction() {} -func (*SessionToolCallReadyAction) isStateAction() {} -func (*SessionToolCallConfirmedAction) isStateAction() {} -func (*SessionToolCallCompleteAction) isStateAction() {} -func (*SessionToolCallResultConfirmedAction) isStateAction() {} -func (*SessionTurnCompleteAction) isStateAction() {} -func (*SessionTurnCancelledAction) isStateAction() {} -func (*SessionErrorAction) isStateAction() {} -func (*SessionTitleChangedAction) isStateAction() {} -func (*SessionUsageAction) isStateAction() {} -func (*SessionReasoningAction) isStateAction() {} -func (*SessionModelChangedAction) isStateAction() {} -func (*SessionAgentChangedAction) isStateAction() {} -func (*SessionIsReadChangedAction) isStateAction() {} -func (*SessionIsArchivedChangedAction) isStateAction() {} -func (*SessionActivityChangedAction) isStateAction() {} -func (*SessionChangesetsChangedAction) isStateAction() {} -func (*SessionServerToolsChangedAction) isStateAction() {} -func (*SessionActiveClientChangedAction) isStateAction() {} -func (*SessionActiveClientToolsChangedAction) isStateAction() {} -func (*SessionPendingMessageSetAction) isStateAction() {} -func (*SessionPendingMessageRemovedAction) isStateAction() {} -func (*SessionQueuedMessagesReorderedAction) isStateAction() {} -func (*SessionInputRequestedAction) isStateAction() {} -func (*SessionInputAnswerChangedAction) isStateAction() {} -func (*SessionInputCompletedAction) isStateAction() {} -func (*SessionCustomizationsChangedAction) isStateAction() {} -func (*SessionCustomizationToggledAction) isStateAction() {} -func (*SessionCustomizationUpdatedAction) isStateAction() {} -func (*SessionCustomizationRemovedAction) isStateAction() {} -func (*SessionMcpServerStateChangedAction) isStateAction() {} -func (*SessionTruncatedAction) isStateAction() {} -func (*SessionConfigChangedAction) isStateAction() {} -func (*SessionMetaChangedAction) isStateAction() {} -func (*SessionToolCallContentChangedAction) isStateAction() {} -func (*ChangesetStatusChangedAction) isStateAction() {} -func (*ChangesetFileSetAction) isStateAction() {} -func (*ChangesetFileRemovedAction) isStateAction() {} -func (*ChangesetOperationsChangedAction) isStateAction() {} -func (*ChangesetOperationStatusChangedAction) isStateAction() {} -func (*ChangesetClearedAction) isStateAction() {} -func (*CommentsThreadSetAction) isStateAction() {} -func (*CommentsThreadRemovedAction) isStateAction() {} -func (*CommentsCommentSetAction) isStateAction() {} -func (*CommentsCommentRemovedAction) isStateAction() {} -func (*CommentsClearedAction) isStateAction() {} -func (*RootTerminalsChangedAction) isStateAction() {} -func (*TerminalDataAction) isStateAction() {} -func (*TerminalInputAction) isStateAction() {} -func (*TerminalResizedAction) isStateAction() {} -func (*TerminalClaimedAction) isStateAction() {} -func (*TerminalTitleChangedAction) isStateAction() {} -func (*TerminalCwdChangedAction) isStateAction() {} -func (*TerminalExitedAction) isStateAction() {} -func (*TerminalClearedAction) isStateAction() {} +func (*RootAgentsChangedAction) isStateAction() {} +func (*RootActiveSessionsChangedAction) isStateAction() {} +func (*RootConfigChangedAction) isStateAction() {} +func (*SessionReadyAction) isStateAction() {} +func (*SessionCreationFailedAction) isStateAction() {} +func (*SessionTurnStartedAction) isStateAction() {} +func (*SessionDeltaAction) isStateAction() {} +func (*SessionResponsePartAction) isStateAction() {} +func (*SessionToolCallStartAction) isStateAction() {} +func (*SessionToolCallDeltaAction) isStateAction() {} +func (*SessionToolCallReadyAction) isStateAction() {} +func (*SessionToolCallConfirmedAction) isStateAction() {} +func (*SessionToolCallCompleteAction) isStateAction() {} +func (*SessionToolCallResultConfirmedAction) isStateAction() {} +func (*SessionTurnCompleteAction) isStateAction() {} +func (*SessionTurnCancelledAction) isStateAction() {} +func (*SessionErrorAction) isStateAction() {} +func (*SessionTitleChangedAction) isStateAction() {} +func (*SessionUsageAction) isStateAction() {} +func (*SessionReasoningAction) isStateAction() {} +func (*SessionModelChangedAction) isStateAction() {} +func (*SessionAgentChangedAction) isStateAction() {} +func (*SessionIsReadChangedAction) isStateAction() {} +func (*SessionIsArchivedChangedAction) isStateAction() {} +func (*SessionActivityChangedAction) isStateAction() {} +func (*SessionChangesetsChangedAction) isStateAction() {} +func (*SessionServerToolsChangedAction) isStateAction() {} +func (*SessionActiveClientChangedAction) isStateAction() {} +func (*SessionActiveClientToolsChangedAction) isStateAction() {} +func (*SessionPendingMessageSetAction) isStateAction() {} +func (*SessionPendingMessageRemovedAction) isStateAction() {} +func (*SessionQueuedMessagesReorderedAction) isStateAction() {} +func (*SessionInputRequestedAction) isStateAction() {} +func (*SessionInputAnswerChangedAction) isStateAction() {} +func (*SessionInputCompletedAction) isStateAction() {} +func (*SessionCustomizationsChangedAction) isStateAction() {} +func (*SessionCustomizationToggledAction) isStateAction() {} +func (*SessionCustomizationUpdatedAction) isStateAction() {} +func (*SessionCustomizationRemovedAction) isStateAction() {} +func (*SessionMcpServerStateChangedAction) isStateAction() {} +func (*SessionTruncatedAction) isStateAction() {} +func (*SessionConfigChangedAction) isStateAction() {} +func (*SessionMetaChangedAction) isStateAction() {} +func (*SessionToolCallContentChangedAction) isStateAction() {} +func (*ChangesetStatusChangedAction) isStateAction() {} +func (*ChangesetFileSetAction) isStateAction() {} +func (*ChangesetFileRemovedAction) isStateAction() {} +func (*ChangesetOperationsChangedAction) isStateAction() {} +func (*ChangesetOperationStatusChangedAction) isStateAction() {} +func (*ChangesetClearedAction) isStateAction() {} +func (*CommentsThreadSetAction) isStateAction() {} +func (*CommentsThreadRemovedAction) isStateAction() {} +func (*CommentsCommentSetAction) isStateAction() {} +func (*CommentsCommentRemovedAction) isStateAction() {} +func (*CommentsClearedAction) isStateAction() {} +func (*RootTerminalsChangedAction) isStateAction() {} +func (*TerminalDataAction) isStateAction() {} +func (*TerminalInputAction) isStateAction() {} +func (*TerminalResizedAction) isStateAction() {} +func (*TerminalClaimedAction) isStateAction() {} +func (*TerminalTitleChangedAction) isStateAction() {} +func (*TerminalCwdChangedAction) isStateAction() {} +func (*TerminalExitedAction) isStateAction() {} +func (*TerminalClearedAction) isStateAction() {} func (*TerminalCommandDetectionAvailableAction) isStateAction() {} -func (*TerminalCommandExecutedAction) isStateAction() {} -func (*TerminalCommandFinishedAction) isStateAction() {} -func (*ResourceWatchChangedAction) isStateAction() {} +func (*TerminalCommandExecutedAction) isStateAction() {} +func (*TerminalCommandFinishedAction) isStateAction() {} +func (*ResourceWatchChangedAction) isStateAction() {} // StateActionUnknown carries an unrecognized StateAction variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type StateActionUnknown struct { diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go index 54f41f54..95a4dcd3 100644 --- a/clients/go/ahptypes/commands.generated.go +++ b/clients/go/ahptypes/commands.generated.go @@ -19,7 +19,7 @@ var _ = json.RawMessage(nil) type ReconnectResultType string const ( - ReconnectResultTypeReplay ReconnectResultType = "replay" + ReconnectResultTypeReplay ReconnectResultType = "replay" ReconnectResultTypeSnapshot ReconnectResultType = "snapshot" ) @@ -28,7 +28,7 @@ type ContentEncoding string const ( ContentEncodingBase64 ContentEncoding = "base64" - ContentEncodingUtf8 ContentEncoding = "utf-8" + ContentEncodingUtf8 ContentEncoding = "utf-8" ) // The kind of completion items being requested. @@ -45,36 +45,36 @@ const ( type ResourceType string const ( - ResourceTypeFile ResourceType = "file" + ResourceTypeFile ResourceType = "file" ResourceTypeDirectory ResourceType = "directory" - ResourceTypeSymlink ResourceType = "symlink" + ResourceTypeSymlink ResourceType = "symlink" ) // How {@link ResourceWriteParams.data} is placed within the target file. // // Each mode interprets {@link ResourceWriteParams.position} differently: // -// - `truncate` (default): rooted at the **start** of the file. The file is -// truncated at `position` (0 by default) and `data` is written from that -// offset, so the resulting file is `existing[0..position] + data`. With -// `position` omitted this is a full overwrite. -// - `append`: rooted at the **end** of the file. `position` counts bytes -// backwards from EOF, so `position: 0` (the default) writes at EOF — -// POSIX append — and `position: 5` inserts `data` 5 bytes before the -// current EOF, shifting those trailing 5 bytes after the inserted region. -// The server MUST evaluate the effective EOF and write atomically with -// respect to other appenders so concurrent `append` writes do not -// clobber each other. -// - `insert`: rooted at the **start** of the file. `position` (0 by default) -// is the byte offset at which `data` is spliced in; bytes at or after -// `position` are shifted right by `data.length`. `insert` always grows -// the file — use `truncate` to overwrite bytes in place. +// - `truncate` (default): rooted at the **start** of the file. The file is +// truncated at `position` (0 by default) and `data` is written from that +// offset, so the resulting file is `existing[0..position] + data`. With +// `position` omitted this is a full overwrite. +// - `append`: rooted at the **end** of the file. `position` counts bytes +// backwards from EOF, so `position: 0` (the default) writes at EOF — +// POSIX append — and `position: 5` inserts `data` 5 bytes before the +// current EOF, shifting those trailing 5 bytes after the inserted region. +// The server MUST evaluate the effective EOF and write atomically with +// respect to other appenders so concurrent `append` writes do not +// clobber each other. +// - `insert`: rooted at the **start** of the file. `position` (0 by default) +// is the byte offset at which `data` is spliced in; bytes at or after +// `position` are shifted right by `data.length`. `insert` always grows +// the file — use `truncate` to overwrite bytes in place. type ResourceWriteMode string const ( ResourceWriteModeTruncate ResourceWriteMode = "truncate" - ResourceWriteModeAppend ResourceWriteMode = "append" - ResourceWriteModeInsert ResourceWriteMode = "insert" + ResourceWriteModeAppend ResourceWriteMode = "append" + ResourceWriteModeInsert ResourceWriteMode = "insert" ) // ─── Command Payloads ───────────────────────────────────────────────── @@ -801,9 +801,9 @@ type CompletionsParams struct { // A single completion item returned by the `completions` command. // // When the user accepts an item, the client SHOULD: -// 1. Replace the range `[rangeStart, rangeEnd)` in the input with `insertText` -// (or insert `insertText` at the cursor when the range is omitted). -// 2. Associate the item's `attachment` with the resulting {@link Message}. +// 1. Replace the range `[rangeStart, rangeEnd)` in the input with `insertText` +// (or insert `insertText` at the cursor when the range is omitted). +// 2. Associate the item's `attachment` with the resulting {@link Message}. type CompletionItem struct { // The text inserted into the input when this item is accepted. InsertText string `json:"insertText"` @@ -881,13 +881,15 @@ type ChangesetOperationFollowUp struct { External *bool `json:"external,omitempty"` } -// Create a new {@link CommentThread} anchored to a file range from a -// specific turn. +// Create a new {@link CommentThread} anchored to a file from a specific +// turn, optionally narrowed to a range within that file. // // The initial comment is required — the protocol forbids empty threads, // so thread creation and first-comment creation are fused into one -// command. The server assigns both {@link CreateCommentThreadResult.threadId} -// and {@link CreateCommentThreadResult.commentId}, then broadcasts a +// command. The created thread always starts unresolved +// ({@link CommentThread.resolved} is `false`). The server assigns both +// {@link CreateCommentThreadResult.threadId} and +// {@link CreateCommentThreadResult.commentId}, then broadcasts a // {@link CommentsThreadSetAction} on the channel. type CreateCommentThreadParams struct { // Channel URI this command targets. @@ -896,8 +898,9 @@ type CreateCommentThreadParams struct { TurnId string `json:"turnId"` // Anchored file URI. Resource URI `json:"resource"` - // Anchored range within {@link resource}. - Range TextRange `json:"range"` + // Anchored range within {@link resource}. When omitted the thread is + // anchored to the entire file. + Range *TextRange `json:"range,omitempty"` // First comment in the thread. The server assigns its {@link Comment.id}. Comment NewComment `json:"comment"` } @@ -910,9 +913,10 @@ type CreateCommentThreadResult struct { CommentId string `json:"commentId"` } -// Re-anchor an existing {@link CommentThread} — typically used to re-pin -// a thread to a different range or a newer turn after an edit. Comments -// themselves are not modified by this command; use +// Re-anchor or resolve an existing {@link CommentThread} — typically used +// to re-pin a thread to a different range or a newer turn after an edit, +// or to mark the thread {@link CommentThread.resolved | resolved} (or +// re-open it). Comments themselves are not modified by this command; use // {@link AddCommentParams | `addComment`}, // {@link EditCommentParams | `editComment`}, or // {@link DeleteCommentParams | `deleteComment`} for that. @@ -930,6 +934,8 @@ type UpdateCommentThreadParams struct { Resource *URI `json:"resource,omitempty"` // New anchored range, if changing. Range *TextRange `json:"range,omitempty"` + // New {@link CommentThread.resolved} state, if changing. + Resolved *bool `json:"resolved,omitempty"` } // Delete an entire comment thread (and every comment it contains). The @@ -1003,7 +1009,7 @@ type ReconnectResult struct { // concrete variant of ReconnectResult. type isReconnectResult interface{ isReconnectResult() } -func (*ReconnectReplayResult) isReconnectResult() {} +func (*ReconnectReplayResult) isReconnectResult() {} func (*ReconnectSnapshotResult) isReconnectResult() {} // UnmarshalJSON decodes the variant indicated by the "type" discriminator. @@ -1061,10 +1067,10 @@ func (*ChangesetOperationResourceTarget) isChangesetOperationTarget() {} // ChangesetOperationRangeTarget targets a range within a resource. type ChangesetOperationRangeTarget struct { - Kind string `json:"kind"` - Resource URI `json:"resource"` - Side *string `json:"side,omitempty"` - Range ChangesetOperationTargetRange `json:"range"` + Kind string `json:"kind"` + Resource URI `json:"resource"` + Side *string `json:"side,omitempty"` + Range ChangesetOperationTargetRange `json:"range"` } func (*ChangesetOperationRangeTarget) isChangesetOperationTarget() {} diff --git a/clients/go/ahptypes/errors.generated.go b/clients/go/ahptypes/errors.generated.go index 3b3e8f82..b5a22f4b 100644 --- a/clients/go/ahptypes/errors.generated.go +++ b/clients/go/ahptypes/errors.generated.go @@ -26,16 +26,16 @@ const ( // AHP application-specific error codes (above the JSON-RPC reserved // range). const ( - ErrorCodeSessionNotFound int32 = -32001 - ErrorCodeProviderNotFound int32 = -32002 - ErrorCodeSessionAlreadyExists int32 = -32003 - ErrorCodeTurnInProgress int32 = -32004 - ErrorCodeUnsupportedProtocolVersion int32 = -32005 - ErrorCodeContentNotFound int32 = -32006 - ErrorCodeAuthRequired int32 = -32007 - ErrorCodeNotFound int32 = -32008 - ErrorCodePermissionDenied int32 = -32009 - ErrorCodeAlreadyExists int32 = -32010 + ErrorCodeSessionNotFound int32 = -32001 + ErrorCodeProviderNotFound int32 = -32002 + ErrorCodeSessionAlreadyExists int32 = -32003 + ErrorCodeTurnInProgress int32 = -32004 + ErrorCodeUnsupportedProtocolVersion int32 = -32005 + ErrorCodeContentNotFound int32 = -32006 + ErrorCodeAuthRequired int32 = -32007 + ErrorCodeNotFound int32 = -32008 + ErrorCodePermissionDenied int32 = -32009 + ErrorCodeAlreadyExists int32 = -32010 ) // AhpErrorCode is the type alias used by AHP application error codes. diff --git a/clients/go/ahptypes/notifications.generated.go b/clients/go/ahptypes/notifications.generated.go index 625be761..7457347b 100644 --- a/clients/go/ahptypes/notifications.generated.go +++ b/clients/go/ahptypes/notifications.generated.go @@ -59,20 +59,20 @@ type SessionRemovedParams struct { // // Semantics: // -// - Only fields present in `changes` have new values; omitted fields are -// unchanged on the client's cached summary. -// - Identity fields (`resource`, `provider`, `createdAt`) never change and -// are not carried. -// - Like all protocol notifications, this is ephemeral: it is **not** -// replayed on reconnect. On reconnect, clients should re-fetch the full -// catalog via `listSessions()` as usual. -// - The server SHOULD emit this notification whenever any mutable field on -// {@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) at their discretion. -// - Clients that have no cached entry for `session` MAY ignore the -// notification; it is not a substitute for `root/sessionAdded`. +// - Only fields present in `changes` have new values; omitted fields are +// unchanged on the client's cached summary. +// - Identity fields (`resource`, `provider`, `createdAt`) never change and +// are not carried. +// - Like all protocol notifications, this is ephemeral: it is **not** +// replayed on reconnect. On reconnect, clients should re-fetch the full +// catalog via `listSessions()` as usual. +// - The server SHOULD emit this notification whenever any mutable field on +// {@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) 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 { // Channel URI this notification belongs to (the root channel) Channel URI `json:"channel"` diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index f82c9383..02df594a 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -19,8 +19,8 @@ var _ = json.RawMessage(nil) type PolicyState string const ( - PolicyStateEnabled PolicyState = "enabled" - PolicyStateDisabled PolicyState = "disabled" + PolicyStateEnabled PolicyState = "enabled" + PolicyStateDisabled PolicyState = "disabled" PolicyStateUnconfigured PolicyState = "unconfigured" ) @@ -38,8 +38,8 @@ const ( type SessionLifecycle string const ( - SessionLifecycleCreating SessionLifecycle = "creating" - SessionLifecycleReady SessionLifecycle = "ready" + SessionLifecycleCreating SessionLifecycle = "creating" + SessionLifecycleReady SessionLifecycle = "ready" SessionLifecycleCreationFailed SessionLifecycle = "creationFailed" ) @@ -75,19 +75,19 @@ func (s SessionStatus) Or(other SessionStatus) SessionStatus { return s | other type SessionInputAnswerState string const ( - SessionInputAnswerStateDraft SessionInputAnswerState = "draft" + SessionInputAnswerStateDraft SessionInputAnswerState = "draft" SessionInputAnswerStateSubmitted SessionInputAnswerState = "submitted" - SessionInputAnswerStateSkipped SessionInputAnswerState = "skipped" + SessionInputAnswerStateSkipped SessionInputAnswerState = "skipped" ) // Answer value kind. type SessionInputAnswerValueKind string const ( - SessionInputAnswerValueKindText SessionInputAnswerValueKind = "text" - SessionInputAnswerValueKindNumber SessionInputAnswerValueKind = "number" - SessionInputAnswerValueKindBoolean SessionInputAnswerValueKind = "boolean" - SessionInputAnswerValueKindSelected SessionInputAnswerValueKind = "selected" + SessionInputAnswerValueKindText SessionInputAnswerValueKind = "text" + SessionInputAnswerValueKindNumber SessionInputAnswerValueKind = "number" + SessionInputAnswerValueKindBoolean SessionInputAnswerValueKind = "boolean" + SessionInputAnswerValueKindSelected SessionInputAnswerValueKind = "selected" SessionInputAnswerValueKindSelectedMany SessionInputAnswerValueKind = "selected-many" ) @@ -95,30 +95,30 @@ const ( type SessionInputQuestionKind string const ( - SessionInputQuestionKindText SessionInputQuestionKind = "text" - SessionInputQuestionKindNumber SessionInputQuestionKind = "number" - SessionInputQuestionKindInteger SessionInputQuestionKind = "integer" - SessionInputQuestionKindBoolean SessionInputQuestionKind = "boolean" + SessionInputQuestionKindText SessionInputQuestionKind = "text" + SessionInputQuestionKindNumber SessionInputQuestionKind = "number" + SessionInputQuestionKindInteger SessionInputQuestionKind = "integer" + SessionInputQuestionKindBoolean SessionInputQuestionKind = "boolean" SessionInputQuestionKindSingleSelect SessionInputQuestionKind = "single-select" - SessionInputQuestionKindMultiSelect SessionInputQuestionKind = "multi-select" + SessionInputQuestionKindMultiSelect SessionInputQuestionKind = "multi-select" ) // How a client completed an input request. type SessionInputResponseKind string const ( - SessionInputResponseKindAccept SessionInputResponseKind = "accept" + SessionInputResponseKindAccept SessionInputResponseKind = "accept" SessionInputResponseKindDecline SessionInputResponseKind = "decline" - SessionInputResponseKindCancel SessionInputResponseKind = "cancel" + SessionInputResponseKindCancel SessionInputResponseKind = "cancel" ) // How a turn ended. type TurnState string const ( - TurnStateComplete TurnState = "complete" + TurnStateComplete TurnState = "complete" TurnStateCancelled TurnState = "cancelled" - TurnStateError TurnState = "error" + TurnStateError TurnState = "error" ) // Discriminant for {@link MessageAttachment} variants. @@ -137,10 +137,10 @@ const ( type ResponsePartKind string const ( - ResponsePartKindMarkdown ResponsePartKind = "markdown" - ResponsePartKindContentRef ResponsePartKind = "contentRef" - ResponsePartKindToolCall ResponsePartKind = "toolCall" - ResponsePartKindReasoning ResponsePartKind = "reasoning" + ResponsePartKindMarkdown ResponsePartKind = "markdown" + ResponsePartKindContentRef ResponsePartKind = "contentRef" + ResponsePartKindToolCall ResponsePartKind = "toolCall" + ResponsePartKindReasoning ResponsePartKind = "reasoning" ResponsePartKindSystemNotification ResponsePartKind = "systemNotification" ) @@ -148,12 +148,12 @@ const ( type ToolCallStatus string const ( - ToolCallStatusStreaming ToolCallStatus = "streaming" - ToolCallStatusPendingConfirmation ToolCallStatus = "pending-confirmation" - ToolCallStatusRunning ToolCallStatus = "running" + ToolCallStatusStreaming ToolCallStatus = "streaming" + ToolCallStatusPendingConfirmation ToolCallStatus = "pending-confirmation" + ToolCallStatusRunning ToolCallStatus = "running" ToolCallStatusPendingResultConfirmation ToolCallStatus = "pending-result-confirmation" - ToolCallStatusCompleted ToolCallStatus = "completed" - ToolCallStatusCancelled ToolCallStatus = "cancelled" + ToolCallStatusCompleted ToolCallStatus = "completed" + ToolCallStatusCancelled ToolCallStatus = "cancelled" ) // How a tool call was confirmed for execution. @@ -164,17 +164,17 @@ const ( type ToolCallConfirmationReason string const ( - ToolCallConfirmationReasonNotNeeded ToolCallConfirmationReason = "not-needed" + ToolCallConfirmationReasonNotNeeded ToolCallConfirmationReason = "not-needed" ToolCallConfirmationReasonUserAction ToolCallConfirmationReason = "user-action" - ToolCallConfirmationReasonSetting ToolCallConfirmationReason = "setting" + ToolCallConfirmationReasonSetting ToolCallConfirmationReason = "setting" ) // Why a tool call was cancelled. type ToolCallCancellationReason string const ( - ToolCallCancellationReasonDenied ToolCallCancellationReason = "denied" - ToolCallCancellationReasonSkipped ToolCallCancellationReason = "skipped" + ToolCallCancellationReasonDenied ToolCallCancellationReason = "denied" + ToolCallCancellationReasonSkipped ToolCallCancellationReason = "skipped" ToolCallCancellationReasonResultDenied ToolCallCancellationReason = "result-denied" ) @@ -183,26 +183,26 @@ type ConfirmationOptionKind string const ( ConfirmationOptionKindApprove ConfirmationOptionKind = "approve" - ConfirmationOptionKindDeny ConfirmationOptionKind = "deny" + ConfirmationOptionKindDeny ConfirmationOptionKind = "deny" ) type ToolCallContributorKind string const ( ToolCallContributorKindClient ToolCallContributorKind = "client" - ToolCallContributorKindMCP ToolCallContributorKind = "mcp" + ToolCallContributorKindMCP ToolCallContributorKind = "mcp" ) // Discriminant for tool result content types. type ToolResultContentType string const ( - ToolResultContentTypeText ToolResultContentType = "text" + ToolResultContentTypeText ToolResultContentType = "text" ToolResultContentTypeEmbeddedResource ToolResultContentType = "embeddedResource" - ToolResultContentTypeResource ToolResultContentType = "resource" - ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" - ToolResultContentTypeTerminal ToolResultContentType = "terminal" - ToolResultContentTypeSubagent ToolResultContentType = "subagent" + ToolResultContentTypeResource ToolResultContentType = "resource" + ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" + ToolResultContentTypeTerminal ToolResultContentType = "terminal" + ToolResultContentTypeSubagent ToolResultContentType = "subagent" ) // Discriminant for the kind of customization. @@ -217,13 +217,13 @@ const ( type CustomizationType string const ( - CustomizationTypePlugin CustomizationType = "plugin" + CustomizationTypePlugin CustomizationType = "plugin" CustomizationTypeDirectory CustomizationType = "directory" - CustomizationTypeAgent CustomizationType = "agent" - CustomizationTypeSkill CustomizationType = "skill" - CustomizationTypePrompt CustomizationType = "prompt" - CustomizationTypeRule CustomizationType = "rule" - CustomizationTypeHook CustomizationType = "hook" + CustomizationTypeAgent CustomizationType = "agent" + CustomizationTypeSkill CustomizationType = "skill" + CustomizationTypePrompt CustomizationType = "prompt" + CustomizationTypeRule CustomizationType = "rule" + CustomizationTypeHook CustomizationType = "hook" CustomizationTypeMcpServer CustomizationType = "mcpServer" ) @@ -231,17 +231,17 @@ const ( type CustomizationLoadStatus string const ( - CustomizationLoadStatusLoading CustomizationLoadStatus = "loading" - CustomizationLoadStatusLoaded CustomizationLoadStatus = "loaded" + CustomizationLoadStatusLoading CustomizationLoadStatus = "loading" + CustomizationLoadStatusLoaded CustomizationLoadStatus = "loaded" CustomizationLoadStatusDegraded CustomizationLoadStatus = "degraded" - CustomizationLoadStatusError CustomizationLoadStatus = "error" + CustomizationLoadStatusError CustomizationLoadStatus = "error" ) // Discriminant for terminal claim kinds. type TerminalClaimKind string const ( - TerminalClaimKindClient TerminalClaimKind = "client" + TerminalClaimKindClient TerminalClaimKind = "client" TerminalClaimKindSession TerminalClaimKind = "session" ) @@ -342,7 +342,7 @@ const ( type ResourceChangeType string const ( - ResourceChangeTypeAdded ResourceChangeType = "added" + ResourceChangeTypeAdded ResourceChangeType = "added" ResourceChangeTypeUpdated ResourceChangeType = "updated" ResourceChangeTypeDeleted ResourceChangeType = "deleted" ) @@ -842,30 +842,30 @@ type SessionInputOption struct { // Value captured for one answer. type SessionInputTextAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` } type SessionInputNumberAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value float64 `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value float64 `json:"value"` } type SessionInputBooleanAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value bool `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value bool `json:"value"` } type SessionInputSelectedAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` // Free-form text entered instead of selecting an option FreeformValues []string `json:"freeformValues,omitempty"` } type SessionInputSelectedManyAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value []string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value []string `json:"value"` // Free-form text entered in addition to selected options FreeformValues []string `json:"freeformValues,omitempty"` } @@ -893,8 +893,8 @@ type SessionInputTextQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Format hint for text questions, such as `email`, `uri`, `date`, or `date-time` Format *string `json:"format,omitempty"` // Minimum string length @@ -914,8 +914,8 @@ type SessionInputNumberQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Minimum value Min *float64 `json:"min,omitempty"` // Maximum value @@ -933,8 +933,8 @@ type SessionInputBooleanQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Default boolean value DefaultValue *bool `json:"defaultValue,omitempty"` } @@ -948,8 +948,8 @@ type SessionInputSingleSelectQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Options the user may select from Options []SessionInputOption `json:"options"` // Whether the user may enter text instead of selecting an option @@ -965,8 +965,8 @@ type SessionInputMultiSelectQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Options the user may select from Options []SessionInputOption `json:"options"` // Whether the user may enter text in addition to selecting options @@ -1258,8 +1258,8 @@ type ToolCallStreamingState struct { // This MAY include a `ui` field corresponding to the MCP Apps (SEP-1865) // `McpUiToolMeta` found in MCP tool calls, which may be used in combination // with the {@link contributor} to serve MCP Apps. - Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Status ToolCallStatus `json:"status"` + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Status ToolCallStatus `json:"status"` // Partial parameters accumulated so far PartialInput *string `json:"partialInput,omitempty"` // Progress message shown while parameters are streaming @@ -1286,8 +1286,8 @@ type ToolCallPendingConfirmationState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // Short title for the confirmation prompt (e.g. `"Run in terminal"`, `"Write file"`) ConfirmationTitle *StringOrMarkdown `json:"confirmationTitle,omitempty"` // File edits that this tool call will perform, for preview before confirmation @@ -1320,8 +1320,8 @@ type ToolCallRunningState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1366,8 +1366,8 @@ type ToolCallPendingResultConfirmationState struct { // This mirrors the `structuredContent` field of MCP `CallToolResult`. StructuredContent map[string]json.RawMessage `json:"structuredContent,omitempty"` // Error details if the tool failed - Error *json.RawMessage `json:"error,omitempty"` - Status ToolCallStatus `json:"status"` + Error *json.RawMessage `json:"error,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1407,8 +1407,8 @@ type ToolCallCompletedState struct { // This mirrors the `structuredContent` field of MCP `CallToolResult`. StructuredContent map[string]json.RawMessage `json:"structuredContent,omitempty"` // Error details if the tool failed - Error *json.RawMessage `json:"error,omitempty"` - Status ToolCallStatus `json:"status"` + Error *json.RawMessage `json:"error,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1434,8 +1434,8 @@ type ToolCallCancelledState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // Why the tool was cancelled Reason ToolCallCancellationReason `json:"reason"` // Optional message explaining the cancellation @@ -1517,8 +1517,8 @@ type ToolResultResourceContent struct { // Approximate size in bytes SizeHint *int64 `json:"sizeHint,omitempty"` // Content MIME type - ContentType *string `json:"contentType,omitempty"` - Type ToolResultContentType `json:"type"` + ContentType *string `json:"contentType,omitempty"` + Type ToolResultContentType `json:"type"` } // Describes a file modification performed by a tool. @@ -1528,7 +1528,7 @@ type ToolResultFileEditContent struct { // The file state after the edit. Absent for file deletions. After *json.RawMessage `json:"after,omitempty"` // Optional diff display metadata - Diff *json.RawMessage `json:"diff,omitempty"` + Diff *json.RawMessage `json:"diff,omitempty"` Type ToolResultContentType `json:"type"` } @@ -1621,7 +1621,7 @@ type PluginCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` } // A {@link PluginCustomization} as published by a client. Extends the @@ -1669,7 +1669,7 @@ type ClientPluginCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` // Opaque version token used by the host to detect changes. Nonce *string `json:"nonce,omitempty"` } @@ -1719,7 +1719,7 @@ type DirectoryCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` // Which child customization type this directory holds. Contents CustomizationType `json:"contents"` // Whether clients may write into this directory. @@ -1752,8 +1752,8 @@ type AgentCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description of what the agent specializes in and when to // invoke it. Sourced from the agent file's frontmatter `description`. Description *string `json:"description,omitempty"` @@ -1790,8 +1790,8 @@ type SkillCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description used for help text and auto-invocation matching. // Sourced from the skill's frontmatter `description`. Description *string `json:"description,omitempty"` @@ -1823,8 +1823,8 @@ type PromptCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description of what the prompt does. Description *string `json:"description,omitempty"` } @@ -1859,8 +1859,8 @@ type RuleCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Description of what the rule enforces. Description *string `json:"description,omitempty"` // When `true`, the rule is always active (subject to `globs` if any). @@ -1894,8 +1894,8 @@ type HookCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` } // An MCP server contributed by a plugin or directory. @@ -1927,8 +1927,8 @@ type McpServerCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Whether this MCP server is currently enabled. Enabled bool `json:"enabled"` // Current lifecycle state of the MCP server. @@ -1985,20 +1985,20 @@ type McpServerCustomizationApps struct { // An agent host MUST only advertise a capability when it actually accepts the // corresponding methods/notifications on the `mcp://` channel: // -// - {@link serverTools}: host proxies `tools/list` and `tools/call` to -// the MCP server. When `listChanged` is `true`, the host also forwards -// `notifications/tools/list_changed`. -// - {@link serverResources}: host proxies `resources/read`, -// `resources/list`, and `resources/templates/list` to the MCP server. -// When `listChanged` is `true`, the host also forwards -// `notifications/resources/list_changed`. -// - {@link logging}: host accepts `notifications/message` log entries -// from the App and forwards them via `mcpNotification` (and forwards -// `logging/setLevel` calls to the server). -// - {@link sampling}: host serves `sampling/createMessage` via -// `mcpMethodCall`. When `sampling.tools` is present, the host also -// accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks -// inside `CreateMessageRequest`. +// - {@link serverTools}: host proxies `tools/list` and `tools/call` to +// the MCP server. When `listChanged` is `true`, the host also forwards +// `notifications/tools/list_changed`. +// - {@link serverResources}: host proxies `resources/read`, +// `resources/list`, and `resources/templates/list` to the MCP server. +// When `listChanged` is `true`, the host also forwards +// `notifications/resources/list_changed`. +// - {@link logging}: host accepts `notifications/message` log entries +// from the App and forwards them via `mcpNotification` (and forwards +// `logging/setLevel` calls to the server). +// - {@link sampling}: host serves `sampling/createMessage` via +// `mcpMethodCall`. When `sampling.tools` is present, the host also +// accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks +// inside `CreateMessageRequest`. type AhpMcpUiHostCapabilities struct { // Producer proxies the MCP `tools/*` methods to the upstream server. ServerTools *json.RawMessage `json:"serverTools,omitempty"` @@ -2374,13 +2374,14 @@ type CommentsState struct { Threads []CommentThread `json:"threads"` } -// A conversation anchored to a specific range in a specific file produced -// by a specific turn. +// A conversation anchored to a specific file produced by a specific turn, +// optionally narrowed to a range within that file. // // {@link turnId} anchors the thread to the file versions that turn // produced, so a later turn that rewrites the same file does not silently // invalidate the comment's anchor — clients can resolve {@link resource} -// and {@link range} against the turn's changeset. +// and {@link range} against the turn's changeset. When {@link range} is +// omitted the thread is anchored to the entire file. // // Every thread MUST contain at least one {@link Comment}. The server // enforces this invariant: {@link CreateCommentThreadParams | @@ -2396,8 +2397,13 @@ type CommentThread struct { TurnId string `json:"turnId"` // The file the thread is anchored to. Resource URI `json:"resource"` - // Range within {@link resource} the thread is anchored to. - Range TextRange `json:"range"` + // Range within {@link resource} the thread is anchored to. When omitted + // the thread is anchored to the entire file. + Range *TextRange `json:"range,omitempty"` + // Whether the thread has been resolved. Newly created threads are always + // unresolved (`false`); a client marks a thread resolved (or re-opens it) + // through {@link UpdateCommentThreadParams | `updateCommentThread`}. + Resolved bool `json:"resolved"` // Comments in this thread, in dispatch order (oldest first). MUST // contain at least one entry. Comments []Comment `json:"comments"` @@ -2521,10 +2527,10 @@ type ResponsePart struct { // concrete variant of ResponsePart. type isResponsePart interface{ isResponsePart() } -func (*MarkdownResponsePart) isResponsePart() {} -func (*ResourceResponsePart) isResponsePart() {} -func (*ToolCallResponsePart) isResponsePart() {} -func (*ReasoningResponsePart) isResponsePart() {} +func (*MarkdownResponsePart) isResponsePart() {} +func (*ResourceResponsePart) isResponsePart() {} +func (*ToolCallResponsePart) isResponsePart() {} +func (*ReasoningResponsePart) isResponsePart() {} func (*SystemNotificationResponsePart) isResponsePart() {} // ResponsePartUnknown carries an unrecognized ResponsePart variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2602,12 +2608,12 @@ type ToolCallState struct { // concrete variant of ToolCallState. type isToolCallState interface{ isToolCallState() } -func (*ToolCallStreamingState) isToolCallState() {} -func (*ToolCallPendingConfirmationState) isToolCallState() {} -func (*ToolCallRunningState) isToolCallState() {} +func (*ToolCallStreamingState) isToolCallState() {} +func (*ToolCallPendingConfirmationState) isToolCallState() {} +func (*ToolCallRunningState) isToolCallState() {} func (*ToolCallPendingResultConfirmationState) isToolCallState() {} -func (*ToolCallCompletedState) isToolCallState() {} -func (*ToolCallCancelledState) isToolCallState() {} +func (*ToolCallCompletedState) isToolCallState() {} +func (*ToolCallCancelledState) isToolCallState() {} // ToolCallStateUnknown carries an unrecognized ToolCallState variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type ToolCallStateUnknown struct { @@ -2690,7 +2696,7 @@ type TerminalClaim struct { // concrete variant of TerminalClaim. type isTerminalClaim interface{ isTerminalClaim() } -func (*TerminalClientClaim) isTerminalClaim() {} +func (*TerminalClientClaim) isTerminalClaim() {} func (*TerminalSessionClaim) isTerminalClaim() {} // TerminalClaimUnknown carries an unrecognized TerminalClaim variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2751,7 +2757,7 @@ type TerminalContentPart struct { type isTerminalContentPart interface{ isTerminalContentPart() } func (*TerminalUnclassifiedPart) isTerminalContentPart() {} -func (*TerminalCommandPart) isTerminalContentPart() {} +func (*TerminalCommandPart) isTerminalContentPart() {} // TerminalContentPartUnknown carries an unrecognized TerminalContentPart variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type TerminalContentPartUnknown struct { @@ -2810,11 +2816,11 @@ type SessionInputQuestion struct { // concrete variant of SessionInputQuestion. type isSessionInputQuestion interface{ isSessionInputQuestion() } -func (*SessionInputTextQuestion) isSessionInputQuestion() {} -func (*SessionInputNumberQuestion) isSessionInputQuestion() {} -func (*SessionInputBooleanQuestion) isSessionInputQuestion() {} +func (*SessionInputTextQuestion) isSessionInputQuestion() {} +func (*SessionInputNumberQuestion) isSessionInputQuestion() {} +func (*SessionInputBooleanQuestion) isSessionInputQuestion() {} func (*SessionInputSingleSelectQuestion) isSessionInputQuestion() {} -func (*SessionInputMultiSelectQuestion) isSessionInputQuestion() {} +func (*SessionInputMultiSelectQuestion) isSessionInputQuestion() {} // SessionInputQuestionUnknown carries an unrecognized SessionInputQuestion variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type SessionInputQuestionUnknown struct { @@ -2897,10 +2903,10 @@ type SessionInputAnswerValue struct { // concrete variant of SessionInputAnswerValue. type isSessionInputAnswerValue interface{ isSessionInputAnswerValue() } -func (*SessionInputTextAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputNumberAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputBooleanAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputSelectedAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputTextAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputNumberAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputBooleanAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputSelectedAnswerValue) isSessionInputAnswerValue() {} func (*SessionInputSelectedManyAnswerValue) isSessionInputAnswerValue() {} // SessionInputAnswerValueUnknown carries an unrecognized SessionInputAnswerValue variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2979,7 +2985,7 @@ type SessionInputAnswer struct { type isSessionInputAnswer interface{ isSessionInputAnswer() } func (*SessionInputAnswered) isSessionInputAnswer() {} -func (*SessionInputSkipped) isSessionInputAnswer() {} +func (*SessionInputSkipped) isSessionInputAnswer() {} // SessionInputAnswerUnknown carries an unrecognized SessionInputAnswer variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type SessionInputAnswerUnknown struct { @@ -3044,12 +3050,12 @@ type ToolResultContent struct { // concrete variant of ToolResultContent. type isToolResultContent interface{ isToolResultContent() } -func (*ToolResultTextContent) isToolResultContent() {} +func (*ToolResultTextContent) isToolResultContent() {} func (*ToolResultEmbeddedResourceContent) isToolResultContent() {} -func (*ToolResultResourceContent) isToolResultContent() {} -func (*ToolResultFileEditContent) isToolResultContent() {} -func (*ToolResultTerminalContent) isToolResultContent() {} -func (*ToolResultSubagentContent) isToolResultContent() {} +func (*ToolResultResourceContent) isToolResultContent() {} +func (*ToolResultFileEditContent) isToolResultContent() {} +func (*ToolResultTerminalContent) isToolResultContent() {} +func (*ToolResultSubagentContent) isToolResultContent() {} // ToolResultContentUnknown carries an unrecognized ToolResultContent variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type ToolResultContentUnknown struct { @@ -3132,9 +3138,9 @@ type MessageAttachment struct { // concrete variant of MessageAttachment. type isMessageAttachment interface{ isMessageAttachment() } -func (*SimpleMessageAttachment) isMessageAttachment() {} +func (*SimpleMessageAttachment) isMessageAttachment() {} func (*MessageEmbeddedResourceAttachment) isMessageAttachment() {} -func (*MessageResourceAttachment) isMessageAttachment() {} +func (*MessageResourceAttachment) isMessageAttachment() {} // MessageAttachmentUnknown carries an unrecognized MessageAttachment variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type MessageAttachmentUnknown struct { @@ -3199,7 +3205,7 @@ type Customization struct { // concrete variant of Customization. type isCustomization interface{ isCustomization() } -func (*PluginCustomization) isCustomization() {} +func (*PluginCustomization) isCustomization() {} func (*DirectoryCustomization) isCustomization() {} func (*McpServerCustomization) isCustomization() {} @@ -3266,11 +3272,11 @@ type ChildCustomization struct { // concrete variant of ChildCustomization. type isChildCustomization interface{ isChildCustomization() } -func (*AgentCustomization) isChildCustomization() {} -func (*SkillCustomization) isChildCustomization() {} -func (*PromptCustomization) isChildCustomization() {} -func (*RuleCustomization) isChildCustomization() {} -func (*HookCustomization) isChildCustomization() {} +func (*AgentCustomization) isChildCustomization() {} +func (*SkillCustomization) isChildCustomization() {} +func (*PromptCustomization) isChildCustomization() {} +func (*RuleCustomization) isChildCustomization() {} +func (*HookCustomization) isChildCustomization() {} func (*McpServerCustomization) isChildCustomization() {} // ChildCustomizationUnknown carries an unrecognized ChildCustomization variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -3354,10 +3360,10 @@ type CustomizationLoadState struct { // concrete variant of CustomizationLoadState. type isCustomizationLoadState interface{ isCustomizationLoadState() } -func (*CustomizationLoadingState) isCustomizationLoadState() {} -func (*CustomizationLoadedState) isCustomizationLoadState() {} +func (*CustomizationLoadingState) isCustomizationLoadState() {} +func (*CustomizationLoadedState) isCustomizationLoadState() {} func (*CustomizationDegradedState) isCustomizationLoadState() {} -func (*CustomizationErrorState) isCustomizationLoadState() {} +func (*CustomizationErrorState) isCustomizationLoadState() {} // CustomizationLoadStateUnknown carries an unrecognized CustomizationLoadState variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type CustomizationLoadStateUnknown struct { @@ -3428,11 +3434,11 @@ type McpServerState struct { // concrete variant of McpServerState. type isMcpServerState interface{ isMcpServerState() } -func (*McpServerStartingState) isMcpServerState() {} -func (*McpServerReadyState) isMcpServerState() {} +func (*McpServerStartingState) isMcpServerState() {} +func (*McpServerReadyState) isMcpServerState() {} func (*McpServerAuthRequiredState) isMcpServerState() {} -func (*McpServerErrorState) isMcpServerState() {} -func (*McpServerStoppedState) isMcpServerState() {} +func (*McpServerErrorState) isMcpServerState() {} +func (*McpServerStoppedState) isMcpServerState() {} // McpServerStateUnknown carries an unrecognized McpServerState variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type McpServerStateUnknown struct { @@ -3510,7 +3516,7 @@ type ToolCallContributor struct { type isToolCallContributor interface{ isToolCallContributor() } func (*ToolCallClientContributor) isToolCallContributor() {} -func (*ToolCallMcpContributor) isToolCallContributor() {} +func (*ToolCallMcpContributor) isToolCallContributor() {} // ToolCallContributorUnknown carries an unrecognized ToolCallContributor variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type ToolCallContributorUnknown struct { diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt index e796b80c..508f0e59 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt @@ -1042,9 +1042,10 @@ data class CreateCommentThreadParams( */ val resource: String, /** - * Anchored range within {@link resource}. + * Anchored range within {@link resource}. When omitted the thread is + * anchored to the entire file. */ - val range: TextRange, + val range: TextRange? = null, /** * First comment in the thread. The server assigns its {@link Comment.id}. */ @@ -1084,7 +1085,11 @@ data class UpdateCommentThreadParams( /** * New anchored range, if changing. */ - val range: TextRange? = null + val range: TextRange? = null, + /** + * New {@link CommentThread.resolved} state, if changing. + */ + val resolved: Boolean? = null ) @Serializable 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 029210ba..36134b05 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 @@ -3333,9 +3333,16 @@ data class CommentThread( */ val resource: String, /** - * Range within {@link resource} the thread is anchored to. + * Range within {@link resource} the thread is anchored to. When omitted + * the thread is anchored to the entire file. */ - val range: TextRange, + val range: TextRange? = null, + /** + * Whether the thread has been resolved. Newly created threads are always + * unresolved (`false`); a client marks a thread resolved (or re-opens it) + * through {@link UpdateCommentThreadParams | `updateCommentThread`}. + */ + val resolved: Boolean, /** * Comments in this thread, in dispatch order (oldest first). MUST * contain at least one entry. diff --git a/clients/rust/crates/ahp-types/src/commands.rs b/clients/rust/crates/ahp-types/src/commands.rs index d3abcac5..69dffd1a 100644 --- a/clients/rust/crates/ahp-types/src/commands.rs +++ b/clients/rust/crates/ahp-types/src/commands.rs @@ -1050,13 +1050,15 @@ pub struct ChangesetOperationFollowUp { pub external: Option, } -/// Create a new {@link CommentThread} anchored to a file range from a -/// specific turn. +/// Create a new {@link CommentThread} anchored to a file from a specific +/// turn, optionally narrowed to a range within that file. /// /// The initial comment is required — the protocol forbids empty threads, /// so thread creation and first-comment creation are fused into one -/// command. The server assigns both {@link CreateCommentThreadResult.threadId} -/// and {@link CreateCommentThreadResult.commentId}, then broadcasts a +/// command. The created thread always starts unresolved +/// ({@link CommentThread.resolved} is `false`). The server assigns both +/// {@link CreateCommentThreadResult.threadId} and +/// {@link CreateCommentThreadResult.commentId}, then broadcasts a /// {@link CommentsThreadSetAction} on the channel. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -1067,8 +1069,10 @@ pub struct CreateCommentThreadParams { pub turn_id: String, /// Anchored file URI. pub resource: Uri, - /// Anchored range within {@link resource}. - pub range: TextRange, + /// Anchored range within {@link resource}. When omitted the thread is + /// anchored to the entire file. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub range: Option, /// First comment in the thread. The server assigns its {@link Comment.id}. pub comment: NewComment, } @@ -1083,9 +1087,10 @@ pub struct CreateCommentThreadResult { pub comment_id: String, } -/// Re-anchor an existing {@link CommentThread} — typically used to re-pin -/// a thread to a different range or a newer turn after an edit. Comments -/// themselves are not modified by this command; use +/// Re-anchor or resolve an existing {@link CommentThread} — typically used +/// to re-pin a thread to a different range or a newer turn after an edit, +/// or to mark the thread {@link CommentThread.resolved | resolved} (or +/// re-open it). Comments themselves are not modified by this command; use /// {@link AddCommentParams | `addComment`}, /// {@link EditCommentParams | `editComment`}, or /// {@link DeleteCommentParams | `deleteComment`} for that. @@ -1108,6 +1113,9 @@ pub struct UpdateCommentThreadParams { /// New anchored range, if changing. #[serde(default, skip_serializing_if = "Option::is_none")] pub range: Option, + /// New {@link CommentThread.resolved} state, if changing. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resolved: Option, } /// Delete an entire comment thread (and every comment it contains). The diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index e58b1ab0..2d06d971 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -2858,13 +2858,14 @@ pub struct CommentsState { pub threads: Vec, } -/// A conversation anchored to a specific range in a specific file produced -/// by a specific turn. +/// A conversation anchored to a specific file produced by a specific turn, +/// optionally narrowed to a range within that file. /// /// {@link turnId} anchors the thread to the file versions that turn /// produced, so a later turn that rewrites the same file does not silently /// invalidate the comment's anchor — clients can resolve {@link resource} -/// and {@link range} against the turn's changeset. +/// and {@link range} against the turn's changeset. When {@link range} is +/// omitted the thread is anchored to the entire file. /// /// Every thread MUST contain at least one {@link Comment}. The server /// enforces this invariant: {@link CreateCommentThreadParams | @@ -2882,8 +2883,14 @@ pub struct CommentThread { pub turn_id: String, /// The file the thread is anchored to. pub resource: Uri, - /// Range within {@link resource} the thread is anchored to. - pub range: TextRange, + /// Range within {@link resource} the thread is anchored to. When omitted + /// the thread is anchored to the entire file. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub range: Option, + /// Whether the thread has been resolved. Newly created threads are always + /// unresolved (`false`); a client marks a thread resolved (or re-opens it) + /// through {@link UpdateCommentThreadParams | `updateCommentThread`}. + pub resolved: bool, /// Comments in this thread, in dispatch order (oldest first). MUST /// contain at least one entry. pub comments: Vec, diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift index 23ee33e4..fbed5ece 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift @@ -1181,8 +1181,9 @@ public struct CreateCommentThreadParams: Codable, Sendable { public var turnId: String /// Anchored file URI. public var resource: String - /// Anchored range within {@link resource}. - public var range: TextRange + /// Anchored range within {@link resource}. When omitted the thread is + /// anchored to the entire file. + public var range: TextRange? /// First comment in the thread. The server assigns its {@link Comment.id}. public var comment: NewComment @@ -1190,7 +1191,7 @@ public struct CreateCommentThreadParams: Codable, Sendable { channel: String, turnId: String, resource: String, - range: TextRange, + range: TextRange? = nil, comment: NewComment ) { self.channel = channel @@ -1227,19 +1228,23 @@ public struct UpdateCommentThreadParams: Codable, Sendable { public var resource: String? /// New anchored range, if changing. public var range: TextRange? + /// New {@link CommentThread.resolved} state, if changing. + public var resolved: Bool? public init( channel: String, threadId: String, turnId: String? = nil, resource: String? = nil, - range: TextRange? = nil + range: TextRange? = nil, + resolved: Bool? = nil ) { self.channel = channel self.threadId = threadId self.turnId = turnId self.resource = resource self.range = range + self.resolved = resolved } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 68c64a55..9eff816e 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -3637,8 +3637,13 @@ public struct CommentThread: Codable, Sendable { public var turnId: String /// The file the thread is anchored to. public var resource: String - /// Range within {@link resource} the thread is anchored to. - public var range: TextRange + /// Range within {@link resource} the thread is anchored to. When omitted + /// the thread is anchored to the entire file. + public var range: TextRange? + /// Whether the thread has been resolved. Newly created threads are always + /// unresolved (`false`); a client marks a thread resolved (or re-opens it) + /// through {@link UpdateCommentThreadParams | `updateCommentThread`}. + public var resolved: Bool /// Comments in this thread, in dispatch order (oldest first). MUST /// contain at least one entry. public var comments: [Comment] @@ -3651,6 +3656,7 @@ public struct CommentThread: Codable, Sendable { case turnId case resource case range + case resolved case comments case meta = "_meta" } @@ -3659,7 +3665,8 @@ public struct CommentThread: Codable, Sendable { id: String, turnId: String, resource: String, - range: TextRange, + range: TextRange? = nil, + resolved: Bool, comments: [Comment], meta: [String: AnyCodable]? = nil ) { @@ -3667,6 +3674,7 @@ public struct CommentThread: Codable, Sendable { self.turnId = turnId self.resource = resource self.range = range + self.resolved = resolved self.comments = comments self.meta = meta } diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 19503a90..3615330e 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -5349,7 +5349,7 @@ }, "CommentThread": { "type": "object", - "description": "A conversation anchored to a specific range in a specific file produced\nby a specific turn.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the thread is anchored to the entire file.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", "properties": { "id": { "type": "string", @@ -5365,7 +5365,11 @@ }, "range": { "$ref": "#/$defs/TextRange", - "description": "Range within {@link resource} the thread is anchored to." + "description": "Range within {@link resource} the thread is anchored to. When omitted\nthe thread is anchored to the entire file." + }, + "resolved": { + "type": "boolean", + "description": "Whether the thread has been resolved. Newly created threads are always\nunresolved (`false`); a client marks a thread resolved (or re-opens it)\nthrough {@link UpdateCommentThreadParams | `updateCommentThread`}." }, "comments": { "type": "array", @@ -5384,7 +5388,7 @@ "id", "turnId", "resource", - "range", + "resolved", "comments" ] }, diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 58446599..a3599990 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -1118,7 +1118,7 @@ }, "CreateCommentThreadParams": { "type": "object", - "description": "Create a new {@link CommentThread} anchored to a file range from a\nspecific turn.\n\nThe initial comment is required — the protocol forbids empty threads,\nso thread creation and first-comment creation are fused into one\ncommand. The server assigns both {@link CreateCommentThreadResult.threadId}\nand {@link CreateCommentThreadResult.commentId}, then broadcasts a\n{@link CommentsThreadSetAction} on the channel.", + "description": "Create a new {@link CommentThread} anchored to a file from a specific\nturn, optionally narrowed to a range within that file.\n\nThe initial comment is required — the protocol forbids empty threads,\nso thread creation and first-comment creation are fused into one\ncommand. The created thread always starts unresolved\n({@link CommentThread.resolved} is `false`). The server assigns both\n{@link CreateCommentThreadResult.threadId} and\n{@link CreateCommentThreadResult.commentId}, then broadcasts a\n{@link CommentsThreadSetAction} on the channel.", "properties": { "channel": { "$ref": "#/$defs/URI", @@ -1134,7 +1134,7 @@ }, "range": { "$ref": "#/$defs/TextRange", - "description": "Anchored range within {@link resource}." + "description": "Anchored range within {@link resource}. When omitted the thread is\nanchored to the entire file." }, "comment": { "$ref": "#/$defs/NewComment", @@ -1145,7 +1145,6 @@ "channel", "turnId", "resource", - "range", "comment" ] }, @@ -1169,7 +1168,7 @@ }, "UpdateCommentThreadParams": { "type": "object", - "description": "Re-anchor an existing {@link CommentThread} — typically used to re-pin\na thread to a different range or a newer turn after an edit. Comments\nthemselves are not modified by this command; use\n{@link AddCommentParams | `addComment`},\n{@link EditCommentParams | `editComment`}, or\n{@link DeleteCommentParams | `deleteComment`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting thread state as a {@link CommentsThreadSetAction}.", + "description": "Re-anchor or resolve an existing {@link CommentThread} — typically used\nto re-pin a thread to a different range or a newer turn after an edit,\nor to mark the thread {@link CommentThread.resolved | resolved} (or\nre-open it). Comments themselves are not modified by this command; use\n{@link AddCommentParams | `addComment`},\n{@link EditCommentParams | `editComment`}, or\n{@link DeleteCommentParams | `deleteComment`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting thread state as a {@link CommentsThreadSetAction}.", "properties": { "channel": { "$ref": "#/$defs/URI", @@ -1190,6 +1189,10 @@ "range": { "$ref": "#/$defs/TextRange", "description": "New anchored range, if changing." + }, + "resolved": { + "type": "boolean", + "description": "New {@link CommentThread.resolved} state, if changing." } }, "required": [ @@ -5085,7 +5088,7 @@ }, "CommentThread": { "type": "object", - "description": "A conversation anchored to a specific range in a specific file produced\nby a specific turn.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the thread is anchored to the entire file.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", "properties": { "id": { "type": "string", @@ -5101,7 +5104,11 @@ }, "range": { "$ref": "#/$defs/TextRange", - "description": "Range within {@link resource} the thread is anchored to." + "description": "Range within {@link resource} the thread is anchored to. When omitted\nthe thread is anchored to the entire file." + }, + "resolved": { + "type": "boolean", + "description": "Whether the thread has been resolved. Newly created threads are always\nunresolved (`false`); a client marks a thread resolved (or re-opens it)\nthrough {@link UpdateCommentThreadParams | `updateCommentThread`}." }, "comments": { "type": "array", @@ -5120,7 +5127,7 @@ "id", "turnId", "resource", - "range", + "resolved", "comments" ] }, diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 59e6cd49..3d9d415f 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -3816,7 +3816,7 @@ }, "CommentThread": { "type": "object", - "description": "A conversation anchored to a specific range in a specific file produced\nby a specific turn.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the thread is anchored to the entire file.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", "properties": { "id": { "type": "string", @@ -3832,7 +3832,11 @@ }, "range": { "$ref": "#/$defs/TextRange", - "description": "Range within {@link resource} the thread is anchored to." + "description": "Range within {@link resource} the thread is anchored to. When omitted\nthe thread is anchored to the entire file." + }, + "resolved": { + "type": "boolean", + "description": "Whether the thread has been resolved. Newly created threads are always\nunresolved (`false`); a client marks a thread resolved (or re-opens it)\nthrough {@link UpdateCommentThreadParams | `updateCommentThread`}." }, "comments": { "type": "array", @@ -3851,7 +3855,7 @@ "id", "turnId", "resource", - "range", + "resolved", "comments" ] }, @@ -5087,7 +5091,7 @@ }, "CreateCommentThreadParams": { "type": "object", - "description": "Create a new {@link CommentThread} anchored to a file range from a\nspecific turn.\n\nThe initial comment is required — the protocol forbids empty threads,\nso thread creation and first-comment creation are fused into one\ncommand. The server assigns both {@link CreateCommentThreadResult.threadId}\nand {@link CreateCommentThreadResult.commentId}, then broadcasts a\n{@link CommentsThreadSetAction} on the channel.", + "description": "Create a new {@link CommentThread} anchored to a file from a specific\nturn, optionally narrowed to a range within that file.\n\nThe initial comment is required — the protocol forbids empty threads,\nso thread creation and first-comment creation are fused into one\ncommand. The created thread always starts unresolved\n({@link CommentThread.resolved} is `false`). The server assigns both\n{@link CreateCommentThreadResult.threadId} and\n{@link CreateCommentThreadResult.commentId}, then broadcasts a\n{@link CommentsThreadSetAction} on the channel.", "properties": { "channel": { "$ref": "#/$defs/URI", @@ -5103,7 +5107,7 @@ }, "range": { "$ref": "#/$defs/TextRange", - "description": "Anchored range within {@link resource}." + "description": "Anchored range within {@link resource}. When omitted the thread is\nanchored to the entire file." }, "comment": { "$ref": "#/$defs/NewComment", @@ -5114,7 +5118,6 @@ "channel", "turnId", "resource", - "range", "comment" ] }, @@ -5138,7 +5141,7 @@ }, "UpdateCommentThreadParams": { "type": "object", - "description": "Re-anchor an existing {@link CommentThread} — typically used to re-pin\na thread to a different range or a newer turn after an edit. Comments\nthemselves are not modified by this command; use\n{@link AddCommentParams | `addComment`},\n{@link EditCommentParams | `editComment`}, or\n{@link DeleteCommentParams | `deleteComment`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting thread state as a {@link CommentsThreadSetAction}.", + "description": "Re-anchor or resolve an existing {@link CommentThread} — typically used\nto re-pin a thread to a different range or a newer turn after an edit,\nor to mark the thread {@link CommentThread.resolved | resolved} (or\nre-open it). Comments themselves are not modified by this command; use\n{@link AddCommentParams | `addComment`},\n{@link EditCommentParams | `editComment`}, or\n{@link DeleteCommentParams | `deleteComment`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting thread state as a {@link CommentsThreadSetAction}.", "properties": { "channel": { "$ref": "#/$defs/URI", @@ -5159,6 +5162,10 @@ "range": { "$ref": "#/$defs/TextRange", "description": "New anchored range, if changing." + }, + "resolved": { + "type": "boolean", + "description": "New {@link CommentThread.resolved} state, if changing." } }, "required": [ diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 9ffde376..9dface86 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -3945,7 +3945,7 @@ }, "CommentThread": { "type": "object", - "description": "A conversation anchored to a specific range in a specific file produced\nby a specific turn.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the thread is anchored to the entire file.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", "properties": { "id": { "type": "string", @@ -3961,7 +3961,11 @@ }, "range": { "$ref": "#/$defs/TextRange", - "description": "Range within {@link resource} the thread is anchored to." + "description": "Range within {@link resource} the thread is anchored to. When omitted\nthe thread is anchored to the entire file." + }, + "resolved": { + "type": "boolean", + "description": "Whether the thread has been resolved. Newly created threads are always\nunresolved (`false`); a client marks a thread resolved (or re-opens it)\nthrough {@link UpdateCommentThreadParams | `updateCommentThread`}." }, "comments": { "type": "array", @@ -3980,7 +3984,7 @@ "id", "turnId", "resource", - "range", + "resolved", "comments" ] }, diff --git a/schema/state.schema.json b/schema/state.schema.json index adaa6342..de1a5c99 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -3727,7 +3727,7 @@ }, "CommentThread": { "type": "object", - "description": "A conversation anchored to a specific range in a specific file produced\nby a specific turn.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the thread is anchored to the entire file.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", "properties": { "id": { "type": "string", @@ -3743,7 +3743,11 @@ }, "range": { "$ref": "#/$defs/TextRange", - "description": "Range within {@link resource} the thread is anchored to." + "description": "Range within {@link resource} the thread is anchored to. When omitted\nthe thread is anchored to the entire file." + }, + "resolved": { + "type": "boolean", + "description": "Whether the thread has been resolved. Newly created threads are always\nunresolved (`false`); a client marks a thread resolved (or re-opens it)\nthrough {@link UpdateCommentThreadParams | `updateCommentThread`}." }, "comments": { "type": "array", @@ -3762,7 +3766,7 @@ "id", "turnId", "resource", - "range", + "resolved", "comments" ] }, diff --git a/types/channels-comments/commands.ts b/types/channels-comments/commands.ts index d4c6035c..9d7405fe 100644 --- a/types/channels-comments/commands.ts +++ b/types/channels-comments/commands.ts @@ -19,13 +19,15 @@ import type { NewComment } from './state.js'; // ─── createCommentThread ───────────────────────────────────────────────────── /** - * Create a new {@link CommentThread} anchored to a file range from a - * specific turn. + * Create a new {@link CommentThread} anchored to a file from a specific + * turn, optionally narrowed to a range within that file. * * The initial comment is required — the protocol forbids empty threads, * so thread creation and first-comment creation are fused into one - * command. The server assigns both {@link CreateCommentThreadResult.threadId} - * and {@link CreateCommentThreadResult.commentId}, then broadcasts a + * command. The created thread always starts unresolved + * ({@link CommentThread.resolved} is `false`). The server assigns both + * {@link CreateCommentThreadResult.threadId} and + * {@link CreateCommentThreadResult.commentId}, then broadcasts a * {@link CommentsThreadSetAction} on the channel. * * @category Commands @@ -41,8 +43,11 @@ export interface CreateCommentThreadParams extends BaseParams { turnId: string; /** Anchored file URI. */ resource: URI; - /** Anchored range within {@link resource}. */ - range: TextRange; + /** + * Anchored range within {@link resource}. When omitted the thread is + * anchored to the entire file. + */ + range?: TextRange; /** First comment in the thread. The server assigns its {@link Comment.id}. */ comment: NewComment; } @@ -62,9 +67,10 @@ export interface CreateCommentThreadResult { // ─── updateCommentThread ───────────────────────────────────────────────────── /** - * Re-anchor an existing {@link CommentThread} — typically used to re-pin - * a thread to a different range or a newer turn after an edit. Comments - * themselves are not modified by this command; use + * Re-anchor or resolve an existing {@link CommentThread} — typically used + * to re-pin a thread to a different range or a newer turn after an edit, + * or to mark the thread {@link CommentThread.resolved | resolved} (or + * re-open it). Comments themselves are not modified by this command; use * {@link AddCommentParams | `addComment`}, * {@link EditCommentParams | `editComment`}, or * {@link DeleteCommentParams | `deleteComment`} for that. @@ -89,6 +95,8 @@ export interface UpdateCommentThreadParams extends BaseParams { resource?: URI; /** New anchored range, if changing. */ range?: TextRange; + /** New {@link CommentThread.resolved} state, if changing. */ + resolved?: boolean; } // ─── deleteCommentThread ───────────────────────────────────────────────────── diff --git a/types/channels-comments/state.ts b/types/channels-comments/state.ts index 70020696..89c2662a 100644 --- a/types/channels-comments/state.ts +++ b/types/channels-comments/state.ts @@ -50,13 +50,14 @@ export interface CommentsState { // ─── Comment Thread ────────────────────────────────────────────────────────── /** - * A conversation anchored to a specific range in a specific file produced - * by a specific turn. + * A conversation anchored to a specific file produced by a specific turn, + * optionally narrowed to a range within that file. * * {@link turnId} anchors the thread to the file versions that turn * produced, so a later turn that rewrites the same file does not silently * invalidate the comment's anchor — clients can resolve {@link resource} - * and {@link range} against the turn's changeset. + * and {@link range} against the turn's changeset. When {@link range} is + * omitted the thread is anchored to the entire file. * * Every thread MUST contain at least one {@link Comment}. The server * enforces this invariant: {@link CreateCommentThreadParams | @@ -77,8 +78,17 @@ export interface CommentThread { turnId: string; /** The file the thread is anchored to. */ resource: URI; - /** Range within {@link resource} the thread is anchored to. */ - range: TextRange; + /** + * Range within {@link resource} the thread is anchored to. When omitted + * the thread is anchored to the entire file. + */ + range?: TextRange; + /** + * Whether the thread has been resolved. Newly created threads are always + * unresolved (`false`); a client marks a thread resolved (or re-opens it) + * through {@link UpdateCommentThreadParams | `updateCommentThread`}. + */ + resolved: boolean; /** * Comments in this thread, in dispatch order (oldest first). MUST * contain at least one entry. diff --git a/types/test-cases/reducers/210-comments-threadset-appends-new-thread.json b/types/test-cases/reducers/210-comments-threadset-appends-new-thread.json index d67ccbcd..62330cf1 100644 --- a/types/test-cases/reducers/210-comments-threadset-appends-new-thread.json +++ b/types/test-cases/reducers/210-comments-threadset-appends-new-thread.json @@ -11,6 +11,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", @@ -27,10 +28,7 @@ "id": "t-2", "turnId": "turn-2", "resource": "file:///src/b.ts", - "range": { - "start": { "line": 10, "character": 0 }, - "end": { "line": 12, "character": 0 } - }, + "resolved": false, "comments": [ { "id": "c-2", @@ -50,6 +48,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", @@ -61,10 +60,7 @@ "id": "t-2", "turnId": "turn-2", "resource": "file:///src/b.ts", - "range": { - "start": { "line": 10, "character": 0 }, - "end": { "line": 12, "character": 0 } - }, + "resolved": false, "comments": [ { "id": "c-2", diff --git a/types/test-cases/reducers/211-comments-threadset-replaces-existing-thread.json b/types/test-cases/reducers/211-comments-threadset-replaces-existing-thread.json index 7a3a3e2b..c6e2d7c9 100644 --- a/types/test-cases/reducers/211-comments-threadset-replaces-existing-thread.json +++ b/types/test-cases/reducers/211-comments-threadset-replaces-existing-thread.json @@ -1,5 +1,5 @@ { - "description": "comments/threadSet replaces an existing thread when the id matches", + "description": "comments/threadSet replaces an existing thread when the id matches, dropping the range to anchor to the whole file and marking it resolved", "reducer": "comments", "initial": { "threads": [ @@ -11,6 +11,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "original" } ] @@ -24,10 +25,7 @@ "id": "t-1", "turnId": "turn-2", "resource": "file:///src/a.ts", - "range": { - "start": { "line": 5, "character": 0 }, - "end": { "line": 5, "character": 10 } - }, + "resolved": true, "comments": [ { "id": "c-1", "text": "rewritten" }, { "id": "c-2", "text": "reply" } @@ -41,10 +39,7 @@ "id": "t-1", "turnId": "turn-2", "resource": "file:///src/a.ts", - "range": { - "start": { "line": 5, "character": 0 }, - "end": { "line": 5, "character": 10 } - }, + "resolved": true, "comments": [ { "id": "c-1", "text": "rewritten" }, { "id": "c-2", "text": "reply" } diff --git a/types/test-cases/reducers/212-comments-threadremoved-drops-matching-thread.json b/types/test-cases/reducers/212-comments-threadremoved-drops-matching-thread.json index e92a2ab7..05bb0700 100644 --- a/types/test-cases/reducers/212-comments-threadremoved-drops-matching-thread.json +++ b/types/test-cases/reducers/212-comments-threadremoved-drops-matching-thread.json @@ -11,6 +11,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "keep" } ] @@ -23,6 +24,7 @@ "start": { "line": 2, "character": 0 }, "end": { "line": 2, "character": 4 } }, + "resolved": false, "comments": [ { "id": "c-2", "text": "drop me" } ] @@ -43,6 +45,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "keep" } ] diff --git a/types/test-cases/reducers/213-comments-commentset-appends-and-replaces.json b/types/test-cases/reducers/213-comments-commentset-appends-and-replaces.json index fb95d787..757cd553 100644 --- a/types/test-cases/reducers/213-comments-commentset-appends-and-replaces.json +++ b/types/test-cases/reducers/213-comments-commentset-appends-and-replaces.json @@ -11,6 +11,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "original" } ] @@ -39,6 +40,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "edited" }, { "id": "c-2", "text": "second" } diff --git a/types/test-cases/reducers/214-comments-commentset-unknown-thread-is-no-op.json b/types/test-cases/reducers/214-comments-commentset-unknown-thread-is-no-op.json index cf876e68..5bf3745b 100644 --- a/types/test-cases/reducers/214-comments-commentset-unknown-thread-is-no-op.json +++ b/types/test-cases/reducers/214-comments-commentset-unknown-thread-is-no-op.json @@ -11,6 +11,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "only" } ] @@ -34,6 +35,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "only" } ] diff --git a/types/test-cases/reducers/215-comments-commentremoved-drops-matching-comment.json b/types/test-cases/reducers/215-comments-commentremoved-drops-matching-comment.json index 43d0adea..69a8b08e 100644 --- a/types/test-cases/reducers/215-comments-commentremoved-drops-matching-comment.json +++ b/types/test-cases/reducers/215-comments-commentremoved-drops-matching-comment.json @@ -11,6 +11,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "first" }, { "id": "c-2", "text": "second" }, @@ -34,6 +35,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "first" }, { "id": "c-3", "text": "third" } diff --git a/types/test-cases/reducers/216-comments-cleared-empties-threads.json b/types/test-cases/reducers/216-comments-cleared-empties-threads.json index 5f7747ba..c78d2118 100644 --- a/types/test-cases/reducers/216-comments-cleared-empties-threads.json +++ b/types/test-cases/reducers/216-comments-cleared-empties-threads.json @@ -11,6 +11,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "first" } ] diff --git a/types/test-cases/reducers/217-comments-unknown-action-type-is-no-op.json b/types/test-cases/reducers/217-comments-unknown-action-type-is-no-op.json index 3ccad602..e2f67e72 100644 --- a/types/test-cases/reducers/217-comments-unknown-action-type-is-no-op.json +++ b/types/test-cases/reducers/217-comments-unknown-action-type-is-no-op.json @@ -11,6 +11,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "only" } ] @@ -30,6 +31,7 @@ "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 5 } }, + "resolved": false, "comments": [ { "id": "c-1", "text": "only" } ] From d789188c6f5fd0d29b9734a78bd9d4d8a209b86a Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 8 Jun 2026 11:04:04 +0200 Subject: [PATCH 08/11] comments attachement --- CHANGELOG.md | 4 ++ clients/go/CHANGELOG.md | 3 + clients/go/ahptypes/state.generated.go | 50 ++++++++++++++ clients/kotlin/CHANGELOG.md | 3 + .../generated/State.generated.kt | 62 ++++++++++++++++- clients/rust/CHANGELOG.md | 3 + clients/rust/crates/ahp-types/src/state.rs | 50 ++++++++++++++ .../Generated/State.generated.swift | 69 +++++++++++++++++++ clients/swift/CHANGELOG.md | 3 + clients/typescript/CHANGELOG.md | 3 + schema/actions.schema.json | 46 +++++++++++++ schema/commands.schema.json | 43 ++++++++++++ schema/errors.schema.json | 43 ++++++++++++ schema/notifications.schema.json | 43 ++++++++++++ schema/state.schema.json | 46 +++++++++++++ scripts/generate-go.ts | 2 + scripts/generate-kotlin.ts | 2 + scripts/generate-rust.ts | 2 + scripts/generate-swift.ts | 2 + types/channels-session/state.ts | 30 +++++++- types/index.ts | 1 + 21 files changed, 508 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e19b9cc..eb250bf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,10 @@ Spec version: `0.3.0` and `comments/cleared` actions. `SessionSummary.comments` advertises the per-session `CommentsSummary` (`{ resource, threadCount, commentCount }`) for badge UI. +- Added a `comments` `MessageAttachment` variant + (`MessageCommentsAttachment`) that references comment threads on a + session's comments channel by its `resource` URI, optionally narrowed to + a `threadIds` array (omitted to reference every thread). - 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`. diff --git a/clients/go/CHANGELOG.md b/clients/go/CHANGELOG.md index 9fffb4ff..1546758c 100644 --- a/clients/go/CHANGELOG.md +++ b/clients/go/CHANGELOG.md @@ -49,6 +49,9 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file. `EditCommentParams`, `DeleteCommentParams` command structs; `ApplyActionToComments` (stub mirroring `ApplyActionToChangeset`); and `SnapshotState.Comments`. +- `MessageCommentsAttachment` (`comments` `MessageAttachment` variant) + referencing comment threads on a session's comments channel by `Resource` + URI, optionally narrowed to a `ThreadIds` array. ### Changed diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 02df594a..4c6fa00a 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -131,6 +131,8 @@ const ( MessageAttachmentKindEmbeddedResource MessageAttachmentKind = "embeddedResource" // An attachment that references a resource by URI. MessageAttachmentKindResource MessageAttachmentKind = "resource" + // An attachment that references comment threads on a comments channel. + MessageAttachmentKindComments MessageAttachmentKind = "comments" ) // Discriminant for response part types. @@ -1141,6 +1143,47 @@ type MessageResourceAttachment struct { Selection *TextSelection `json:"selection,omitempty"` } +// An attachment that references comment threads on a session's comments +// channel (see {@link CommentsState}). +// +// When {@link threadIds} is omitted the attachment references every thread +// on the channel; when present it references only the listed +// {@link CommentThread.id | thread ids}. +type MessageCommentsAttachment struct { + // A human-readable label for the attachment (e.g. the filename of a file + // attachment). Used for display in UI. + Label string `json:"label"` + // If defined, the range in {@link Message.text} that references this + // attachment. This is a text range, not a byte range. + Range *TextRange `json:"range,omitempty"` + // Advisory display hint for clients rendering this attachment. Recognized + // values include: + // + // - `'image'`: the attachment is an image + // - `'document'`: the attachment is a textual document + // - `'symbol'`: the attachment is a code symbol (e.g. a function or class) + // - `'directory'`: the attachment is a folder + // - `'selection'`: the attachment is a selection within a document + // + // Implementations MAY provide additional values; clients SHOULD fall back + // to a reasonable default when an unknown value is encountered. + DisplayKind *string `json:"displayKind,omitempty"` + // Additional implementation-defined metadata for the attachment. + // + // If the attachment was produced by the `completions` command, the client + // MUST preserve every property of `_meta` originally returned by the agent + // host when sending the user message containing the accepted completion. + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + // Discriminant + Type MessageAttachmentKind `json:"type"` + // The comments channel URI (typically `ahp-session://comments`). + // Matches {@link CommentsSummary.resource}. + Resource URI `json:"resource"` + // Specific {@link CommentThread.id | thread ids} to reference. When + // omitted, the attachment references all threads on the channel. + ThreadIds []string `json:"threadIds,omitempty"` +} + type MarkdownResponsePart struct { // Discriminant Kind ResponsePartKind `json:"kind"` @@ -3141,6 +3184,7 @@ type isMessageAttachment interface{ isMessageAttachment() } func (*SimpleMessageAttachment) isMessageAttachment() {} func (*MessageEmbeddedResourceAttachment) isMessageAttachment() {} func (*MessageResourceAttachment) isMessageAttachment() {} +func (*MessageCommentsAttachment) isMessageAttachment() {} // MessageAttachmentUnknown carries an unrecognized MessageAttachment variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type MessageAttachmentUnknown struct { @@ -3174,6 +3218,12 @@ func (u *MessageAttachment) UnmarshalJSON(data []byte) error { return err } u.Value = &value + case "comments": + var value MessageCommentsAttachment + if err := json.Unmarshal(data, &value); err != nil { + return err + } + u.Value = &value default: raw := make(json.RawMessage, len(data)) copy(raw, data) diff --git a/clients/kotlin/CHANGELOG.md b/clients/kotlin/CHANGELOG.md index d5f697cd..9dbcfa27 100644 --- a/clients/kotlin/CHANGELOG.md +++ b/clients/kotlin/CHANGELOG.md @@ -50,6 +50,9 @@ versions (`*-SNAPSHOT`) are explicitly rejected by the publish pipeline; bump `AhpClientRequests`; and `SnapshotState.Comments`. `SessionSummary.comments` surfaces the per-session `CommentsSummary`. `SessionSummary.comments` surfaces the per-session `CommentsSummary`. +- `MessageCommentsAttachment` (`comments` `MessageAttachment` variant) + referencing comment threads on a session's comments channel by `resource` + URI, optionally narrowed to a `threadIds` array. ### Changed 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 36134b05..1a7c77d8 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 @@ -273,7 +273,12 @@ enum class MessageAttachmentKind { * An attachment that references a resource by URI. */ @SerialName("resource") - RESOURCE + RESOURCE, + /** + * An attachment that references comment threads on a comments channel. + */ + @SerialName("comments") + COMMENTS } /** @@ -1684,6 +1689,57 @@ data class MessageResourceAttachment( val selection: TextSelection? = null ) +@Serializable +data class MessageCommentsAttachment( + /** + * A human-readable label for the attachment (e.g. the filename of a file + * attachment). Used for display in UI. + */ + val label: String, + /** + * If defined, the range in {@link Message.text} that references this + * attachment. This is a text range, not a byte range. + */ + val range: TextRange? = null, + /** + * Advisory display hint for clients rendering this attachment. Recognized + * values include: + * + * - `'image'`: the attachment is an image + * - `'document'`: the attachment is a textual document + * - `'symbol'`: the attachment is a code symbol (e.g. a function or class) + * - `'directory'`: the attachment is a folder + * - `'selection'`: the attachment is a selection within a document + * + * Implementations MAY provide additional values; clients SHOULD fall back + * to a reasonable default when an unknown value is encountered. + */ + val displayKind: String? = null, + /** + * Additional implementation-defined metadata for the attachment. + * + * If the attachment was produced by the `completions` command, the client + * MUST preserve every property of `_meta` originally returned by the agent + * host when sending the user message containing the accepted completion. + */ + @SerialName("_meta") + val meta: Map? = null, + /** + * Discriminant + */ + val type: MessageAttachmentKind, + /** + * The comments channel URI (typically `ahp-session://comments`). + * Matches {@link CommentsSummary.resource}. + */ + val resource: String, + /** + * Specific {@link CommentThread.id | thread ids} to reference. When + * omitted, the attachment references all threads on the channel. + */ + val threadIds: List? = null +) + @Serializable data class MarkdownResponsePart( /** @@ -3873,6 +3929,8 @@ value class MessageAttachmentSimple(val value: SimpleMessageAttachment) : Messag value class MessageAttachmentEmbeddedResource(val value: MessageEmbeddedResourceAttachment) : MessageAttachment @JvmInline value class MessageAttachmentResource(val value: MessageResourceAttachment) : MessageAttachment +@JvmInline +value class MessageAttachmentComments(val value: MessageCommentsAttachment) : MessageAttachment /** * Forward-compat catch-all for unknown MessageAttachment discriminators. * @@ -3900,6 +3958,7 @@ internal object MessageAttachmentSerializer : KSerializer { "simple" -> MessageAttachmentSimple(input.json.decodeFromJsonElement(SimpleMessageAttachment.serializer(), element)) "embeddedResource" -> MessageAttachmentEmbeddedResource(input.json.decodeFromJsonElement(MessageEmbeddedResourceAttachment.serializer(), element)) "resource" -> MessageAttachmentResource(input.json.decodeFromJsonElement(MessageResourceAttachment.serializer(), element)) + "comments" -> MessageAttachmentComments(input.json.decodeFromJsonElement(MessageCommentsAttachment.serializer(), element)) else -> MessageAttachmentUnknown(obj) } } @@ -3911,6 +3970,7 @@ internal object MessageAttachmentSerializer : KSerializer { is MessageAttachmentSimple -> output.json.encodeToJsonElement(SimpleMessageAttachment.serializer(), value.value) is MessageAttachmentEmbeddedResource -> output.json.encodeToJsonElement(MessageEmbeddedResourceAttachment.serializer(), value.value) is MessageAttachmentResource -> output.json.encodeToJsonElement(MessageResourceAttachment.serializer(), value.value) + is MessageAttachmentComments -> output.json.encodeToJsonElement(MessageCommentsAttachment.serializer(), value.value) is MessageAttachmentUnknown -> value.raw } output.encodeJsonElement(element) diff --git a/clients/rust/CHANGELOG.md b/clients/rust/CHANGELOG.md index c469733a..e5038b7a 100644 --- a/clients/rust/CHANGELOG.md +++ b/clients/rust/CHANGELOG.md @@ -49,6 +49,9 @@ matching `## [X.Y.Z]` heading is missing from this file. `addComment`, `editComment`, and `deleteComment` command structs; `MultiHostStateMirror.comments()` and `SnapshotState::Comments`. Reducer logic is deferred (matches the changeset stub). +- `MessageCommentsAttachment` (`comments` `MessageAttachment` variant) + referencing comment threads on a session's comments channel by `resource` + URI, optionally narrowed to a `threadIds` array. ### Changed diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 2d06d971..081d9824 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -145,6 +145,9 @@ pub enum MessageAttachmentKind { /// An attachment that references a resource by URI. #[serde(rename = "resource")] Resource, + /// An attachment that references comment threads on a comments channel. + #[serde(rename = "comments")] + Comments, } /// Discriminant for response part types. @@ -1425,6 +1428,51 @@ pub struct MessageResourceAttachment { pub selection: Option, } +/// An attachment that references comment threads on a session's comments +/// channel (see {@link CommentsState}). +/// +/// When {@link threadIds} is omitted the attachment references every thread +/// on the channel; when present it references only the listed +/// {@link CommentThread.id | thread ids}. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MessageCommentsAttachment { + /// A human-readable label for the attachment (e.g. the filename of a file + /// attachment). Used for display in UI. + pub label: String, + /// If defined, the range in {@link Message.text} that references this + /// attachment. This is a text range, not a byte range. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub range: Option, + /// Advisory display hint for clients rendering this attachment. Recognized + /// values include: + /// + /// - `'image'`: the attachment is an image + /// - `'document'`: the attachment is a textual document + /// - `'symbol'`: the attachment is a code symbol (e.g. a function or class) + /// - `'directory'`: the attachment is a folder + /// - `'selection'`: the attachment is a selection within a document + /// + /// Implementations MAY provide additional values; clients SHOULD fall back + /// to a reasonable default when an unknown value is encountered. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub display_kind: Option, + /// Additional implementation-defined metadata for the attachment. + /// + /// If the attachment was produced by the `completions` command, the client + /// MUST preserve every property of `_meta` originally returned by the agent + /// host when sending the user message containing the accepted completion. + #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] + pub meta: Option, + /// The comments channel URI (typically `ahp-session://comments`). + /// Matches {@link CommentsSummary.resource}. + pub resource: Uri, + /// Specific {@link CommentThread.id | thread ids} to reference. When + /// omitted, the attachment references all threads on the channel. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub thread_ids: Option>, +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MarkdownResponsePart { @@ -3183,6 +3231,8 @@ pub enum MessageAttachment { EmbeddedResource(MessageEmbeddedResourceAttachment), #[serde(rename = "resource")] Resource(MessageResourceAttachment), + #[serde(rename = "comments")] + Comments(MessageCommentsAttachment), /// Unknown or future variant — preserved as raw JSON for round-trip fidelity. /// Reducers treat this as a no-op. #[serde(untagged)] diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 9eff816e..bf6b3277 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -133,6 +133,8 @@ public enum MessageAttachmentKind: String, Codable, Sendable { case embeddedResource = "embeddedResource" /// An attachment that references a resource by URI. case resource = "resource" + /// An attachment that references comment threads on a comments channel. + case comments = "comments" } /// Discriminant for response part types. @@ -1632,6 +1634,69 @@ public struct MessageResourceAttachment: Codable, Sendable { } } +public struct MessageCommentsAttachment: Codable, Sendable { + /// A human-readable label for the attachment (e.g. the filename of a file + /// attachment). Used for display in UI. + public var label: String + /// If defined, the range in {@link Message.text} that references this + /// attachment. This is a text range, not a byte range. + public var range: TextRange? + /// Advisory display hint for clients rendering this attachment. Recognized + /// values include: + /// + /// - `'image'`: the attachment is an image + /// - `'document'`: the attachment is a textual document + /// - `'symbol'`: the attachment is a code symbol (e.g. a function or class) + /// - `'directory'`: the attachment is a folder + /// - `'selection'`: the attachment is a selection within a document + /// + /// Implementations MAY provide additional values; clients SHOULD fall back + /// to a reasonable default when an unknown value is encountered. + public var displayKind: String? + /// Additional implementation-defined metadata for the attachment. + /// + /// If the attachment was produced by the `completions` command, the client + /// MUST preserve every property of `_meta` originally returned by the agent + /// host when sending the user message containing the accepted completion. + public var meta: [String: AnyCodable]? + /// Discriminant + public var type: MessageAttachmentKind + /// The comments channel URI (typically `ahp-session://comments`). + /// Matches {@link CommentsSummary.resource}. + public var resource: String + /// Specific {@link CommentThread.id | thread ids} to reference. When + /// omitted, the attachment references all threads on the channel. + public var threadIds: [String]? + + enum CodingKeys: String, CodingKey { + case label + case range + case displayKind + case meta = "_meta" + case type + case resource + case threadIds + } + + public init( + label: String, + range: TextRange? = nil, + displayKind: String? = nil, + meta: [String: AnyCodable]? = nil, + type: MessageAttachmentKind, + resource: String, + threadIds: [String]? = nil + ) { + self.label = label + self.range = range + self.displayKind = displayKind + self.meta = meta + self.type = type + self.resource = resource + self.threadIds = threadIds + } +} + public struct MarkdownResponsePart: Codable, Sendable { /// Discriminant public var kind: ResponsePartKind @@ -4082,6 +4147,7 @@ public enum MessageAttachment: Codable, Sendable { case simple(SimpleMessageAttachment) case embeddedResource(MessageEmbeddedResourceAttachment) case resource(MessageResourceAttachment) + case comments(MessageCommentsAttachment) private enum DiscriminantKey: String, CodingKey { case discriminant = "type" @@ -4097,6 +4163,8 @@ public enum MessageAttachment: Codable, Sendable { self = .embeddedResource(try MessageEmbeddedResourceAttachment(from: decoder)) case "resource": self = .resource(try MessageResourceAttachment(from: decoder)) + case "comments": + self = .comments(try MessageCommentsAttachment(from: decoder)) default: throw DecodingError.dataCorruptedError(forKey: .discriminant, in: container, debugDescription: "Unknown MessageAttachment discriminant: \(discriminant)") } @@ -4107,6 +4175,7 @@ public enum MessageAttachment: Codable, Sendable { case .simple(let value): try value.encode(to: encoder) case .embeddedResource(let value): try value.encode(to: encoder) case .resource(let value): try value.encode(to: encoder) + case .comments(let value): try value.encode(to: encoder) } } } diff --git a/clients/swift/CHANGELOG.md b/clients/swift/CHANGELOG.md index a8e83c17..44c33bde 100644 --- a/clients/swift/CHANGELOG.md +++ b/clients/swift/CHANGELOG.md @@ -51,6 +51,9 @@ the tag matches the version pinned in [`VERSION`](VERSION). `DeleteCommentThreadParams`, `AddCommentParams/Result`, `EditCommentParams`, `DeleteCommentParams`; and `SnapshotState.comments`. Reducer logic is deferred (matches the changeset/resource-watch parity). +- `MessageCommentsAttachment` (`comments` `MessageAttachment` variant) + referencing comment threads on a session's comments channel by `resource` + URI, optionally narrowed to a `threadIds` array. ### Changed diff --git a/clients/typescript/CHANGELOG.md b/clients/typescript/CHANGELOG.md index 69e35c58..41a52714 100644 --- a/clients/typescript/CHANGELOG.md +++ b/clients/typescript/CHANGELOG.md @@ -52,6 +52,9 @@ hotfix escape hatch. and the `createCommentThread`, `updateCommentThread`, `deleteCommentThread`, `addComment`, `editComment`, `deleteComment` commands. `SessionSummary.comments` surfaces the per-session `CommentsSummary` for badge UI. +- `MessageCommentsAttachment` (`comments` `MessageAttachment` variant) + referencing comment threads on a session's comments channel by `resource` + URI, optionally narrowed to a `threadIds` array. ### Changed diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 3615330e..d3d7a2ad 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -3296,6 +3296,49 @@ "type" ] }, + "MessageCommentsAttachment": { + "type": "object", + "description": "An attachment that references comment threads on a session's comments\nchannel (see {@link CommentsState}).\n\nWhen {@link threadIds} is omitted the attachment references every thread\non the channel; when present it references only the listed\n{@link CommentThread.id | thread ids}.", + "properties": { + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Comments", + "description": "Discriminant" + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI (typically `ahp-session://comments`).\nMatches {@link CommentsSummary.resource}." + }, + "threadIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Specific {@link CommentThread.id | thread ids} to reference. When\nomitted, the attachment references all threads on the channel." + } + }, + "required": [ + "label", + "type", + "resource" + ] + }, "MarkdownResponsePart": { "type": "object", "properties": { @@ -5591,6 +5634,9 @@ }, { "$ref": "#/$defs/MessageResourceAttachment" + }, + { + "$ref": "#/$defs/MessageCommentsAttachment" } ], "description": "An attachment associated with a {@link Message}." diff --git a/schema/commands.schema.json b/schema/commands.schema.json index a3599990..4be1f3e6 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -3035,6 +3035,49 @@ "type" ] }, + "MessageCommentsAttachment": { + "type": "object", + "description": "An attachment that references comment threads on a session's comments\nchannel (see {@link CommentsState}).\n\nWhen {@link threadIds} is omitted the attachment references every thread\non the channel; when present it references only the listed\n{@link CommentThread.id | thread ids}.", + "properties": { + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Comments", + "description": "Discriminant" + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI (typically `ahp-session://comments`).\nMatches {@link CommentsSummary.resource}." + }, + "threadIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Specific {@link CommentThread.id | thread ids} to reference. When\nomitted, the attachment references all threads on the channel." + } + }, + "required": [ + "label", + "type", + "resource" + ] + }, "MarkdownResponsePart": { "type": "object", "properties": { diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 3d9d415f..82cc4690 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -1763,6 +1763,49 @@ "type" ] }, + "MessageCommentsAttachment": { + "type": "object", + "description": "An attachment that references comment threads on a session's comments\nchannel (see {@link CommentsState}).\n\nWhen {@link threadIds} is omitted the attachment references every thread\non the channel; when present it references only the listed\n{@link CommentThread.id | thread ids}.", + "properties": { + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Comments", + "description": "Discriminant" + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI (typically `ahp-session://comments`).\nMatches {@link CommentsSummary.resource}." + }, + "threadIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Specific {@link CommentThread.id | thread ids} to reference. When\nomitted, the attachment references all threads on the channel." + } + }, + "required": [ + "label", + "type", + "resource" + ] + }, "MarkdownResponsePart": { "type": "object", "properties": { diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 9dface86..b0fbda2f 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -1892,6 +1892,49 @@ "type" ] }, + "MessageCommentsAttachment": { + "type": "object", + "description": "An attachment that references comment threads on a session's comments\nchannel (see {@link CommentsState}).\n\nWhen {@link threadIds} is omitted the attachment references every thread\non the channel; when present it references only the listed\n{@link CommentThread.id | thread ids}.", + "properties": { + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Comments", + "description": "Discriminant" + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI (typically `ahp-session://comments`).\nMatches {@link CommentsSummary.resource}." + }, + "threadIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Specific {@link CommentThread.id | thread ids} to reference. When\nomitted, the attachment references all threads on the channel." + } + }, + "required": [ + "label", + "type", + "resource" + ] + }, "MarkdownResponsePart": { "type": "object", "properties": { diff --git a/schema/state.schema.json b/schema/state.schema.json index de1a5c99..fd5fdcc5 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -1674,6 +1674,49 @@ "type" ] }, + "MessageCommentsAttachment": { + "type": "object", + "description": "An attachment that references comment threads on a session's comments\nchannel (see {@link CommentsState}).\n\nWhen {@link threadIds} is omitted the attachment references every thread\non the channel; when present it references only the listed\n{@link CommentThread.id | thread ids}.", + "properties": { + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Comments", + "description": "Discriminant" + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "The comments channel URI (typically `ahp-session://comments`).\nMatches {@link CommentsSummary.resource}." + }, + "threadIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Specific {@link CommentThread.id | thread ids} to reference. When\nomitted, the attachment references all threads on the channel." + } + }, + "required": [ + "label", + "type", + "resource" + ] + }, "MarkdownResponsePart": { "type": "object", "properties": { @@ -3969,6 +4012,9 @@ }, { "$ref": "#/$defs/MessageResourceAttachment" + }, + { + "$ref": "#/$defs/MessageCommentsAttachment" } ], "description": "An attachment associated with a {@link Message}." diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 92fbbf12..1663fa09 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -680,6 +680,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'SimpleMessageAttachment' }, { name: 'MessageEmbeddedResourceAttachment' }, { name: 'MessageResourceAttachment' }, + { name: 'MessageCommentsAttachment' }, { name: 'MarkdownResponsePart' }, { name: 'ContentRef' }, { name: 'ResourceReponsePart', goName: 'ResourceResponsePart' }, @@ -863,6 +864,7 @@ const MESSAGE_ATTACHMENT_UNION: UnionConfig = { { variantName: 'Simple', innerType: 'SimpleMessageAttachment', wireValue: 'simple' }, { variantName: 'EmbeddedResource', innerType: 'MessageEmbeddedResourceAttachment', wireValue: 'embeddedResource' }, { variantName: 'Resource', innerType: 'MessageResourceAttachment', wireValue: 'resource' }, + { variantName: 'Comments', innerType: 'MessageCommentsAttachment', wireValue: 'comments' }, ], unknown: true, }; diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index b8760cae..03f5e1a5 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -779,6 +779,7 @@ const STATE_STRUCTS = [ 'SessionInputRequest', 'TextPosition', 'TextRange', 'TextSelection', 'SimpleMessageAttachment', 'MessageEmbeddedResourceAttachment', 'MessageResourceAttachment', + 'MessageCommentsAttachment', 'MarkdownResponsePart', 'ContentRef', 'ResourceReponsePart', 'ToolCallResponsePart', 'ReasoningResponsePart', 'SystemNotificationResponsePart', @@ -902,6 +903,7 @@ const MESSAGE_ATTACHMENT_UNION: UnionConfig = { { caseName: 'Simple', structName: 'SimpleMessageAttachment', discriminantValue: 'simple' }, { caseName: 'EmbeddedResource', structName: 'MessageEmbeddedResourceAttachment', discriminantValue: 'embeddedResource' }, { caseName: 'Resource', structName: 'MessageResourceAttachment', discriminantValue: 'resource' }, + { caseName: 'Comments', structName: 'MessageCommentsAttachment', discriminantValue: 'comments' }, ], unknown: true, }; diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 7936f74f..cd251a3a 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -580,6 +580,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'SimpleMessageAttachment', omitDiscriminants: true }, { name: 'MessageEmbeddedResourceAttachment', omitDiscriminants: true }, { name: 'MessageResourceAttachment', omitDiscriminants: true }, + { name: 'MessageCommentsAttachment', omitDiscriminants: true }, { name: 'MarkdownResponsePart', omitDiscriminants: true }, { name: 'ContentRef' }, { name: 'ResourceReponsePart', omitDiscriminants: true, rustName: 'ResourceResponsePart' }, @@ -763,6 +764,7 @@ const MESSAGE_ATTACHMENT_UNION: UnionConfig = { { variantName: 'Simple', innerType: 'SimpleMessageAttachment', wireValue: 'simple' }, { variantName: 'EmbeddedResource', innerType: 'MessageEmbeddedResourceAttachment', wireValue: 'embeddedResource' }, { variantName: 'Resource', innerType: 'MessageResourceAttachment', wireValue: 'resource' }, + { variantName: 'Comments', innerType: 'MessageCommentsAttachment', wireValue: 'comments' }, ], unknown: true, }; diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index 2a1094c7..cc087ad4 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -532,6 +532,7 @@ const STATE_STRUCTS = [ 'SessionInputRequest', 'TextPosition', 'TextRange', 'TextSelection', 'SimpleMessageAttachment', 'MessageEmbeddedResourceAttachment', 'MessageResourceAttachment', + 'MessageCommentsAttachment', 'MarkdownResponsePart', 'ContentRef', 'ResourceReponsePart', 'ToolCallResponsePart', 'ReasoningResponsePart', 'SystemNotificationResponsePart', @@ -646,6 +647,7 @@ const MESSAGE_ATTACHMENT_UNION: UnionConfig = { { caseName: 'simple', structName: 'SimpleMessageAttachment', discriminantValue: 'simple' }, { caseName: 'embeddedResource', structName: 'MessageEmbeddedResourceAttachment', discriminantValue: 'embeddedResource' }, { caseName: 'resource', structName: 'MessageResourceAttachment', discriminantValue: 'resource' }, + { caseName: 'comments', structName: 'MessageCommentsAttachment', discriminantValue: 'comments' }, ], }; diff --git a/types/channels-session/state.ts b/types/channels-session/state.ts index a1e84583..0ee3516a 100644 --- a/types/channels-session/state.ts +++ b/types/channels-session/state.ts @@ -593,6 +593,8 @@ export const enum MessageAttachmentKind { EmbeddedResource = 'embeddedResource', /** An attachment that references a resource by URI. */ Resource = 'resource', + /** An attachment that references comment threads on a comments channel. */ + Comments = 'comments', } /** @@ -782,6 +784,31 @@ export interface MessageResourceAttachment extends MessageAttachmentBase, Conten selection?: TextSelection; } +/** + * An attachment that references comment threads on a session's comments + * channel (see {@link CommentsState}). + * + * When {@link threadIds} is omitted the attachment references every thread + * on the channel; when present it references only the listed + * {@link CommentThread.id | thread ids}. + * + * @category Turn Types + */ +export interface MessageCommentsAttachment extends MessageAttachmentBase { + /** Discriminant */ + type: MessageAttachmentKind.Comments; + /** + * The comments channel URI (typically `ahp-session://comments`). + * Matches {@link CommentsSummary.resource}. + */ + resource: URI; + /** + * Specific {@link CommentThread.id | thread ids} to reference. When + * omitted, the attachment references all threads on the channel. + */ + threadIds?: string[]; +} + /** * An attachment associated with a {@link Message}. * @@ -790,7 +817,8 @@ export interface MessageResourceAttachment extends MessageAttachmentBase, Conten export type MessageAttachment = | SimpleMessageAttachment | MessageEmbeddedResourceAttachment - | MessageResourceAttachment; + | MessageResourceAttachment + | MessageCommentsAttachment; // ─── Response Parts ────────────────────────────────────────────────────────── diff --git a/types/index.ts b/types/index.ts index 6edb4873..bd310a7b 100644 --- a/types/index.ts +++ b/types/index.ts @@ -34,6 +34,7 @@ export type { SimpleMessageAttachment, MessageEmbeddedResourceAttachment, MessageResourceAttachment, + MessageCommentsAttachment, MarkdownResponsePart, ContentRef, ToolCallResponsePart, From 2d7ba11947ba850c875cd47ed853676654fcd39a Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 8 Jun 2026 15:30:03 +0200 Subject: [PATCH 09/11] Rename to annotations --- CHANGELOG.md | 28 +- clients/go/CHANGELOG.md | 26 +- clients/go/ahp/reducers.go | 19 +- clients/go/ahp/reducers_fixture_test.go | 6 +- clients/go/ahptypes/actions.generated.go | 123 ++++---- clients/go/ahptypes/commands.generated.go | 137 ++++---- .../go/ahptypes/notifications.generated.go | 10 +- clients/go/ahptypes/state.generated.go | 146 ++++----- clients/kotlin/CHANGELOG.md | 27 +- .../microsoft/agenthostprotocol/Reducers.kt | 100 +++--- .../generated/Actions.generated.kt | 82 +++-- .../generated/Commands.generated.kt | 72 ++--- .../generated/Messages.generated.kt | 24 +- .../generated/Notifications.generated.kt | 10 +- .../generated/State.generated.kt | 100 +++--- .../FixtureDrivenReducerTest.kt | 8 +- clients/rust/CHANGELOG.md | 22 +- clients/rust/crates/ahp-types/src/actions.rs | 123 ++++---- clients/rust/crates/ahp-types/src/commands.rs | 143 +++++---- .../crates/ahp-types/src/notifications.rs | 12 +- clients/rust/crates/ahp-types/src/state.rs | 136 ++++---- .../crates/ahp/src/multi_host_state_mirror.rs | 20 +- clients/rust/crates/ahp/src/reducers.rs | 6 +- clients/rust/crates/ahp/tests/hosts.rs | 2 +- .../ahp/tests/multi_host_state_mirror.rs | 4 +- .../Generated/Actions.generated.swift | 111 +++---- .../Generated/Commands.generated.swift | 120 +++---- .../Generated/Notifications.generated.swift | 14 +- .../Generated/State.generated.swift | 126 ++++---- .../AHPStateMirror.swift | 14 +- .../MultiHostStateMirror.swift | 18 +- .../FixtureDrivenReducerTests.swift | 4 +- clients/swift/CHANGELOG.md | 22 +- clients/typescript/CHANGELOG.md | 20 +- docs/.vitepress/config.mts | 2 +- schema/actions.schema.json | 177 +++++------ schema/commands.schema.json | 292 +++++++++--------- schema/errors.schema.json | 216 ++++++------- schema/notifications.schema.json | 94 +++--- schema/state.schema.json | 90 +++--- scripts/find-protocol-sources.ts | 2 +- scripts/generate-action-origin.ts | 46 +-- scripts/generate-go.ts | 49 ++- scripts/generate-kotlin.ts | 65 ++-- scripts/generate-markdown.ts | 20 +- scripts/generate-rust.ts | 49 ++- scripts/generate-swift.ts | 39 ++- types/action-origin.generated.ts | 48 ++- types/actions.ts | 2 +- types/channels-annotations/actions.ts | 91 ++++++ types/channels-annotations/commands.ts | 203 ++++++++++++ types/channels-annotations/reducer.ts | 88 ++++++ types/channels-annotations/state.ts | 144 +++++++++ types/channels-comments/actions.ts | 105 ------- types/channels-comments/commands.ts | 204 ------------ types/channels-comments/reducer.ts | 94 ------ types/channels-comments/state.ts | 144 --------- types/channels-session/state.ts | 42 +-- types/commands.ts | 2 +- types/common/actions.ts | 29 +- types/common/messages.ts | 30 +- types/common/reducer-helpers.ts | 6 +- types/common/state.ts | 4 +- types/index.ts | 45 ++- types/messages.test.ts | 2 +- types/reducers.test.ts | 14 +- types/reducers.ts | 2 +- types/state.ts | 2 +- ...notations-set-appends-new-annotation.json} | 28 +- ...ons-set-replaces-existing-annotation.json} | 18 +- ...ns-removed-drops-matching-annotation.json} | 18 +- ...ations-entryset-appends-and-replaces.json} | 24 +- ...entryset-unknown-annotation-is-no-op.json} | 18 +- ...ns-entryremoved-drops-matching-entry.json} | 18 +- .../216-comments-cleared-empties-threads.json | 28 -- ...tations-unknown-action-type-is-no-op.json} | 14 +- types/version/message-checks.ts | 12 +- types/version/registry.ts | 9 +- 78 files changed, 2156 insertions(+), 2308 deletions(-) create mode 100644 types/channels-annotations/actions.ts create mode 100644 types/channels-annotations/commands.ts create mode 100644 types/channels-annotations/reducer.ts create mode 100644 types/channels-annotations/state.ts delete mode 100644 types/channels-comments/actions.ts delete mode 100644 types/channels-comments/commands.ts delete mode 100644 types/channels-comments/reducer.ts delete mode 100644 types/channels-comments/state.ts rename types/test-cases/reducers/{210-comments-threadset-appends-new-thread.json => 210-annotations-set-appends-new-annotation.json} (68%) rename types/test-cases/reducers/{211-comments-threadset-replaces-existing-thread.json => 211-annotations-set-replaces-existing-annotation.json} (71%) rename types/test-cases/reducers/{212-comments-threadremoved-drops-matching-thread.json => 212-annotations-removed-drops-matching-annotation.json} (73%) rename types/test-cases/reducers/{213-comments-commentset-appends-and-replaces.json => 213-annotations-entryset-appends-and-replaces.json} (64%) rename types/test-cases/reducers/{214-comments-commentset-unknown-thread-is-no-op.json => 214-annotations-entryset-unknown-annotation-is-no-op.json} (68%) rename types/test-cases/reducers/{215-comments-commentremoved-drops-matching-comment.json => 215-annotations-entryremoved-drops-matching-entry.json} (63%) delete mode 100644 types/test-cases/reducers/216-comments-cleared-empties-threads.json rename types/test-cases/reducers/{217-comments-unknown-action-type-is-no-op.json => 217-annotations-unknown-action-type-is-no-op.json} (75%) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb250bf6..01b60484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,22 +75,22 @@ Spec version: `0.3.0` - Added optional `changes` field of type `ChangesSummary` to `SessionSummary`, carrying optional `additions`, `deletions`, and `files` counts so servers can advertise an at-a-glance view of a session's file-change footprint. -- Added a new comments channel exposed on `ahp-session://comments`. - Threads anchor to a `(turnId, resource)` pair with an optional `range` +- Added a new annotations channel exposed on `ahp-session://annotations`. + Annotations anchor to a `(turnId, resource)` pair with an optional `range` (omitted to anchor to the entire file), carry a `resolved` flag (newly - created threads start unresolved; clients flip it via - `updateCommentThread`), and always carry at least one comment; new - `createCommentThread`, `updateCommentThread`, - `deleteCommentThread`, `addComment`, `editComment`, `deleteComment` - commands drive mutations and echo as `comments/threadSet`, - `comments/threadRemoved`, `comments/commentSet`, `comments/commentRemoved`, - and `comments/cleared` actions. `SessionSummary.comments` advertises the - per-session `CommentsSummary` (`{ resource, threadCount, commentCount }`) + created annotations start unresolved; clients flip it via + `updateAnnotation`), and always carry at least one entry; new + `createAnnotation`, `updateAnnotation`, + `deleteAnnotation`, `addAnnotationEntry`, `editAnnotationEntry`, `deleteAnnotationEntry` + commands drive mutations and echo as `annotations/set`, + `annotations/removed`, `annotations/entrySet`, and `annotations/entryRemoved` + actions. `SessionSummary.annotations` advertises the + per-session `AnnotationsSummary` (`{ resource, annotationCount, entryCount }`) for badge UI. -- Added a `comments` `MessageAttachment` variant - (`MessageCommentsAttachment`) that references comment threads on a - session's comments channel by its `resource` URI, optionally narrowed to - a `threadIds` array (omitted to reference every thread). +- Added an `annotations` `MessageAttachment` variant + (`MessageAnnotationsAttachment`) that references annotations on a + session's annotations channel by its `resource` URI, optionally narrowed to + an `annotationIds` array (omitted to reference every annotation). - 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`. diff --git a/clients/go/CHANGELOG.md b/clients/go/CHANGELOG.md index 1546758c..270b1705 100644 --- a/clients/go/CHANGELOG.md +++ b/clients/go/CHANGELOG.md @@ -39,19 +39,19 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file. `idle → running → error` lifecycle of a changeset operation. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. -- New comments channel wire types (`ahp-session://comments`): - `CommentsState`, `CommentThread`, `Comment`, `NewComment`, - `CommentsSummary`; the `CommentsThreadSetAction`, - `CommentsThreadRemovedAction`, `CommentsCommentSetAction`, - `CommentsCommentRemovedAction`, `CommentsClearedAction` variants; - `CreateCommentThreadParams/Result`, `UpdateCommentThreadParams`, - `DeleteCommentThreadParams`, `AddCommentParams/Result`, - `EditCommentParams`, `DeleteCommentParams` command structs; - `ApplyActionToComments` (stub mirroring `ApplyActionToChangeset`); and - `SnapshotState.Comments`. -- `MessageCommentsAttachment` (`comments` `MessageAttachment` variant) - referencing comment threads on a session's comments channel by `Resource` - URI, optionally narrowed to a `ThreadIds` array. +- New annotations channel wire types (`ahp-session://annotations`): + `AnnotationsState`, `Annotation`, `AnnotationEntry`, `NewAnnotationEntry`, + `AnnotationsSummary`; the `AnnotationsSetAction`, + `AnnotationsRemovedAction`, `AnnotationsEntrySetAction`, + `AnnotationsEntryRemovedAction` variants; + `CreateAnnotationParams/Result`, `UpdateAnnotationParams`, + `DeleteAnnotationParams`, `AddAnnotationEntryParams/Result`, + `EditAnnotationEntryParams`, `DeleteAnnotationEntryParams` command structs; + `ApplyActionToAnnotations` (stub mirroring `ApplyActionToChangeset`); and + `SnapshotState.Annotations`. +- `MessageAnnotationsAttachment` (`annotations` `MessageAttachment` variant) + referencing annotations on a session's annotations channel by `Resource` + URI, optionally narrowed to an `AnnotationIds` array. ### Changed diff --git a/clients/go/ahp/reducers.go b/clients/go/ahp/reducers.go index 3bd69288..79a6e8bf 100644 --- a/clients/go/ahp/reducers.go +++ b/clients/go/ahp/reducers.go @@ -1167,20 +1167,19 @@ func ApplyActionToChangeset(state *ahptypes.ChangesetState, action ahptypes.Stat return ReduceOutcomeOutOfScope } -// ─── Comments Reducer ───────────────────────────────────────── +// ─── Annotations Reducer ───────────────────────────────────────── -// ApplyActionToComments is the entry point for comments actions. -// Mirrors the Rust client's stub: every recognized comments action -// short-circuits as [ReduceOutcomeNoOp] until the full comments +// ApplyActionToAnnotations is the entry point for annotations actions. +// Mirrors the Rust client's stub: every recognized annotations action +// short-circuits as [ReduceOutcomeNoOp] until the full annotations // reducer is ported. Unrelated actions return [ReduceOutcomeOutOfScope]. -func ApplyActionToComments(state *ahptypes.CommentsState, action ahptypes.StateAction) ReduceOutcome { +func ApplyActionToAnnotations(state *ahptypes.AnnotationsState, action ahptypes.StateAction) ReduceOutcome { _ = state switch action.Value.(type) { - case *ahptypes.CommentsThreadSetAction, - *ahptypes.CommentsThreadRemovedAction, - *ahptypes.CommentsCommentSetAction, - *ahptypes.CommentsCommentRemovedAction, - *ahptypes.CommentsClearedAction: + case *ahptypes.AnnotationsSetAction, + *ahptypes.AnnotationsRemovedAction, + *ahptypes.AnnotationsEntrySetAction, + *ahptypes.AnnotationsEntryRemovedAction: return ReduceOutcomeNoOp } return ReduceOutcomeOutOfScope diff --git a/clients/go/ahp/reducers_fixture_test.go b/clients/go/ahp/reducers_fixture_test.go index 0259175c..0d64fddd 100644 --- a/clients/go/ahp/reducers_fixture_test.go +++ b/clients/go/ahp/reducers_fixture_test.go @@ -154,9 +154,9 @@ func TestFixtureDrivenReducerParity(t *testing.T) { case "changeset": // Changeset reducer logic is deferred — skip. tt.Skip("changeset reducer is a stub in this client (parity with Rust)") - case "comments": - // Comments reducer logic is deferred — skip. - tt.Skip("comments reducer is a stub in this client (parity with Rust)") + case "annotations": + // Annotations reducer logic is deferred — skip. + tt.Skip("annotations reducer is a stub in this client (parity with Rust)") case "resourceWatch": // Resource-watch reducer logic is deferred — skip. tt.Skip("resourceWatch reducer is a stub in this client (parity with Rust)") diff --git a/clients/go/ahptypes/actions.generated.go b/clients/go/ahptypes/actions.generated.go index 218e1526..c3a81ab9 100644 --- a/clients/go/ahptypes/actions.generated.go +++ b/clients/go/ahptypes/actions.generated.go @@ -68,11 +68,10 @@ const ( ActionTypeChangesetOperationsChanged ActionType = "changeset/operationsChanged" ActionTypeChangesetOperationStatusChanged ActionType = "changeset/operationStatusChanged" ActionTypeChangesetCleared ActionType = "changeset/cleared" - ActionTypeCommentsThreadSet ActionType = "comments/threadSet" - ActionTypeCommentsThreadRemoved ActionType = "comments/threadRemoved" - ActionTypeCommentsCommentSet ActionType = "comments/commentSet" - ActionTypeCommentsCommentRemoved ActionType = "comments/commentRemoved" - ActionTypeCommentsCleared ActionType = "comments/cleared" + ActionTypeAnnotationsSet ActionType = "annotations/set" + ActionTypeAnnotationsRemoved ActionType = "annotations/removed" + ActionTypeAnnotationsEntrySet ActionType = "annotations/entrySet" + ActionTypeAnnotationsEntryRemoved ActionType = "annotations/entryRemoved" ActionTypeRootTerminalsChanged ActionType = "root/terminalsChanged" ActionTypeRootConfigChanged ActionType = "root/configChanged" ActionTypeTerminalData ActionType = "terminal/data" @@ -801,67 +800,58 @@ type ChangesetClearedAction struct { Type ActionType `json:"type"` } -// Upsert a {@link CommentThread} in the comments channel — adds a new -// thread, or replaces an existing one identified by -// {@link CommentThread.id}. When replacing, the full thread payload -// (including its {@link CommentThread.comments | comments} list) is -// substituted; producers SHOULD prefer {@link CommentsCommentSetAction} -// for per-comment edits to keep wire updates small. -type CommentsThreadSetAction struct { +// Upsert an {@link Annotation} in the annotations channel — adds a new +// annotation, or replaces an existing one identified by +// {@link Annotation.id}. When replacing, the full annotation payload +// (including its {@link Annotation.entries | entries} list) is +// substituted; producers SHOULD prefer {@link AnnotationsEntrySetAction} +// for per-entry edits to keep wire updates small. +type AnnotationsSetAction struct { Type ActionType `json:"type"` - // The new or replacement thread. MUST contain at least one comment. - Thread CommentThread `json:"thread"` + // The new or replacement annotation. MUST contain at least one entry. + Annotation Annotation `json:"annotation"` } -// Remove a {@link CommentThread} from the channel by its id. +// Remove an {@link Annotation} from the channel by its id. // // The server emits this in two cases: // 1. The client explicitly invoked -// {@link DeleteCommentThreadParams | `deleteCommentThread`}. -// 2. The client invoked {@link DeleteCommentParams | `deleteComment`} on -// the last remaining comment in the thread — the protocol collapses -// the thread rather than leaving an empty one behind. -type CommentsThreadRemovedAction struct { +// {@link DeleteAnnotationParams | `deleteAnnotation`}. +// 2. The client invoked {@link DeleteAnnotationEntryParams | +// `deleteAnnotationEntry`} on the last remaining entry in the +// annotation — the protocol collapses the annotation rather than +// leaving an empty one behind. +type AnnotationsRemovedAction struct { Type ActionType `json:"type"` - // The {@link CommentThread.id} of the thread to remove. - ThreadId string `json:"threadId"` + // The {@link Annotation.id} of the annotation to remove. + AnnotationId string `json:"annotationId"` } -// Upsert a {@link Comment} within an existing thread — adds a new -// comment, or replaces one identified by {@link Comment.id}. If -// {@link threadId} does not match any current thread the action is a -// no-op. -type CommentsCommentSetAction struct { +// Upsert an {@link AnnotationEntry} within an existing annotation — adds a +// new entry, or replaces one identified by {@link AnnotationEntry.id}. If +// {@link annotationId} does not match any current annotation the action is +// a no-op. +type AnnotationsEntrySetAction struct { Type ActionType `json:"type"` - // The {@link CommentThread.id} the comment belongs to. - ThreadId string `json:"threadId"` - // The new or replacement comment. - Comment Comment `json:"comment"` + // The {@link Annotation.id} the entry belongs to. + AnnotationId string `json:"annotationId"` + // The new or replacement entry. + Entry AnnotationEntry `json:"entry"` } -// Remove a single {@link Comment} from a thread without collapsing the -// thread itself. Used when more than one comment remains — the server -// MUST dispatch {@link CommentsThreadRemovedAction} instead when removing -// the last comment would otherwise leave the thread empty. +// Remove a single {@link AnnotationEntry} from an annotation without +// collapsing the annotation itself. Used when more than one entry remains +// — the server MUST dispatch {@link AnnotationsRemovedAction} instead when +// removing the last entry would otherwise leave the annotation empty. // -// If either {@link threadId} or {@link commentId} does not match the +// If either {@link annotationId} or {@link entryId} does not match the // current state the action is a no-op. -type CommentsCommentRemovedAction struct { - Type ActionType `json:"type"` - // The {@link CommentThread.id} the comment belongs to. - ThreadId string `json:"threadId"` - // The {@link Comment.id} to remove. - CommentId string `json:"commentId"` -} - -// Drop every thread from the comments channel. -// -// Dispatched when the owning session is going away and the channel is -// about to become un-subscribable. Clients SHOULD release references on -// receipt and react to the corresponding session-level lifecycle signal -// (e.g. `root/sessionRemoved`) to fully tear down UI. -type CommentsClearedAction struct { +type AnnotationsEntryRemovedAction struct { Type ActionType `json:"type"` + // The {@link Annotation.id} the entry belongs to. + AnnotationId string `json:"annotationId"` + // The {@link AnnotationEntry.id} to remove. + EntryId string `json:"entryId"` } // Fired when the list of known terminals changes. @@ -1069,11 +1059,10 @@ func (*ChangesetFileRemovedAction) isStateAction() {} func (*ChangesetOperationsChangedAction) isStateAction() {} func (*ChangesetOperationStatusChangedAction) isStateAction() {} func (*ChangesetClearedAction) isStateAction() {} -func (*CommentsThreadSetAction) isStateAction() {} -func (*CommentsThreadRemovedAction) isStateAction() {} -func (*CommentsCommentSetAction) isStateAction() {} -func (*CommentsCommentRemovedAction) isStateAction() {} -func (*CommentsClearedAction) isStateAction() {} +func (*AnnotationsSetAction) isStateAction() {} +func (*AnnotationsRemovedAction) isStateAction() {} +func (*AnnotationsEntrySetAction) isStateAction() {} +func (*AnnotationsEntryRemovedAction) isStateAction() {} func (*RootTerminalsChangedAction) isStateAction() {} func (*TerminalDataAction) isStateAction() {} func (*TerminalInputAction) isStateAction() {} @@ -1402,32 +1391,26 @@ func (u *StateAction) UnmarshalJSON(data []byte) error { return err } u.Value = &value - case "comments/threadSet": - var value CommentsThreadSetAction - if err := json.Unmarshal(data, &value); err != nil { - return err - } - u.Value = &value - case "comments/threadRemoved": - var value CommentsThreadRemovedAction + case "annotations/set": + var value AnnotationsSetAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "comments/commentSet": - var value CommentsCommentSetAction + case "annotations/removed": + var value AnnotationsRemovedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "comments/commentRemoved": - var value CommentsCommentRemovedAction + case "annotations/entrySet": + var value AnnotationsEntrySetAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "comments/cleared": - var value CommentsClearedAction + case "annotations/entryRemoved": + var value AnnotationsEntryRemovedAction if err := json.Unmarshal(data, &value); err != nil { return err } diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go index 95a4dcd3..01487d82 100644 --- a/clients/go/ahptypes/commands.generated.go +++ b/clients/go/ahptypes/commands.generated.go @@ -881,121 +881,120 @@ type ChangesetOperationFollowUp struct { External *bool `json:"external,omitempty"` } -// Create a new {@link CommentThread} anchored to a file from a specific +// Create a new {@link Annotation} anchored to a file from a specific // turn, optionally narrowed to a range within that file. // -// The initial comment is required — the protocol forbids empty threads, -// so thread creation and first-comment creation are fused into one -// command. The created thread always starts unresolved -// ({@link CommentThread.resolved} is `false`). The server assigns both -// {@link CreateCommentThreadResult.threadId} and -// {@link CreateCommentThreadResult.commentId}, then broadcasts a -// {@link CommentsThreadSetAction} on the channel. -type CreateCommentThreadParams struct { +// The initial entry is required — the protocol forbids empty annotations, +// so annotation creation and first-entry creation are fused into one +// command. The created annotation always starts unresolved +// ({@link Annotation.resolved} is `false`). The server assigns both +// {@link CreateAnnotationResult.annotationId} and +// {@link CreateAnnotationResult.entryId}, then broadcasts an +// {@link AnnotationsSetAction} on the channel. +type CreateAnnotationParams struct { // Channel URI this command targets. Channel URI `json:"channel"` // Turn whose file versions {@link resource} + {@link range} address. TurnId string `json:"turnId"` // Anchored file URI. Resource URI `json:"resource"` - // Anchored range within {@link resource}. When omitted the thread is + // Anchored range within {@link resource}. When omitted the annotation is // anchored to the entire file. Range *TextRange `json:"range,omitempty"` - // First comment in the thread. The server assigns its {@link Comment.id}. - Comment NewComment `json:"comment"` + // First entry in the annotation. The server assigns its {@link AnnotationEntry.id}. + Entry NewAnnotationEntry `json:"entry"` } -// Result of {@link CreateCommentThreadParams | `createCommentThread`}. -type CreateCommentThreadResult struct { - // Server-assigned {@link CommentThread.id}. - ThreadId string `json:"threadId"` - // Server-assigned {@link Comment.id} of the initial comment. - CommentId string `json:"commentId"` +// Result of {@link CreateAnnotationParams | `createAnnotation`}. +type CreateAnnotationResult struct { + // Server-assigned {@link Annotation.id}. + AnnotationId string `json:"annotationId"` + // Server-assigned {@link AnnotationEntry.id} of the initial entry. + EntryId string `json:"entryId"` } -// Re-anchor or resolve an existing {@link CommentThread} — typically used -// to re-pin a thread to a different range or a newer turn after an edit, -// or to mark the thread {@link CommentThread.resolved | resolved} (or -// re-open it). Comments themselves are not modified by this command; use -// {@link AddCommentParams | `addComment`}, -// {@link EditCommentParams | `editComment`}, or -// {@link DeleteCommentParams | `deleteComment`} for that. +// Re-anchor or resolve an existing {@link Annotation} — typically used +// to re-pin an annotation to a different range or a newer turn after an +// edit, or to mark the annotation {@link Annotation.resolved | resolved} +// (or re-open it). Entries themselves are not modified by this command; +// use {@link AddAnnotationEntryParams | `addAnnotationEntry`}, +// {@link EditAnnotationEntryParams | `editAnnotationEntry`}, or +// {@link DeleteAnnotationEntryParams | `deleteAnnotationEntry`} for that. // // Omitted optional fields preserve their current value. The server -// echoes the resulting thread state as a {@link CommentsThreadSetAction}. -type UpdateCommentThreadParams struct { +// echoes the resulting annotation state as an {@link AnnotationsSetAction}. +type UpdateAnnotationParams struct { // Channel URI this command targets. Channel URI `json:"channel"` - // The {@link CommentThread.id} to update. - ThreadId string `json:"threadId"` - // New {@link CommentThread.turnId}, if changing. + // The {@link Annotation.id} to update. + AnnotationId string `json:"annotationId"` + // New {@link Annotation.turnId}, if changing. TurnId *string `json:"turnId,omitempty"` // New anchored file URI, if changing. Resource *URI `json:"resource,omitempty"` // New anchored range, if changing. Range *TextRange `json:"range,omitempty"` - // New {@link CommentThread.resolved} state, if changing. + // New {@link Annotation.resolved} state, if changing. Resolved *bool `json:"resolved,omitempty"` } -// Delete an entire comment thread (and every comment it contains). The -// server echoes a {@link CommentsThreadRemovedAction} on the channel. -type DeleteCommentThreadParams struct { +// Delete an entire annotation (and every entry it contains). The +// server echoes an {@link AnnotationsRemovedAction} on the channel. +type DeleteAnnotationParams struct { // Channel URI this command targets. Channel URI `json:"channel"` - // The {@link CommentThread.id} to delete. - ThreadId string `json:"threadId"` + // The {@link Annotation.id} to delete. + AnnotationId string `json:"annotationId"` } -// Append a new {@link Comment} to an existing thread. The server assigns -// the resulting {@link Comment.id} and echoes a -// {@link CommentsCommentSetAction}. -type AddCommentParams struct { +// Append a new {@link AnnotationEntry} to an existing annotation. The +// server assigns the resulting {@link AnnotationEntry.id} and echoes an +// {@link AnnotationsEntrySetAction}. +type AddAnnotationEntryParams struct { // Channel URI this command targets. Channel URI `json:"channel"` - // Thread that receives the new comment. - ThreadId string `json:"threadId"` - // Comment payload — the server assigns the id. - Comment NewComment `json:"comment"` + // Annotation that receives the new entry. + AnnotationId string `json:"annotationId"` + // Entry payload — the server assigns the id. + Entry NewAnnotationEntry `json:"entry"` } -// Result of {@link AddCommentParams | `addComment`}. -type AddCommentResult struct { - // Server-assigned {@link Comment.id} of the new comment. - CommentId string `json:"commentId"` +// Result of {@link AddAnnotationEntryParams | `addAnnotationEntry`}. +type AddAnnotationEntryResult struct { + // Server-assigned {@link AnnotationEntry.id} of the new entry. + EntryId string `json:"entryId"` } -// Edit the body of an existing comment in place. The server echoes a -// {@link CommentsCommentSetAction} carrying the updated comment. +// Edit the body of an existing entry in place. The server echoes an +// {@link AnnotationsEntrySetAction} carrying the updated entry. // // Only the body is mutable through this command; to change -// {@link Comment.source} or {@link Comment._meta} delete and re-create -// the comment. -type EditCommentParams struct { +// {@link AnnotationEntry._meta} delete and re-create the entry. +type EditAnnotationEntryParams struct { // Channel URI this command targets. Channel URI `json:"channel"` - // Enclosing thread. - ThreadId string `json:"threadId"` - // {@link Comment.id} to edit. - CommentId string `json:"commentId"` - // New comment body. See {@link Comment.text}. + // Enclosing annotation. + AnnotationId string `json:"annotationId"` + // {@link AnnotationEntry.id} to edit. + EntryId string `json:"entryId"` + // New entry body. See {@link AnnotationEntry.text}. Text StringOrMarkdown `json:"text"` } -// Remove a single comment from a thread. +// Remove a single entry from an annotation. // -// If the removal would leave the thread empty (i.e. the targeted comment -// is the only one remaining), the server collapses the thread instead -// — it dispatches a {@link CommentsThreadRemovedAction} and the thread -// disappears from {@link CommentsState.threads}. Otherwise the server -// echoes a {@link CommentsCommentRemovedAction}. -type DeleteCommentParams struct { +// If the removal would leave the annotation empty (i.e. the targeted entry +// is the only one remaining), the server collapses the annotation instead +// — it dispatches an {@link AnnotationsRemovedAction} and the annotation +// disappears from {@link AnnotationsState.annotations}. Otherwise the server +// echoes an {@link AnnotationsEntryRemovedAction}. +type DeleteAnnotationEntryParams struct { // Channel URI this command targets. Channel URI `json:"channel"` - // Enclosing thread. - ThreadId string `json:"threadId"` - // {@link Comment.id} to remove. - CommentId string `json:"commentId"` + // Enclosing annotation. + AnnotationId string `json:"annotationId"` + // {@link AnnotationEntry.id} to remove. + EntryId string `json:"entryId"` } // ─── ReconnectResult Union ──────────────────────────────────────────── diff --git a/clients/go/ahptypes/notifications.generated.go b/clients/go/ahptypes/notifications.generated.go index 7457347b..6384f763 100644 --- a/clients/go/ahptypes/notifications.generated.go +++ b/clients/go/ahptypes/notifications.generated.go @@ -189,9 +189,9 @@ type PartialSessionSummary struct { // session's footprint (e.g., for list rendering) without requiring the // client to subscribe to a changeset. Changes *ChangesSummary `json:"changes,omitempty"` - // Lightweight summary of this session's inline comments channel - // (`ahp-session://comments`). Surfaced so badge UI can render - // thread / comment counts without subscribing. Absent when the session - // does not expose a comments channel. - Comments *CommentsSummary `json:"comments,omitempty"` + // Lightweight summary of this session's inline annotations channel + // (`ahp-session://annotations`). Surfaced so badge UI can render + // annotation / entry counts without subscribing. Absent when the session + // does not expose an annotations channel. + Annotations *AnnotationsSummary `json:"annotations,omitempty"` } diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 4c6fa00a..813fc4d3 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -131,8 +131,8 @@ const ( MessageAttachmentKindEmbeddedResource MessageAttachmentKind = "embeddedResource" // An attachment that references a resource by URI. MessageAttachmentKindResource MessageAttachmentKind = "resource" - // An attachment that references comment threads on a comments channel. - MessageAttachmentKindComments MessageAttachmentKind = "comments" + // An attachment that references annotations on an annotations channel. + MessageAttachmentKindAnnotations MessageAttachmentKind = "annotations" ) // Discriminant for response part types. @@ -690,11 +690,11 @@ type SessionSummary struct { // session's footprint (e.g., for list rendering) without requiring the // client to subscribe to a changeset. Changes *ChangesSummary `json:"changes,omitempty"` - // Lightweight summary of this session's inline comments channel - // (`ahp-session://comments`). Surfaced so badge UI can render - // thread / comment counts without subscribing. Absent when the session - // does not expose a comments channel. - Comments *CommentsSummary `json:"comments,omitempty"` + // Lightweight summary of this session's inline annotations channel + // (`ahp-session://annotations`). Surfaced so badge UI can render + // annotation / entry counts without subscribing. Absent when the session + // does not expose an annotations channel. + Annotations *AnnotationsSummary `json:"annotations,omitempty"` } // Aggregate counts describing the file changes associated with a session. @@ -1143,13 +1143,13 @@ type MessageResourceAttachment struct { Selection *TextSelection `json:"selection,omitempty"` } -// An attachment that references comment threads on a session's comments -// channel (see {@link CommentsState}). +// An attachment that references annotations on a session's annotations +// channel (see {@link AnnotationsState}). // -// When {@link threadIds} is omitted the attachment references every thread -// on the channel; when present it references only the listed -// {@link CommentThread.id | thread ids}. -type MessageCommentsAttachment struct { +// When {@link annotationIds} is omitted the attachment references every +// annotation on the channel; when present it references only the listed +// {@link Annotation.id | annotation ids}. +type MessageAnnotationsAttachment struct { // A human-readable label for the attachment (e.g. the filename of a file // attachment). Used for display in UI. Label string `json:"label"` @@ -1176,12 +1176,12 @@ type MessageCommentsAttachment struct { Meta map[string]json.RawMessage `json:"_meta,omitempty"` // Discriminant Type MessageAttachmentKind `json:"type"` - // The comments channel URI (typically `ahp-session://comments`). - // Matches {@link CommentsSummary.resource}. + // The annotations channel URI (typically `ahp-session://annotations`). + // Matches {@link AnnotationsSummary.resource}. Resource URI `json:"resource"` - // Specific {@link CommentThread.id | thread ids} to reference. When - // omitted, the attachment references all threads on the channel. - ThreadIds []string `json:"threadIds,omitempty"` + // Specific {@link Annotation.id | annotation ids} to reference. When + // omitted, the attachment references all annotations on the channel. + AnnotationIds []string `json:"annotationIds,omitempty"` } type MarkdownResponsePart struct { @@ -2395,71 +2395,71 @@ type ChangesetOperation struct { Error *ErrorInfo `json:"error,omitempty"` } -// Lightweight per-session summary of the comments channel, surfaced on -// {@link SessionSummary.comments} so badge UI can render thread / comment -// counts without subscribing to the channel itself. -type CommentsSummary struct { - // The subscribable comments channel URI for the owning session - // (typically `ahp-session://comments`). Surfaced explicitly even +// Lightweight per-session summary of the annotations channel, surfaced on +// {@link SessionSummary.annotations} so badge UI can render annotation / +// entry counts without subscribing to the channel itself. +type AnnotationsSummary struct { + // The subscribable annotations channel URI for the owning session + // (typically `ahp-session://annotations`). Surfaced explicitly even // though it is derivable from the session URI so badge UI does not need // to know the derivation rule. Resource URI `json:"resource"` - // Total number of {@link CommentThread} entries in the channel. - ThreadCount int64 `json:"threadCount"` - // Total number of {@link Comment} entries across every thread. - CommentCount int64 `json:"commentCount"` + // Total number of {@link Annotation} entries in the channel. + AnnotationCount int64 `json:"annotationCount"` + // Total number of {@link AnnotationEntry} entries across every annotation. + EntryCount int64 `json:"entryCount"` } -// Full state for a session's comments channel, returned when a client -// subscribes to an `ahp-session://comments` URI. -type CommentsState struct { - // Comment threads in this channel, keyed by {@link CommentThread.id}. - Threads []CommentThread `json:"threads"` +// Full state for a session's annotations channel, returned when a client +// subscribes to an `ahp-session://annotations` URI. +type AnnotationsState struct { + // Annotations in this channel, keyed by {@link Annotation.id}. + Annotations []Annotation `json:"annotations"` } // A conversation anchored to a specific file produced by a specific turn, // optionally narrowed to a range within that file. // -// {@link turnId} anchors the thread to the file versions that turn +// {@link turnId} anchors the annotation to the file versions that turn // produced, so a later turn that rewrites the same file does not silently -// invalidate the comment's anchor — clients can resolve {@link resource} +// invalidate the annotation's anchor — clients can resolve {@link resource} // and {@link range} against the turn's changeset. When {@link range} is -// omitted the thread is anchored to the entire file. +// omitted the annotation is anchored to the entire file. // -// Every thread MUST contain at least one {@link Comment}. The server -// enforces this invariant: {@link CreateCommentThreadParams | -// `createCommentThread`} requires an initial comment, and deleting the -// last remaining comment collapses the thread into a -// {@link CommentsThreadRemovedAction} rather than leaving an empty thread +// Every annotation MUST contain at least one {@link AnnotationEntry}. The +// server enforces this invariant: {@link CreateAnnotationParams | +// `createAnnotation`} requires an initial entry, and deleting the +// last remaining entry collapses the annotation into a +// {@link AnnotationsRemovedAction} rather than leaving an empty annotation // behind. -type CommentThread struct { - // Stable identifier within the comments channel. Server-assigned. +type Annotation struct { + // Stable identifier within the annotations channel. Server-assigned. Id string `json:"id"` - // Turn that produced the file versions this thread is anchored to. + // Turn that produced the file versions this annotation is anchored to. // Matches a {@link Turn.id} on the owning session. TurnId string `json:"turnId"` - // The file the thread is anchored to. + // The file the annotation is anchored to. Resource URI `json:"resource"` - // Range within {@link resource} the thread is anchored to. When omitted - // the thread is anchored to the entire file. + // Range within {@link resource} the annotation is anchored to. When + // omitted the annotation is anchored to the entire file. Range *TextRange `json:"range,omitempty"` - // Whether the thread has been resolved. Newly created threads are always - // unresolved (`false`); a client marks a thread resolved (or re-opens it) - // through {@link UpdateCommentThreadParams | `updateCommentThread`}. + // Whether the annotation has been resolved. Newly created annotations are + // always unresolved (`false`); a client marks an annotation resolved (or + // re-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}. Resolved bool `json:"resolved"` - // Comments in this thread, in dispatch order (oldest first). MUST + // Entries in this annotation, in dispatch order (oldest first). MUST // contain at least one entry. - Comments []Comment `json:"comments"` + Entries []AnnotationEntry `json:"entries"` // Server-defined opaque metadata, surfaced to tooling but not // interpreted by the protocol. Meta map[string]json.RawMessage `json:"_meta,omitempty"` } -// A single comment within a {@link CommentThread}. -type Comment struct { - // Stable identifier within the enclosing thread. Server-assigned. +// A single entry within an {@link Annotation}. +type AnnotationEntry struct { + // Stable identifier within the enclosing annotation. Server-assigned. Id string `json:"id"` - // Comment body. A bare `string` is rendered as plain text; pass + // Entry body. A bare `string` is rendered as plain text; pass // `{ markdown: "…" }` to opt into Markdown rendering. See // {@link StringOrMarkdown}. Text StringOrMarkdown `json:"text"` @@ -2468,14 +2468,14 @@ type Comment struct { Meta map[string]json.RawMessage `json:"_meta,omitempty"` } -// Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`} -// and {@link AddCommentParams | `addComment`}. The server assigns the -// resulting {@link Comment.id}. -type NewComment struct { - // Comment body. See {@link Comment.text}. +// Input shape passed to {@link CreateAnnotationParams | `createAnnotation`} +// and {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server +// assigns the resulting {@link AnnotationEntry.id}. +type NewAnnotationEntry struct { + // Entry body. See {@link AnnotationEntry.text}. Text StringOrMarkdown `json:"text"` // Server-defined opaque metadata, forwarded onto the resulting - // {@link Comment._meta}. + // {@link AnnotationEntry._meta}. Meta map[string]json.RawMessage `json:"_meta,omitempty"` } @@ -3184,7 +3184,7 @@ type isMessageAttachment interface{ isMessageAttachment() } func (*SimpleMessageAttachment) isMessageAttachment() {} func (*MessageEmbeddedResourceAttachment) isMessageAttachment() {} func (*MessageResourceAttachment) isMessageAttachment() {} -func (*MessageCommentsAttachment) isMessageAttachment() {} +func (*MessageAnnotationsAttachment) isMessageAttachment() {} // MessageAttachmentUnknown carries an unrecognized MessageAttachment variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type MessageAttachmentUnknown struct { @@ -3218,8 +3218,8 @@ func (u *MessageAttachment) UnmarshalJSON(data []byte) error { return err } u.Value = &value - case "comments": - var value MessageCommentsAttachment + case "annotations": + var value MessageAnnotationsAttachment if err := json.Unmarshal(data, &value); err != nil { return err } @@ -3617,15 +3617,15 @@ func (u ToolCallContributor) MarshalJSON() ([]byte, error) { } // SnapshotState is the state payload of a snapshot — root, session, -// terminal, changeset, or comments state. The active variant is chosen by which +// terminal, changeset, or annotations state. The active variant is chosen by which // pointer field is non-nil; UnmarshalJSON probes for required fields in -// the canonical order (session → terminal → changeset → comments → root). +// the canonical order (session → terminal → changeset → annotations → root). type SnapshotState struct { Root *RootState `json:"-"` Session *SessionState `json:"-"` Terminal *TerminalState `json:"-"` Changeset *ChangesetState `json:"-"` - Comments *CommentsState `json:"-"` + Annotations *AnnotationsState `json:"-"` } // MarshalJSON encodes whichever variant is currently populated. @@ -3637,8 +3637,8 @@ func (s SnapshotState) MarshalJSON() ([]byte, error) { return json.Marshal(s.Terminal) case s.Changeset != nil: return json.Marshal(s.Changeset) - case s.Comments != nil: - return json.Marshal(s.Comments) + case s.Annotations != nil: + return json.Marshal(s.Annotations) case s.Root != nil: return json.Marshal(s.Root) default: @@ -3673,12 +3673,12 @@ func (s *SnapshotState) UnmarshalJSON(data []byte) error { return err } s.Changeset = &v - case containsAll(probe, "threads"): - var v CommentsState + case containsAll(probe, "annotations"): + var v AnnotationsState if err := json.Unmarshal(data, &v); err != nil { return err } - s.Comments = &v + s.Annotations = &v default: var v RootState if err := json.Unmarshal(data, &v); err != nil { diff --git a/clients/kotlin/CHANGELOG.md b/clients/kotlin/CHANGELOG.md index 9dbcfa27..ff5e9b93 100644 --- a/clients/kotlin/CHANGELOG.md +++ b/clients/kotlin/CHANGELOG.md @@ -39,20 +39,19 @@ versions (`*-SNAPSHOT`) are explicitly rejected by the publish pipeline; bump `idle → running → error` lifecycle of a changeset operation. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. -- New comments channel (`ahp-session://comments`): `CommentsState`, - `CommentThread`, `Comment`, `NewComment`, - `CommentsSummary`; the `commentsReducer` top-level function and - `CommentsReducer` object; the `comments/threadSet`, - `comments/threadRemoved`, `comments/commentSet`, `comments/commentRemoved`, - `comments/cleared` action variants; `createCommentThread`, - `updateCommentThread`, `deleteCommentThread`, `addComment`, - `editComment`, and `deleteComment` request factories on - `AhpClientRequests`; and `SnapshotState.Comments`. - `SessionSummary.comments` surfaces the per-session `CommentsSummary`. - `SessionSummary.comments` surfaces the per-session `CommentsSummary`. -- `MessageCommentsAttachment` (`comments` `MessageAttachment` variant) - referencing comment threads on a session's comments channel by `resource` - URI, optionally narrowed to a `threadIds` array. +- New annotations channel (`ahp-session://annotations`): `AnnotationsState`, + `Annotation`, `AnnotationEntry`, `NewAnnotationEntry`, + `AnnotationsSummary`; the `annotationsReducer` top-level function and + `AnnotationsReducer` object; the `annotations/set`, + `annotations/removed`, `annotations/entrySet`, and `annotations/entryRemoved` + action variants; `createAnnotation`, + `updateAnnotation`, `deleteAnnotation`, `addAnnotationEntry`, + `editAnnotationEntry`, and `deleteAnnotationEntry` request factories on + `AhpClientRequests`; and `SnapshotState.Annotations`. + `SessionSummary.annotations` surfaces the per-session `AnnotationsSummary`. +- `MessageAnnotationsAttachment` (`annotations` `MessageAttachment` variant) + referencing annotations on a session's annotations channel by `resource` + URI, optionally narrowed to an `annotationIds` array. ### Changed 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 6ac20937..c170c009 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt @@ -21,9 +21,9 @@ import com.microsoft.agenthostprotocol.generated.ChildCustomizationPrompt import com.microsoft.agenthostprotocol.generated.ChildCustomizationRule import com.microsoft.agenthostprotocol.generated.ChildCustomizationSkill import com.microsoft.agenthostprotocol.generated.ChildCustomizationUnknown -import com.microsoft.agenthostprotocol.generated.Comment -import com.microsoft.agenthostprotocol.generated.CommentThread -import com.microsoft.agenthostprotocol.generated.CommentsState +import com.microsoft.agenthostprotocol.generated.AnnotationEntry +import com.microsoft.agenthostprotocol.generated.Annotation +import com.microsoft.agenthostprotocol.generated.AnnotationsState import com.microsoft.agenthostprotocol.generated.ConfirmationOption import com.microsoft.agenthostprotocol.generated.Customization import com.microsoft.agenthostprotocol.generated.CustomizationDirectory @@ -55,11 +55,10 @@ import com.microsoft.agenthostprotocol.generated.StateActionChangesetFileSet import com.microsoft.agenthostprotocol.generated.StateActionChangesetOperationsChanged import com.microsoft.agenthostprotocol.generated.StateActionChangesetOperationStatusChanged import com.microsoft.agenthostprotocol.generated.StateActionChangesetStatusChanged -import com.microsoft.agenthostprotocol.generated.StateActionCommentsCleared -import com.microsoft.agenthostprotocol.generated.StateActionCommentsCommentRemoved -import com.microsoft.agenthostprotocol.generated.StateActionCommentsCommentSet -import com.microsoft.agenthostprotocol.generated.StateActionCommentsThreadRemoved -import com.microsoft.agenthostprotocol.generated.StateActionCommentsThreadSet +import com.microsoft.agenthostprotocol.generated.StateActionAnnotationsEntryRemoved +import com.microsoft.agenthostprotocol.generated.StateActionAnnotationsEntrySet +import com.microsoft.agenthostprotocol.generated.StateActionAnnotationsRemoved +import com.microsoft.agenthostprotocol.generated.StateActionAnnotationsSet import com.microsoft.agenthostprotocol.generated.StateActionRootActiveSessionsChanged import com.microsoft.agenthostprotocol.generated.StateActionRootAgentsChanged import com.microsoft.agenthostprotocol.generated.StateActionRootConfigChanged @@ -153,9 +152,9 @@ import kotlinx.serialization.json.JsonElement * no mutation of [state] and no side effects. * * The companion top-level functions ([rootReducer], [sessionReducer], - * [terminalReducer], [changesetReducer], [commentsReducer], [resourceWatchReducer]) are the canonical implementations. + * [terminalReducer], [changesetReducer], [annotationsReducer], [resourceWatchReducer]) are the canonical implementations. * The object instances on this interface ([RootReducer], [SessionReducer], - * [TerminalReducer], [ChangesetReducer], [CommentsReducer]) wrap them for use as values where + * [TerminalReducer], [ChangesetReducer], [AnnotationsReducer]) wrap them for use as values where * an instance is needed. */ public fun interface Reducer { @@ -186,10 +185,10 @@ public object ChangesetReducer : Reducer { changesetReducer(state, action) } -/** Pure comments reducer as a [Reducer] instance. Delegates to [commentsReducer]. */ -public object CommentsReducer : Reducer { - override fun reduce(state: CommentsState, action: StateAction): CommentsState = - commentsReducer(state, action) +/** Pure annotations reducer as a [Reducer] instance. Delegates to [annotationsReducer]. */ +public object AnnotationsReducer : Reducer { + override fun reduce(state: AnnotationsState, action: StateAction): AnnotationsState = + annotationsReducer(state, action) } /** Pure resource-watch reducer as a [Reducer] instance. Delegates to [resourceWatchReducer]. */ @@ -1351,81 +1350,78 @@ public fun changesetReducer(state: ChangesetState, action: StateAction): Changes else -> state } -// ─── Comments Reducer ────────────────────────────────────────── +// ─── Annotations Reducer ────────────────────────────────────────── /** - * Pure reducer for [CommentsState]. Handles all comments-channel action + * Pure reducer for [AnnotationsState]. Handles all annotations-channel action * variants; actions belonging to other channels (or unknown variants) are * no-ops that return [state] unchanged. * - * The single-comment-minimum invariant is enforced by the server, not the - * reducer — a malformed server that removes a thread's last comment via - * `comments/commentRemoved` would leave an empty thread, which is + * The single-entry-minimum invariant is enforced by the server, not the + * reducer — a malformed server that removes an annotation's last entry via + * `annotations/entryRemoved` would leave an empty annotation, which is * observable but not catastrophic. */ -public fun commentsReducer(state: CommentsState, action: StateAction): CommentsState = when (action) { - is StateActionCommentsThreadSet -> { - val thread = action.value.thread - val idx = state.threads.indexOfFirst { it.id == thread.id } +public fun annotationsReducer(state: AnnotationsState, action: StateAction): AnnotationsState = when (action) { + is StateActionAnnotationsSet -> { + val annotation = action.value.annotation + val idx = state.annotations.indexOfFirst { it.id == annotation.id } if (idx < 0) { - state.copy(threads = state.threads + thread) + state.copy(annotations = state.annotations + annotation) } else { - val next = state.threads.toMutableList().also { it[idx] = thread } - state.copy(threads = next) + val next = state.annotations.toMutableList().also { it[idx] = annotation } + state.copy(annotations = next) } } - is StateActionCommentsThreadRemoved -> { - val idx = state.threads.indexOfFirst { it.id == action.value.threadId } + is StateActionAnnotationsRemoved -> { + val idx = state.annotations.indexOfFirst { it.id == action.value.annotationId } if (idx < 0) { state } else { - val next: List = state.threads.toMutableList().also { it.removeAt(idx) } - state.copy(threads = next) + val next: List = state.annotations.toMutableList().also { it.removeAt(idx) } + state.copy(annotations = next) } } - is StateActionCommentsCommentSet -> { - val tIdx = state.threads.indexOfFirst { it.id == action.value.threadId } + is StateActionAnnotationsEntrySet -> { + val tIdx = state.annotations.indexOfFirst { it.id == action.value.annotationId } if (tIdx < 0) { state } else { - val thread = state.threads[tIdx] - val comment = action.value.comment - val cIdx = thread.comments.indexOfFirst { it.id == comment.id } - val nextComments = if (cIdx < 0) { - thread.comments + comment + val annotation = state.annotations[tIdx] + val entry = action.value.entry + val cIdx = annotation.entries.indexOfFirst { it.id == entry.id } + val nextEntries = if (cIdx < 0) { + annotation.entries + entry } else { - thread.comments.toMutableList().also { it[cIdx] = comment } + annotation.entries.toMutableList().also { it[cIdx] = entry } } - val nextThreads = state.threads.toMutableList() - .also { it[tIdx] = thread.copy(comments = nextComments) } - state.copy(threads = nextThreads) + val nextAnnotations = state.annotations.toMutableList() + .also { it[tIdx] = annotation.copy(entries = nextEntries) } + state.copy(annotations = nextAnnotations) } } - is StateActionCommentsCommentRemoved -> { - val tIdx = state.threads.indexOfFirst { it.id == action.value.threadId } + is StateActionAnnotationsEntryRemoved -> { + val tIdx = state.annotations.indexOfFirst { it.id == action.value.annotationId } if (tIdx < 0) { state } else { - val thread = state.threads[tIdx] - val cIdx = thread.comments.indexOfFirst { it.id == action.value.commentId } + val annotation = state.annotations[tIdx] + val cIdx = annotation.entries.indexOfFirst { it.id == action.value.entryId } if (cIdx < 0) { state } else { - val nextComments: List = thread.comments.toMutableList() + val nextEntries: List = annotation.entries.toMutableList() .also { it.removeAt(cIdx) } - val nextThreads = state.threads.toMutableList() - .also { it[tIdx] = thread.copy(comments = nextComments) } - state.copy(threads = nextThreads) + val nextAnnotations = state.annotations.toMutableList() + .also { it[tIdx] = annotation.copy(entries = nextEntries) } + state.copy(annotations = nextAnnotations) } } } - is StateActionCommentsCleared -> - if (state.threads.isEmpty()) state else state.copy(threads = emptyList()) - else -> state } 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 ceae736e..ceb0677f 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 @@ -124,16 +124,14 @@ enum class ActionType { CHANGESET_OPERATION_STATUS_CHANGED, @SerialName("changeset/cleared") CHANGESET_CLEARED, - @SerialName("comments/threadSet") - COMMENTS_THREAD_SET, - @SerialName("comments/threadRemoved") - COMMENTS_THREAD_REMOVED, - @SerialName("comments/commentSet") - COMMENTS_COMMENT_SET, - @SerialName("comments/commentRemoved") - COMMENTS_COMMENT_REMOVED, - @SerialName("comments/cleared") - COMMENTS_CLEARED, + @SerialName("annotations/set") + ANNOTATIONS_SET, + @SerialName("annotations/removed") + ANNOTATIONS_REMOVED, + @SerialName("annotations/entrySet") + ANNOTATIONS_ENTRY_SET, + @SerialName("annotations/entryRemoved") + ANNOTATIONS_ENTRY_REMOVED, @SerialName("root/terminalsChanged") ROOT_TERMINALS_CHANGED, @SerialName("root/configChanged") @@ -888,52 +886,47 @@ data class ChangesetClearedAction( ) @Serializable -data class CommentsThreadSetAction( +data class AnnotationsSetAction( val type: ActionType, /** - * The new or replacement thread. MUST contain at least one comment. + * The new or replacement annotation. MUST contain at least one entry. */ - val thread: CommentThread + val annotation: Annotation ) @Serializable -data class CommentsThreadRemovedAction( +data class AnnotationsRemovedAction( val type: ActionType, /** - * The {@link CommentThread.id} of the thread to remove. + * The {@link Annotation.id} of the annotation to remove. */ - val threadId: String + val annotationId: String ) @Serializable -data class CommentsCommentSetAction( +data class AnnotationsEntrySetAction( val type: ActionType, /** - * The {@link CommentThread.id} the comment belongs to. + * The {@link Annotation.id} the entry belongs to. */ - val threadId: String, + val annotationId: String, /** - * The new or replacement comment. + * The new or replacement entry. */ - val comment: Comment + val entry: AnnotationEntry ) @Serializable -data class CommentsCommentRemovedAction( +data class AnnotationsEntryRemovedAction( val type: ActionType, /** - * The {@link CommentThread.id} the comment belongs to. + * The {@link Annotation.id} the entry belongs to. */ - val threadId: String, + val annotationId: String, /** - * The {@link Comment.id} to remove. + * The {@link AnnotationEntry.id} to remove. */ - val commentId: String -) - -@Serializable -data class CommentsClearedAction( - val type: ActionType + val entryId: String ) @Serializable @@ -1144,11 +1137,10 @@ sealed interface StateAction @JvmInline value class StateActionChangesetOperationsChanged(val value: ChangesetOperationsChangedAction) : StateAction @JvmInline value class StateActionChangesetOperationStatusChanged(val value: ChangesetOperationStatusChangedAction) : StateAction @JvmInline value class StateActionChangesetCleared(val value: ChangesetClearedAction) : StateAction -@JvmInline value class StateActionCommentsThreadSet(val value: CommentsThreadSetAction) : StateAction -@JvmInline value class StateActionCommentsThreadRemoved(val value: CommentsThreadRemovedAction) : StateAction -@JvmInline value class StateActionCommentsCommentSet(val value: CommentsCommentSetAction) : StateAction -@JvmInline value class StateActionCommentsCommentRemoved(val value: CommentsCommentRemovedAction) : StateAction -@JvmInline value class StateActionCommentsCleared(val value: CommentsClearedAction) : StateAction +@JvmInline value class StateActionAnnotationsSet(val value: AnnotationsSetAction) : StateAction +@JvmInline value class StateActionAnnotationsRemoved(val value: AnnotationsRemovedAction) : StateAction +@JvmInline value class StateActionAnnotationsEntrySet(val value: AnnotationsEntrySetAction) : StateAction +@JvmInline value class StateActionAnnotationsEntryRemoved(val value: AnnotationsEntryRemovedAction) : StateAction @JvmInline value class StateActionRootTerminalsChanged(val value: RootTerminalsChangedAction) : StateAction @JvmInline value class StateActionRootConfigChanged(val value: RootConfigChangedAction) : StateAction @JvmInline value class StateActionTerminalData(val value: TerminalDataAction) : StateAction @@ -1227,11 +1219,10 @@ internal object StateActionSerializer : KSerializer { "changeset/operationsChanged" -> StateActionChangesetOperationsChanged(input.json.decodeFromJsonElement(ChangesetOperationsChangedAction.serializer(), element)) "changeset/operationStatusChanged" -> StateActionChangesetOperationStatusChanged(input.json.decodeFromJsonElement(ChangesetOperationStatusChangedAction.serializer(), element)) "changeset/cleared" -> StateActionChangesetCleared(input.json.decodeFromJsonElement(ChangesetClearedAction.serializer(), element)) - "comments/threadSet" -> StateActionCommentsThreadSet(input.json.decodeFromJsonElement(CommentsThreadSetAction.serializer(), element)) - "comments/threadRemoved" -> StateActionCommentsThreadRemoved(input.json.decodeFromJsonElement(CommentsThreadRemovedAction.serializer(), element)) - "comments/commentSet" -> StateActionCommentsCommentSet(input.json.decodeFromJsonElement(CommentsCommentSetAction.serializer(), element)) - "comments/commentRemoved" -> StateActionCommentsCommentRemoved(input.json.decodeFromJsonElement(CommentsCommentRemovedAction.serializer(), element)) - "comments/cleared" -> StateActionCommentsCleared(input.json.decodeFromJsonElement(CommentsClearedAction.serializer(), element)) + "annotations/set" -> StateActionAnnotationsSet(input.json.decodeFromJsonElement(AnnotationsSetAction.serializer(), element)) + "annotations/removed" -> StateActionAnnotationsRemoved(input.json.decodeFromJsonElement(AnnotationsRemovedAction.serializer(), element)) + "annotations/entrySet" -> StateActionAnnotationsEntrySet(input.json.decodeFromJsonElement(AnnotationsEntrySetAction.serializer(), element)) + "annotations/entryRemoved" -> StateActionAnnotationsEntryRemoved(input.json.decodeFromJsonElement(AnnotationsEntryRemovedAction.serializer(), element)) "root/terminalsChanged" -> StateActionRootTerminalsChanged(input.json.decodeFromJsonElement(RootTerminalsChangedAction.serializer(), element)) "root/configChanged" -> StateActionRootConfigChanged(input.json.decodeFromJsonElement(RootConfigChangedAction.serializer(), element)) "terminal/data" -> StateActionTerminalData(input.json.decodeFromJsonElement(TerminalDataAction.serializer(), element)) @@ -1303,11 +1294,10 @@ internal object StateActionSerializer : KSerializer { is StateActionChangesetOperationsChanged -> output.json.encodeToJsonElement(ChangesetOperationsChangedAction.serializer(), value.value) is StateActionChangesetOperationStatusChanged -> output.json.encodeToJsonElement(ChangesetOperationStatusChangedAction.serializer(), value.value) is StateActionChangesetCleared -> output.json.encodeToJsonElement(ChangesetClearedAction.serializer(), value.value) - is StateActionCommentsThreadSet -> output.json.encodeToJsonElement(CommentsThreadSetAction.serializer(), value.value) - is StateActionCommentsThreadRemoved -> output.json.encodeToJsonElement(CommentsThreadRemovedAction.serializer(), value.value) - is StateActionCommentsCommentSet -> output.json.encodeToJsonElement(CommentsCommentSetAction.serializer(), value.value) - is StateActionCommentsCommentRemoved -> output.json.encodeToJsonElement(CommentsCommentRemovedAction.serializer(), value.value) - is StateActionCommentsCleared -> output.json.encodeToJsonElement(CommentsClearedAction.serializer(), value.value) + is StateActionAnnotationsSet -> output.json.encodeToJsonElement(AnnotationsSetAction.serializer(), value.value) + is StateActionAnnotationsRemoved -> output.json.encodeToJsonElement(AnnotationsRemovedAction.serializer(), value.value) + is StateActionAnnotationsEntrySet -> output.json.encodeToJsonElement(AnnotationsEntrySetAction.serializer(), value.value) + is StateActionAnnotationsEntryRemoved -> output.json.encodeToJsonElement(AnnotationsEntryRemovedAction.serializer(), value.value) is StateActionRootTerminalsChanged -> output.json.encodeToJsonElement(RootTerminalsChangedAction.serializer(), value.value) is StateActionRootConfigChanged -> output.json.encodeToJsonElement(RootConfigChangedAction.serializer(), value.value) is StateActionTerminalData -> output.json.encodeToJsonElement(TerminalDataAction.serializer(), value.value) diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt index 508f0e59..1139305d 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt @@ -1028,7 +1028,7 @@ data class InvokeChangesetOperationResult( ) @Serializable -data class CreateCommentThreadParams( +data class CreateAnnotationParams( /** * Channel URI this command targets. */ @@ -1042,40 +1042,40 @@ data class CreateCommentThreadParams( */ val resource: String, /** - * Anchored range within {@link resource}. When omitted the thread is + * Anchored range within {@link resource}. When omitted the annotation is * anchored to the entire file. */ val range: TextRange? = null, /** - * First comment in the thread. The server assigns its {@link Comment.id}. + * First entry in the annotation. The server assigns its {@link AnnotationEntry.id}. */ - val comment: NewComment + val entry: NewAnnotationEntry ) @Serializable -data class CreateCommentThreadResult( +data class CreateAnnotationResult( /** - * Server-assigned {@link CommentThread.id}. + * Server-assigned {@link Annotation.id}. */ - val threadId: String, + val annotationId: String, /** - * Server-assigned {@link Comment.id} of the initial comment. + * Server-assigned {@link AnnotationEntry.id} of the initial entry. */ - val commentId: String + val entryId: String ) @Serializable -data class UpdateCommentThreadParams( +data class UpdateAnnotationParams( /** * Channel URI this command targets. */ val channel: String, /** - * The {@link CommentThread.id} to update. + * The {@link Annotation.id} to update. */ - val threadId: String, + val annotationId: String, /** - * New {@link CommentThread.turnId}, if changing. + * New {@link Annotation.turnId}, if changing. */ val turnId: String? = null, /** @@ -1087,81 +1087,81 @@ data class UpdateCommentThreadParams( */ val range: TextRange? = null, /** - * New {@link CommentThread.resolved} state, if changing. + * New {@link Annotation.resolved} state, if changing. */ val resolved: Boolean? = null ) @Serializable -data class DeleteCommentThreadParams( +data class DeleteAnnotationParams( /** * Channel URI this command targets. */ val channel: String, /** - * The {@link CommentThread.id} to delete. + * The {@link Annotation.id} to delete. */ - val threadId: String + val annotationId: String ) @Serializable -data class AddCommentParams( +data class AddAnnotationEntryParams( /** * Channel URI this command targets. */ val channel: String, /** - * Thread that receives the new comment. + * Annotation that receives the new entry. */ - val threadId: String, + val annotationId: String, /** - * Comment payload — the server assigns the id. + * Entry payload — the server assigns the id. */ - val comment: NewComment + val entry: NewAnnotationEntry ) @Serializable -data class AddCommentResult( +data class AddAnnotationEntryResult( /** - * Server-assigned {@link Comment.id} of the new comment. + * Server-assigned {@link AnnotationEntry.id} of the new entry. */ - val commentId: String + val entryId: String ) @Serializable -data class EditCommentParams( +data class EditAnnotationEntryParams( /** * Channel URI this command targets. */ val channel: String, /** - * Enclosing thread. + * Enclosing annotation. */ - val threadId: String, + val annotationId: String, /** - * {@link Comment.id} to edit. + * {@link AnnotationEntry.id} to edit. */ - val commentId: String, + val entryId: String, /** - * New comment body. See {@link Comment.text}. + * New entry body. See {@link AnnotationEntry.text}. */ val text: StringOrMarkdown ) @Serializable -data class DeleteCommentParams( +data class DeleteAnnotationEntryParams( /** * Channel URI this command targets. */ val channel: String, /** - * Enclosing thread. + * Enclosing annotation. */ - val threadId: String, + val annotationId: String, /** - * {@link Comment.id} to remove. + * {@link AnnotationEntry.id} to remove. */ - val commentId: String + val entryId: String ) @Serializable diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt index e95b3102..dc056593 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt @@ -146,23 +146,23 @@ object AhpCommands { fun invokeChangesetOperation(id: Long, params: InvokeChangesetOperationParams): JsonRpcRequest = JsonRpcRequest(id = id, method = "invokeChangesetOperation", params = params) - fun createCommentThread(id: Long, params: CreateCommentThreadParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "createCommentThread", params = params) + fun createAnnotation(id: Long, params: CreateAnnotationParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "createAnnotation", params = params) - fun updateCommentThread(id: Long, params: UpdateCommentThreadParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "updateCommentThread", params = params) + fun updateAnnotation(id: Long, params: UpdateAnnotationParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "updateAnnotation", params = params) - fun deleteCommentThread(id: Long, params: DeleteCommentThreadParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "deleteCommentThread", params = params) + fun deleteAnnotation(id: Long, params: DeleteAnnotationParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "deleteAnnotation", params = params) - fun addComment(id: Long, params: AddCommentParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "addComment", params = params) + fun addAnnotationEntry(id: Long, params: AddAnnotationEntryParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "addAnnotationEntry", params = params) - fun editComment(id: Long, params: EditCommentParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "editComment", params = params) + fun editAnnotationEntry(id: Long, params: EditAnnotationEntryParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "editAnnotationEntry", params = params) - fun deleteComment(id: Long, params: DeleteCommentParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "deleteComment", params = params) + fun deleteAnnotationEntry(id: Long, params: DeleteAnnotationEntryParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "deleteAnnotationEntry", params = params) } /** 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 7cc3e37a..e7128cda 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 @@ -200,10 +200,10 @@ data class PartialSessionSummary( */ val changes: ChangesSummary? = null, /** - * Lightweight summary of this session's inline comments channel - * (`ahp-session://comments`). Surfaced so badge UI can render - * thread / comment counts without subscribing. Absent when the session - * does not expose a comments channel. + * Lightweight summary of this session's inline annotations channel + * (`ahp-session://annotations`). Surfaced so badge UI can render + * annotation / entry counts without subscribing. Absent when the session + * does not expose an annotations channel. */ - val comments: CommentsSummary? = null + val annotations: AnnotationsSummary? = null ) 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 1a7c77d8..04bcf268 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 @@ -275,10 +275,10 @@ enum class MessageAttachmentKind { @SerialName("resource") RESOURCE, /** - * An attachment that references comment threads on a comments channel. + * An attachment that references annotations on an annotations channel. */ - @SerialName("comments") - COMMENTS + @SerialName("annotations") + ANNOTATIONS } /** @@ -1100,12 +1100,12 @@ data class SessionSummary( */ val changes: ChangesSummary? = null, /** - * Lightweight summary of this session's inline comments channel - * (`ahp-session://comments`). Surfaced so badge UI can render - * thread / comment counts without subscribing. Absent when the session - * does not expose a comments channel. + * Lightweight summary of this session's inline annotations channel + * (`ahp-session://annotations`). Surfaced so badge UI can render + * annotation / entry counts without subscribing. Absent when the session + * does not expose an annotations channel. */ - val comments: CommentsSummary? = null + val annotations: AnnotationsSummary? = null ) @Serializable @@ -1690,7 +1690,7 @@ data class MessageResourceAttachment( ) @Serializable -data class MessageCommentsAttachment( +data class MessageAnnotationsAttachment( /** * A human-readable label for the attachment (e.g. the filename of a file * attachment). Used for display in UI. @@ -1729,15 +1729,15 @@ data class MessageCommentsAttachment( */ val type: MessageAttachmentKind, /** - * The comments channel URI (typically `ahp-session://comments`). - * Matches {@link CommentsSummary.resource}. + * The annotations channel URI (typically `ahp-session://annotations`). + * Matches {@link AnnotationsSummary.resource}. */ val resource: String, /** - * Specific {@link CommentThread.id | thread ids} to reference. When - * omitted, the attachment references all threads on the channel. + * Specific {@link Annotation.id | annotation ids} to reference. When + * omitted, the attachment references all annotations on the channel. */ - val threadIds: List? = null + val annotationIds: List? = null ) @Serializable @@ -3347,63 +3347,63 @@ data class ChangesetOperation( ) @Serializable -data class CommentsSummary( +data class AnnotationsSummary( /** - * The subscribable comments channel URI for the owning session - * (typically `ahp-session://comments`). Surfaced explicitly even + * The subscribable annotations channel URI for the owning session + * (typically `ahp-session://annotations`). Surfaced explicitly even * though it is derivable from the session URI so badge UI does not need * to know the derivation rule. */ val resource: String, /** - * Total number of {@link CommentThread} entries in the channel. + * Total number of {@link Annotation} entries in the channel. */ - val threadCount: Long, + val annotationCount: Long, /** - * Total number of {@link Comment} entries across every thread. + * Total number of {@link AnnotationEntry} entries across every annotation. */ - val commentCount: Long + val entryCount: Long ) @Serializable -data class CommentsState( +data class AnnotationsState( /** - * Comment threads in this channel, keyed by {@link CommentThread.id}. + * Annotations in this channel, keyed by {@link Annotation.id}. */ - val threads: List + val annotations: List ) @Serializable -data class CommentThread( +data class Annotation( /** - * Stable identifier within the comments channel. Server-assigned. + * Stable identifier within the annotations channel. Server-assigned. */ val id: String, /** - * Turn that produced the file versions this thread is anchored to. + * Turn that produced the file versions this annotation is anchored to. * Matches a {@link Turn.id} on the owning session. */ val turnId: String, /** - * The file the thread is anchored to. + * The file the annotation is anchored to. */ val resource: String, /** - * Range within {@link resource} the thread is anchored to. When omitted - * the thread is anchored to the entire file. + * Range within {@link resource} the annotation is anchored to. When + * omitted the annotation is anchored to the entire file. */ val range: TextRange? = null, /** - * Whether the thread has been resolved. Newly created threads are always - * unresolved (`false`); a client marks a thread resolved (or re-opens it) - * through {@link UpdateCommentThreadParams | `updateCommentThread`}. + * Whether the annotation has been resolved. Newly created annotations are + * always unresolved (`false`); a client marks an annotation resolved (or + * re-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}. */ val resolved: Boolean, /** - * Comments in this thread, in dispatch order (oldest first). MUST + * Entries in this annotation, in dispatch order (oldest first). MUST * contain at least one entry. */ - val comments: List, + val entries: List, /** * Server-defined opaque metadata, surfaced to tooling but not * interpreted by the protocol. @@ -3413,13 +3413,13 @@ data class CommentThread( ) @Serializable -data class Comment( +data class AnnotationEntry( /** - * Stable identifier within the enclosing thread. Server-assigned. + * Stable identifier within the enclosing annotation. Server-assigned. */ val id: String, /** - * Comment body. A bare `string` is rendered as plain text; pass + * Entry body. A bare `string` is rendered as plain text; pass * `{ markdown: "…" }` to opt into Markdown rendering. See * {@link StringOrMarkdown}. */ @@ -3433,14 +3433,14 @@ data class Comment( ) @Serializable -data class NewComment( +data class NewAnnotationEntry( /** - * Comment body. See {@link Comment.text}. + * Entry body. See {@link AnnotationEntry.text}. */ val text: StringOrMarkdown, /** * Server-defined opaque metadata, forwarded onto the resulting - * {@link Comment._meta}. + * {@link AnnotationEntry._meta}. */ @SerialName("_meta") val meta: Map? = null @@ -3930,7 +3930,7 @@ value class MessageAttachmentEmbeddedResource(val value: MessageEmbeddedResource @JvmInline value class MessageAttachmentResource(val value: MessageResourceAttachment) : MessageAttachment @JvmInline -value class MessageAttachmentComments(val value: MessageCommentsAttachment) : MessageAttachment +value class MessageAttachmentAnnotations(val value: MessageAnnotationsAttachment) : MessageAttachment /** * Forward-compat catch-all for unknown MessageAttachment discriminators. * @@ -3958,7 +3958,7 @@ internal object MessageAttachmentSerializer : KSerializer { "simple" -> MessageAttachmentSimple(input.json.decodeFromJsonElement(SimpleMessageAttachment.serializer(), element)) "embeddedResource" -> MessageAttachmentEmbeddedResource(input.json.decodeFromJsonElement(MessageEmbeddedResourceAttachment.serializer(), element)) "resource" -> MessageAttachmentResource(input.json.decodeFromJsonElement(MessageResourceAttachment.serializer(), element)) - "comments" -> MessageAttachmentComments(input.json.decodeFromJsonElement(MessageCommentsAttachment.serializer(), element)) + "annotations" -> MessageAttachmentAnnotations(input.json.decodeFromJsonElement(MessageAnnotationsAttachment.serializer(), element)) else -> MessageAttachmentUnknown(obj) } } @@ -3970,7 +3970,7 @@ internal object MessageAttachmentSerializer : KSerializer { is MessageAttachmentSimple -> output.json.encodeToJsonElement(SimpleMessageAttachment.serializer(), value.value) is MessageAttachmentEmbeddedResource -> output.json.encodeToJsonElement(MessageEmbeddedResourceAttachment.serializer(), value.value) is MessageAttachmentResource -> output.json.encodeToJsonElement(MessageResourceAttachment.serializer(), value.value) - is MessageAttachmentComments -> output.json.encodeToJsonElement(MessageCommentsAttachment.serializer(), value.value) + is MessageAttachmentAnnotations -> output.json.encodeToJsonElement(MessageAnnotationsAttachment.serializer(), value.value) is MessageAttachmentUnknown -> value.raw } output.encodeJsonElement(element) @@ -4321,7 +4321,7 @@ internal object ToolResultContentSerializer : KSerializer { /** * The state payload of a snapshot — root, session, terminal, changeset, - * or comments state. + * or annotations state. */ @Serializable(with = SnapshotStateSerializer::class) sealed interface SnapshotState { @@ -4329,7 +4329,7 @@ sealed interface SnapshotState { @JvmInline value class Session(val value: SessionState) : SnapshotState @JvmInline value class Terminal(val value: TerminalState) : SnapshotState @JvmInline value class Changeset(val value: ChangesetState) : SnapshotState - @JvmInline value class Comments(val value: CommentsState) : SnapshotState + @JvmInline value class Annotations(val value: AnnotationsState) : SnapshotState } internal object SnapshotStateSerializer : KSerializer { @@ -4344,14 +4344,14 @@ internal object SnapshotStateSerializer : KSerializer { ?: error("Expected JsonObject for SnapshotState") // Try the most distinctive shape first. SessionState has required // `summary`; ChangesetState has required `status` + `files`; - // CommentsState has required `threads`; TerminalState has `uri` + // AnnotationsState has required `annotations`; TerminalState has `uri` // / `size` / `buffer`; RootState is the catch-all. return when { obj.containsKey("summary") -> SnapshotState.Session(input.json.decodeFromJsonElement(SessionState.serializer(), element)) obj.containsKey("status") && obj.containsKey("files") -> SnapshotState.Changeset(input.json.decodeFromJsonElement(ChangesetState.serializer(), element)) - obj.containsKey("threads") -> - SnapshotState.Comments(input.json.decodeFromJsonElement(CommentsState.serializer(), element)) + obj.containsKey("annotations") -> + SnapshotState.Annotations(input.json.decodeFromJsonElement(AnnotationsState.serializer(), element)) obj.containsKey("size") || obj.containsKey("uri") || obj.containsKey("buffer") -> SnapshotState.Terminal(input.json.decodeFromJsonElement(TerminalState.serializer(), element)) else -> SnapshotState.Root(input.json.decodeFromJsonElement(RootState.serializer(), element)) @@ -4366,7 +4366,7 @@ internal object SnapshotStateSerializer : KSerializer { is SnapshotState.Session -> output.json.encodeToJsonElement(SessionState.serializer(), value.value) is SnapshotState.Terminal -> output.json.encodeToJsonElement(TerminalState.serializer(), value.value) is SnapshotState.Changeset -> output.json.encodeToJsonElement(ChangesetState.serializer(), value.value) - is SnapshotState.Comments -> output.json.encodeToJsonElement(CommentsState.serializer(), value.value) + is SnapshotState.Annotations -> output.json.encodeToJsonElement(AnnotationsState.serializer(), value.value) } output.encodeJsonElement(element) } diff --git a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt index 1078cac1..229a69c0 100644 --- a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt +++ b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt @@ -1,7 +1,7 @@ package com.microsoft.agenthostprotocol import com.microsoft.agenthostprotocol.generated.ChangesetState -import com.microsoft.agenthostprotocol.generated.CommentsState +import com.microsoft.agenthostprotocol.generated.AnnotationsState import com.microsoft.agenthostprotocol.generated.ResourceWatchState import com.microsoft.agenthostprotocol.generated.RootState import com.microsoft.agenthostprotocol.generated.SessionState @@ -180,14 +180,14 @@ class FixtureDrivenReducerTest { }, ) - "comments" -> compareFixture( + "annotations" -> compareFixture( file = file, initial = initial, expected = expected, - serializer = CommentsState.serializer(), + serializer = AnnotationsState.serializer(), run = { state -> var s = state - for (action in actions) s = commentsReducer(s, action) + for (action in actions) s = annotationsReducer(s, action) s }, ) diff --git a/clients/rust/CHANGELOG.md b/clients/rust/CHANGELOG.md index e5038b7a..a645eacd 100644 --- a/clients/rust/CHANGELOG.md +++ b/clients/rust/CHANGELOG.md @@ -40,18 +40,18 @@ matching `## [X.Y.Z]` heading is missing from this file. `idle → running → error` lifecycle of a changeset operation. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. -- New comments channel wire types (`ahp-session://comments`): - `CommentsState`, `CommentThread`, `Comment`, `NewComment`, - `CommentsSummary`; the - `comments/threadSet` / `comments/threadRemoved` / `comments/commentSet` - / `comments/commentRemoved` / `comments/cleared` action variants; - `createCommentThread`, `updateCommentThread`, `deleteCommentThread`, - `addComment`, `editComment`, and `deleteComment` command structs; - `MultiHostStateMirror.comments()` and `SnapshotState::Comments`. +- New annotations channel wire types (`ahp-session://annotations`): + `AnnotationsState`, `Annotation`, `AnnotationEntry`, `NewAnnotationEntry`, + `AnnotationsSummary`; the + `annotations/set` / `annotations/removed` / `annotations/entrySet` + / `annotations/entryRemoved` action variants; + `createAnnotation`, `updateAnnotation`, `deleteAnnotation`, + `addAnnotationEntry`, `editAnnotationEntry`, and `deleteAnnotationEntry` command structs; + `MultiHostStateMirror.annotations()` and `SnapshotState::Annotations`. Reducer logic is deferred (matches the changeset stub). -- `MessageCommentsAttachment` (`comments` `MessageAttachment` variant) - referencing comment threads on a session's comments channel by `resource` - URI, optionally narrowed to a `threadIds` array. +- `MessageAnnotationsAttachment` (`annotations` `MessageAttachment` variant) + referencing annotations on a session's annotations channel by `resource` + URI, optionally narrowed to an `annotationIds` array. ### Changed diff --git a/clients/rust/crates/ahp-types/src/actions.rs b/clients/rust/crates/ahp-types/src/actions.rs index e211752f..5d1b4325 100644 --- a/clients/rust/crates/ahp-types/src/actions.rs +++ b/clients/rust/crates/ahp-types/src/actions.rs @@ -12,8 +12,8 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::state::{ - AgentInfo, AgentSelection, Changeset, ChangesetFile, ChangesetOperation, - ChangesetOperationStatus, ChangesetStatus, Comment, CommentThread, ConfirmationOption, + AgentInfo, AgentSelection, Annotation, AnnotationEntry, Changeset, ChangesetFile, + ChangesetOperation, ChangesetOperationStatus, ChangesetStatus, ConfirmationOption, Customization, ErrorInfo, McpServerState, Message, ModelSelection, PendingMessageKind, ResponsePart, SessionActiveClient, SessionInputAnswer, SessionInputRequest, SessionInputResponseKind, TerminalClaim, TerminalInfo, ToolCallCancellationReason, @@ -124,16 +124,14 @@ pub enum ActionType { ChangesetOperationStatusChanged, #[serde(rename = "changeset/cleared")] ChangesetCleared, - #[serde(rename = "comments/threadSet")] - CommentsThreadSet, - #[serde(rename = "comments/threadRemoved")] - CommentsThreadRemoved, - #[serde(rename = "comments/commentSet")] - CommentsCommentSet, - #[serde(rename = "comments/commentRemoved")] - CommentsCommentRemoved, - #[serde(rename = "comments/cleared")] - CommentsCleared, + #[serde(rename = "annotations/set")] + AnnotationsSet, + #[serde(rename = "annotations/removed")] + AnnotationsRemoved, + #[serde(rename = "annotations/entrySet")] + AnnotationsEntrySet, + #[serde(rename = "annotations/entryRemoved")] + AnnotationsEntryRemoved, #[serde(rename = "root/terminalsChanged")] RootTerminalsChanged, #[serde(rename = "root/configChanged")] @@ -978,73 +976,64 @@ pub struct ChangesetOperationStatusChangedAction { #[serde(rename_all = "camelCase")] pub struct ChangesetClearedAction {} -/// Upsert a {@link CommentThread} in the comments channel — adds a new -/// thread, or replaces an existing one identified by -/// {@link CommentThread.id}. When replacing, the full thread payload -/// (including its {@link CommentThread.comments | comments} list) is -/// substituted; producers SHOULD prefer {@link CommentsCommentSetAction} -/// for per-comment edits to keep wire updates small. +/// Upsert an {@link Annotation} in the annotations channel — adds a new +/// annotation, or replaces an existing one identified by +/// {@link Annotation.id}. When replacing, the full annotation payload +/// (including its {@link Annotation.entries | entries} list) is +/// substituted; producers SHOULD prefer {@link AnnotationsEntrySetAction} +/// for per-entry edits to keep wire updates small. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommentsThreadSetAction { - /// The new or replacement thread. MUST contain at least one comment. - pub thread: CommentThread, +pub struct AnnotationsSetAction { + /// The new or replacement annotation. MUST contain at least one entry. + pub annotation: Annotation, } -/// Remove a {@link CommentThread} from the channel by its id. +/// Remove an {@link Annotation} from the channel by its id. /// /// The server emits this in two cases: /// 1. The client explicitly invoked -/// {@link DeleteCommentThreadParams | `deleteCommentThread`}. -/// 2. The client invoked {@link DeleteCommentParams | `deleteComment`} on -/// the last remaining comment in the thread — the protocol collapses -/// the thread rather than leaving an empty one behind. +/// {@link DeleteAnnotationParams | `deleteAnnotation`}. +/// 2. The client invoked {@link DeleteAnnotationEntryParams | +/// `deleteAnnotationEntry`} on the last remaining entry in the +/// annotation — the protocol collapses the annotation rather than +/// leaving an empty one behind. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommentsThreadRemovedAction { - /// The {@link CommentThread.id} of the thread to remove. - pub thread_id: String, +pub struct AnnotationsRemovedAction { + /// The {@link Annotation.id} of the annotation to remove. + pub annotation_id: String, } -/// Upsert a {@link Comment} within an existing thread — adds a new -/// comment, or replaces one identified by {@link Comment.id}. If -/// {@link threadId} does not match any current thread the action is a -/// no-op. +/// Upsert an {@link AnnotationEntry} within an existing annotation — adds a +/// new entry, or replaces one identified by {@link AnnotationEntry.id}. If +/// {@link annotationId} does not match any current annotation the action is +/// a no-op. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommentsCommentSetAction { - /// The {@link CommentThread.id} the comment belongs to. - pub thread_id: String, - /// The new or replacement comment. - pub comment: Comment, +pub struct AnnotationsEntrySetAction { + /// The {@link Annotation.id} the entry belongs to. + pub annotation_id: String, + /// The new or replacement entry. + pub entry: AnnotationEntry, } -/// Remove a single {@link Comment} from a thread without collapsing the -/// thread itself. Used when more than one comment remains — the server -/// MUST dispatch {@link CommentsThreadRemovedAction} instead when removing -/// the last comment would otherwise leave the thread empty. +/// Remove a single {@link AnnotationEntry} from an annotation without +/// collapsing the annotation itself. Used when more than one entry remains +/// — the server MUST dispatch {@link AnnotationsRemovedAction} instead when +/// removing the last entry would otherwise leave the annotation empty. /// -/// If either {@link threadId} or {@link commentId} does not match the +/// If either {@link annotationId} or {@link entryId} does not match the /// current state the action is a no-op. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommentsCommentRemovedAction { - /// The {@link CommentThread.id} the comment belongs to. - pub thread_id: String, - /// The {@link Comment.id} to remove. - pub comment_id: String, +pub struct AnnotationsEntryRemovedAction { + /// The {@link Annotation.id} the entry belongs to. + pub annotation_id: String, + /// The {@link AnnotationEntry.id} to remove. + pub entry_id: String, } -/// Drop every thread from the comments channel. -/// -/// Dispatched when the owning session is going away and the channel is -/// about to become un-subscribable. Clients SHOULD release references on -/// receipt and react to the corresponding session-level lifecycle signal -/// (e.g. `root/sessionRemoved`) to fully tear down UI. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CommentsClearedAction {} - /// Fired when the list of known terminals changes. /// /// Full-replacement semantics: the `terminals` array replaces the previous @@ -1309,16 +1298,14 @@ pub enum StateAction { ChangesetOperationStatusChanged(ChangesetOperationStatusChangedAction), #[serde(rename = "changeset/cleared")] ChangesetCleared(ChangesetClearedAction), - #[serde(rename = "comments/threadSet")] - CommentsThreadSet(CommentsThreadSetAction), - #[serde(rename = "comments/threadRemoved")] - CommentsThreadRemoved(CommentsThreadRemovedAction), - #[serde(rename = "comments/commentSet")] - CommentsCommentSet(CommentsCommentSetAction), - #[serde(rename = "comments/commentRemoved")] - CommentsCommentRemoved(CommentsCommentRemovedAction), - #[serde(rename = "comments/cleared")] - CommentsCleared(CommentsClearedAction), + #[serde(rename = "annotations/set")] + AnnotationsSet(AnnotationsSetAction), + #[serde(rename = "annotations/removed")] + AnnotationsRemoved(AnnotationsRemovedAction), + #[serde(rename = "annotations/entrySet")] + AnnotationsEntrySet(AnnotationsEntrySetAction), + #[serde(rename = "annotations/entryRemoved")] + AnnotationsEntryRemoved(AnnotationsEntryRemovedAction), #[serde(rename = "root/terminalsChanged")] RootTerminalsChanged(RootTerminalsChangedAction), #[serde(rename = "terminal/data")] diff --git a/clients/rust/crates/ahp-types/src/commands.rs b/clients/rust/crates/ahp-types/src/commands.rs index 69dffd1a..3abc9c07 100644 --- a/clients/rust/crates/ahp-types/src/commands.rs +++ b/clients/rust/crates/ahp-types/src/commands.rs @@ -15,9 +15,9 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::actions::{ActionEnvelope, StateAction}; #[allow(unused_imports)] use crate::state::{ - AgentSelection, ContentRef, MessageAttachment, ModelSelection, NewComment, SessionActiveClient, - SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, - TerminalClaim, TextRange, Turn, + AgentSelection, ContentRef, MessageAttachment, ModelSelection, NewAnnotationEntry, + SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, + TelemetryCapabilities, TerminalClaim, TextRange, Turn, }; // ─── Enums ──────────────────────────────────────────────────────────── @@ -1050,61 +1050,61 @@ pub struct ChangesetOperationFollowUp { pub external: Option, } -/// Create a new {@link CommentThread} anchored to a file from a specific +/// Create a new {@link Annotation} anchored to a file from a specific /// turn, optionally narrowed to a range within that file. /// -/// The initial comment is required — the protocol forbids empty threads, -/// so thread creation and first-comment creation are fused into one -/// command. The created thread always starts unresolved -/// ({@link CommentThread.resolved} is `false`). The server assigns both -/// {@link CreateCommentThreadResult.threadId} and -/// {@link CreateCommentThreadResult.commentId}, then broadcasts a -/// {@link CommentsThreadSetAction} on the channel. +/// The initial entry is required — the protocol forbids empty annotations, +/// so annotation creation and first-entry creation are fused into one +/// command. The created annotation always starts unresolved +/// ({@link Annotation.resolved} is `false`). The server assigns both +/// {@link CreateAnnotationResult.annotationId} and +/// {@link CreateAnnotationResult.entryId}, then broadcasts an +/// {@link AnnotationsSetAction} on the channel. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CreateCommentThreadParams { +pub struct CreateAnnotationParams { /// Channel URI this command targets. pub channel: Uri, /// Turn whose file versions {@link resource} + {@link range} address. pub turn_id: String, /// Anchored file URI. pub resource: Uri, - /// Anchored range within {@link resource}. When omitted the thread is + /// Anchored range within {@link resource}. When omitted the annotation is /// anchored to the entire file. #[serde(default, skip_serializing_if = "Option::is_none")] pub range: Option, - /// First comment in the thread. The server assigns its {@link Comment.id}. - pub comment: NewComment, + /// First entry in the annotation. The server assigns its {@link AnnotationEntry.id}. + pub entry: NewAnnotationEntry, } -/// Result of {@link CreateCommentThreadParams | `createCommentThread`}. +/// Result of {@link CreateAnnotationParams | `createAnnotation`}. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CreateCommentThreadResult { - /// Server-assigned {@link CommentThread.id}. - pub thread_id: String, - /// Server-assigned {@link Comment.id} of the initial comment. - pub comment_id: String, +pub struct CreateAnnotationResult { + /// Server-assigned {@link Annotation.id}. + pub annotation_id: String, + /// Server-assigned {@link AnnotationEntry.id} of the initial entry. + pub entry_id: String, } -/// Re-anchor or resolve an existing {@link CommentThread} — typically used -/// to re-pin a thread to a different range or a newer turn after an edit, -/// or to mark the thread {@link CommentThread.resolved | resolved} (or -/// re-open it). Comments themselves are not modified by this command; use -/// {@link AddCommentParams | `addComment`}, -/// {@link EditCommentParams | `editComment`}, or -/// {@link DeleteCommentParams | `deleteComment`} for that. +/// Re-anchor or resolve an existing {@link Annotation} — typically used +/// to re-pin an annotation to a different range or a newer turn after an +/// edit, or to mark the annotation {@link Annotation.resolved | resolved} +/// (or re-open it). Entries themselves are not modified by this command; +/// use {@link AddAnnotationEntryParams | `addAnnotationEntry`}, +/// {@link EditAnnotationEntryParams | `editAnnotationEntry`}, or +/// {@link DeleteAnnotationEntryParams | `deleteAnnotationEntry`} for that. /// /// Omitted optional fields preserve their current value. The server -/// echoes the resulting thread state as a {@link CommentsThreadSetAction}. +/// echoes the resulting annotation state as an {@link AnnotationsSetAction}. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct UpdateCommentThreadParams { +pub struct UpdateAnnotationParams { /// Channel URI this command targets. pub channel: Uri, - /// The {@link CommentThread.id} to update. - pub thread_id: String, - /// New {@link CommentThread.turnId}, if changing. + /// The {@link Annotation.id} to update. + pub annotation_id: String, + /// New {@link Annotation.turnId}, if changing. #[serde(default, skip_serializing_if = "Option::is_none")] pub turn_id: Option, /// New anchored file URI, if changing. @@ -1113,79 +1113,78 @@ pub struct UpdateCommentThreadParams { /// New anchored range, if changing. #[serde(default, skip_serializing_if = "Option::is_none")] pub range: Option, - /// New {@link CommentThread.resolved} state, if changing. + /// New {@link Annotation.resolved} state, if changing. #[serde(default, skip_serializing_if = "Option::is_none")] pub resolved: Option, } -/// Delete an entire comment thread (and every comment it contains). The -/// server echoes a {@link CommentsThreadRemovedAction} on the channel. +/// Delete an entire annotation (and every entry it contains). The +/// server echoes an {@link AnnotationsRemovedAction} on the channel. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DeleteCommentThreadParams { +pub struct DeleteAnnotationParams { /// Channel URI this command targets. pub channel: Uri, - /// The {@link CommentThread.id} to delete. - pub thread_id: String, + /// The {@link Annotation.id} to delete. + pub annotation_id: String, } -/// Append a new {@link Comment} to an existing thread. The server assigns -/// the resulting {@link Comment.id} and echoes a -/// {@link CommentsCommentSetAction}. +/// Append a new {@link AnnotationEntry} to an existing annotation. The +/// server assigns the resulting {@link AnnotationEntry.id} and echoes an +/// {@link AnnotationsEntrySetAction}. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AddCommentParams { +pub struct AddAnnotationEntryParams { /// Channel URI this command targets. pub channel: Uri, - /// Thread that receives the new comment. - pub thread_id: String, - /// Comment payload — the server assigns the id. - pub comment: NewComment, + /// Annotation that receives the new entry. + pub annotation_id: String, + /// Entry payload — the server assigns the id. + pub entry: NewAnnotationEntry, } -/// Result of {@link AddCommentParams | `addComment`}. +/// Result of {@link AddAnnotationEntryParams | `addAnnotationEntry`}. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct AddCommentResult { - /// Server-assigned {@link Comment.id} of the new comment. - pub comment_id: String, +pub struct AddAnnotationEntryResult { + /// Server-assigned {@link AnnotationEntry.id} of the new entry. + pub entry_id: String, } -/// Edit the body of an existing comment in place. The server echoes a -/// {@link CommentsCommentSetAction} carrying the updated comment. +/// Edit the body of an existing entry in place. The server echoes an +/// {@link AnnotationsEntrySetAction} carrying the updated entry. /// /// Only the body is mutable through this command; to change -/// {@link Comment.source} or {@link Comment._meta} delete and re-create -/// the comment. +/// {@link AnnotationEntry._meta} delete and re-create the entry. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct EditCommentParams { +pub struct EditAnnotationEntryParams { /// Channel URI this command targets. pub channel: Uri, - /// Enclosing thread. - pub thread_id: String, - /// {@link Comment.id} to edit. - pub comment_id: String, - /// New comment body. See {@link Comment.text}. + /// Enclosing annotation. + pub annotation_id: String, + /// {@link AnnotationEntry.id} to edit. + pub entry_id: String, + /// New entry body. See {@link AnnotationEntry.text}. pub text: StringOrMarkdown, } -/// Remove a single comment from a thread. +/// Remove a single entry from an annotation. /// -/// If the removal would leave the thread empty (i.e. the targeted comment -/// is the only one remaining), the server collapses the thread instead -/// — it dispatches a {@link CommentsThreadRemovedAction} and the thread -/// disappears from {@link CommentsState.threads}. Otherwise the server -/// echoes a {@link CommentsCommentRemovedAction}. +/// If the removal would leave the annotation empty (i.e. the targeted entry +/// is the only one remaining), the server collapses the annotation instead +/// — it dispatches an {@link AnnotationsRemovedAction} and the annotation +/// disappears from {@link AnnotationsState.annotations}. Otherwise the server +/// echoes an {@link AnnotationsEntryRemovedAction}. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DeleteCommentParams { +pub struct DeleteAnnotationEntryParams { /// Channel URI this command targets. pub channel: Uri, - /// Enclosing thread. - pub thread_id: String, - /// {@link Comment.id} to remove. - pub comment_id: String, + /// Enclosing annotation. + pub annotation_id: String, + /// {@link AnnotationEntry.id} to remove. + pub entry_id: String, } // ─── ReconnectResult Union ──────────────────────────────────────────── diff --git a/clients/rust/crates/ahp-types/src/notifications.rs b/clients/rust/crates/ahp-types/src/notifications.rs index ad20f915..826f3bef 100644 --- a/clients/rust/crates/ahp-types/src/notifications.rs +++ b/clients/rust/crates/ahp-types/src/notifications.rs @@ -13,7 +13,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; #[allow(unused_imports)] use crate::state::{ - AgentSelection, ChangesSummary, Changeset, CommentsSummary, FileEdit, ModelSelection, + AgentSelection, AnnotationsSummary, ChangesSummary, Changeset, FileEdit, ModelSelection, ProjectInfo, SessionStatus, SessionSummary, }; @@ -223,10 +223,10 @@ pub struct PartialSessionSummary { /// client to subscribe to a changeset. #[serde(default, skip_serializing_if = "Option::is_none")] pub changes: Option, - /// Lightweight summary of this session's inline comments channel - /// (`ahp-session://comments`). Surfaced so badge UI can render - /// thread / comment counts without subscribing. Absent when the session - /// does not expose a comments channel. + /// Lightweight summary of this session's inline annotations channel + /// (`ahp-session://annotations`). Surfaced so badge UI can render + /// annotation / entry counts without subscribing. Absent when the session + /// does not expose an annotations channel. #[serde(default, skip_serializing_if = "Option::is_none")] - pub comments: Option, + pub annotations: Option, } diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 081d9824..e79f4557 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -145,9 +145,9 @@ pub enum MessageAttachmentKind { /// An attachment that references a resource by URI. #[serde(rename = "resource")] Resource, - /// An attachment that references comment threads on a comments channel. - #[serde(rename = "comments")] - Comments, + /// An attachment that references annotations on an annotations channel. + #[serde(rename = "annotations")] + Annotations, } /// Discriminant for response part types. @@ -873,12 +873,12 @@ pub struct SessionSummary { /// client to subscribe to a changeset. #[serde(default, skip_serializing_if = "Option::is_none")] pub changes: Option, - /// Lightweight summary of this session's inline comments channel - /// (`ahp-session://comments`). Surfaced so badge UI can render - /// thread / comment counts without subscribing. Absent when the session - /// does not expose a comments channel. + /// Lightweight summary of this session's inline annotations channel + /// (`ahp-session://annotations`). Surfaced so badge UI can render + /// annotation / entry counts without subscribing. Absent when the session + /// does not expose an annotations channel. #[serde(default, skip_serializing_if = "Option::is_none")] - pub comments: Option, + pub annotations: Option, } /// Aggregate counts describing the file changes associated with a session. @@ -1428,15 +1428,15 @@ pub struct MessageResourceAttachment { pub selection: Option, } -/// An attachment that references comment threads on a session's comments -/// channel (see {@link CommentsState}). +/// An attachment that references annotations on a session's annotations +/// channel (see {@link AnnotationsState}). /// -/// When {@link threadIds} is omitted the attachment references every thread -/// on the channel; when present it references only the listed -/// {@link CommentThread.id | thread ids}. +/// When {@link annotationIds} is omitted the attachment references every +/// annotation on the channel; when present it references only the listed +/// {@link Annotation.id | annotation ids}. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct MessageCommentsAttachment { +pub struct MessageAnnotationsAttachment { /// A human-readable label for the attachment (e.g. the filename of a file /// attachment). Used for display in UI. pub label: String, @@ -1464,13 +1464,13 @@ pub struct MessageCommentsAttachment { /// host when sending the user message containing the accepted completion. #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, - /// The comments channel URI (typically `ahp-session://comments`). - /// Matches {@link CommentsSummary.resource}. + /// The annotations channel URI (typically `ahp-session://annotations`). + /// Matches {@link AnnotationsSummary.resource}. pub resource: Uri, - /// Specific {@link CommentThread.id | thread ids} to reference. When - /// omitted, the attachment references all threads on the channel. + /// Specific {@link Annotation.id | annotation ids} to reference. When + /// omitted, the attachment references all annotations on the channel. #[serde(default, skip_serializing_if = "Option::is_none")] - pub thread_ids: Option>, + pub annotation_ids: Option>, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -2880,81 +2880,81 @@ pub struct ChangesetOperation { pub error: Option, } -/// Lightweight per-session summary of the comments channel, surfaced on -/// {@link SessionSummary.comments} so badge UI can render thread / comment -/// counts without subscribing to the channel itself. +/// Lightweight per-session summary of the annotations channel, surfaced on +/// {@link SessionSummary.annotations} so badge UI can render annotation / +/// entry counts without subscribing to the channel itself. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommentsSummary { - /// The subscribable comments channel URI for the owning session - /// (typically `ahp-session://comments`). Surfaced explicitly even +pub struct AnnotationsSummary { + /// The subscribable annotations channel URI for the owning session + /// (typically `ahp-session://annotations`). Surfaced explicitly even /// though it is derivable from the session URI so badge UI does not need /// to know the derivation rule. pub resource: Uri, - /// Total number of {@link CommentThread} entries in the channel. - pub thread_count: i64, - /// Total number of {@link Comment} entries across every thread. - pub comment_count: i64, + /// Total number of {@link Annotation} entries in the channel. + pub annotation_count: i64, + /// Total number of {@link AnnotationEntry} entries across every annotation. + pub entry_count: i64, } -/// Full state for a session's comments channel, returned when a client -/// subscribes to an `ahp-session://comments` URI. +/// Full state for a session's annotations channel, returned when a client +/// subscribes to an `ahp-session://annotations` URI. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommentsState { - /// Comment threads in this channel, keyed by {@link CommentThread.id}. - pub threads: Vec, +pub struct AnnotationsState { + /// Annotations in this channel, keyed by {@link Annotation.id}. + pub annotations: Vec, } /// A conversation anchored to a specific file produced by a specific turn, /// optionally narrowed to a range within that file. /// -/// {@link turnId} anchors the thread to the file versions that turn +/// {@link turnId} anchors the annotation to the file versions that turn /// produced, so a later turn that rewrites the same file does not silently -/// invalidate the comment's anchor — clients can resolve {@link resource} +/// invalidate the annotation's anchor — clients can resolve {@link resource} /// and {@link range} against the turn's changeset. When {@link range} is -/// omitted the thread is anchored to the entire file. +/// omitted the annotation is anchored to the entire file. /// -/// Every thread MUST contain at least one {@link Comment}. The server -/// enforces this invariant: {@link CreateCommentThreadParams | -/// `createCommentThread`} requires an initial comment, and deleting the -/// last remaining comment collapses the thread into a -/// {@link CommentsThreadRemovedAction} rather than leaving an empty thread +/// Every annotation MUST contain at least one {@link AnnotationEntry}. The +/// server enforces this invariant: {@link CreateAnnotationParams | +/// `createAnnotation`} requires an initial entry, and deleting the +/// last remaining entry collapses the annotation into a +/// {@link AnnotationsRemovedAction} rather than leaving an empty annotation /// behind. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CommentThread { - /// Stable identifier within the comments channel. Server-assigned. +pub struct Annotation { + /// Stable identifier within the annotations channel. Server-assigned. pub id: String, - /// Turn that produced the file versions this thread is anchored to. + /// Turn that produced the file versions this annotation is anchored to. /// Matches a {@link Turn.id} on the owning session. pub turn_id: String, - /// The file the thread is anchored to. + /// The file the annotation is anchored to. pub resource: Uri, - /// Range within {@link resource} the thread is anchored to. When omitted - /// the thread is anchored to the entire file. + /// Range within {@link resource} the annotation is anchored to. When + /// omitted the annotation is anchored to the entire file. #[serde(default, skip_serializing_if = "Option::is_none")] pub range: Option, - /// Whether the thread has been resolved. Newly created threads are always - /// unresolved (`false`); a client marks a thread resolved (or re-opens it) - /// through {@link UpdateCommentThreadParams | `updateCommentThread`}. + /// Whether the annotation has been resolved. Newly created annotations are + /// always unresolved (`false`); a client marks an annotation resolved (or + /// re-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}. pub resolved: bool, - /// Comments in this thread, in dispatch order (oldest first). MUST + /// Entries in this annotation, in dispatch order (oldest first). MUST /// contain at least one entry. - pub comments: Vec, + pub entries: Vec, /// Server-defined opaque metadata, surfaced to tooling but not /// interpreted by the protocol. #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, } -/// A single comment within a {@link CommentThread}. +/// A single entry within an {@link Annotation}. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Comment { - /// Stable identifier within the enclosing thread. Server-assigned. +pub struct AnnotationEntry { + /// Stable identifier within the enclosing annotation. Server-assigned. pub id: String, - /// Comment body. A bare `string` is rendered as plain text; pass + /// Entry body. A bare `string` is rendered as plain text; pass /// `{ markdown: "…" }` to opt into Markdown rendering. See /// {@link StringOrMarkdown}. pub text: StringOrMarkdown, @@ -2964,16 +2964,16 @@ pub struct Comment { pub meta: Option, } -/// Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`} -/// and {@link AddCommentParams | `addComment`}. The server assigns the -/// resulting {@link Comment.id}. +/// Input shape passed to {@link CreateAnnotationParams | `createAnnotation`} +/// and {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server +/// assigns the resulting {@link AnnotationEntry.id}. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct NewComment { - /// Comment body. See {@link Comment.text}. +pub struct NewAnnotationEntry { + /// Entry body. See {@link AnnotationEntry.text}. pub text: StringOrMarkdown, /// Server-defined opaque metadata, forwarded onto the resulting - /// {@link Comment._meta}. + /// {@link AnnotationEntry._meta}. #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, } @@ -3231,8 +3231,8 @@ pub enum MessageAttachment { EmbeddedResource(MessageEmbeddedResourceAttachment), #[serde(rename = "resource")] Resource(MessageResourceAttachment), - #[serde(rename = "comments")] - Comments(MessageCommentsAttachment), + #[serde(rename = "annotations")] + Annotations(MessageAnnotationsAttachment), /// Unknown or future variant — preserved as raw JSON for round-trip fidelity. /// Reducers treat this as a no-op. #[serde(untagged)] @@ -3330,11 +3330,11 @@ pub enum ToolCallContributor { } /// The state payload of a snapshot — root, session, terminal, -/// changeset, or comments state. +/// changeset, or annotations state. /// /// Deserialized by trying session first (has required `summary`), then /// terminal (has required `content`), then changeset (has required -/// `status` and `files`), then comments (has required `threads`), +/// `status` and `files`), then annotations (has required `annotations`), /// then root. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] @@ -3342,6 +3342,6 @@ pub enum SnapshotState { Session(Box), Terminal(Box), Changeset(Box), - Comments(Box), + Annotations(Box), Root(Box), } diff --git a/clients/rust/crates/ahp/src/multi_host_state_mirror.rs b/clients/rust/crates/ahp/src/multi_host_state_mirror.rs index 09adc03c..c59226b0 100644 --- a/clients/rust/crates/ahp/src/multi_host_state_mirror.rs +++ b/clients/rust/crates/ahp/src/multi_host_state_mirror.rs @@ -37,7 +37,7 @@ use std::collections::HashMap; use ahp_types::actions::ActionEnvelope; use ahp_types::common::ROOT_RESOURCE_URI; use ahp_types::state::{ - ChangesetState, CommentsState, RootState, SessionState, SnapshotState, TerminalState, + AnnotationsState, ChangesetState, RootState, SessionState, SnapshotState, TerminalState, }; use crate::hosts::{HostId, HostSubscriptionEvent}; @@ -86,7 +86,7 @@ pub struct MultiHostStateMirror { sessions: HashMap, terminals: HashMap, changesets: HashMap, - comments: HashMap, + annotations: HashMap, } impl MultiHostStateMirror { @@ -115,9 +115,9 @@ impl MultiHostStateMirror { &self.changesets } - /// Borrow the comments states map keyed by `(host_id, uri)`. - pub fn comments(&self) -> &HashMap { - &self.comments + /// Borrow the annotations states map keyed by `(host_id, uri)`. + pub fn annotations(&self) -> &HashMap { + &self.annotations } /// Convenience: apply a [`HostSubscriptionEvent`] produced by @@ -181,20 +181,20 @@ impl MultiHostStateMirror { SnapshotState::Changeset(state) => { self.changesets.insert(key, state.as_ref().clone()); } - SnapshotState::Comments(state) => { - self.comments.insert(key, state.as_ref().clone()); + SnapshotState::Annotations(state) => { + self.annotations.insert(key, state.as_ref().clone()); } } } /// Drop every slot keyed under `host` — root state, sessions, - /// terminals, changesets, and comments. + /// terminals, changesets, and annotations. pub fn reset_host(&mut self, host: &HostId) { self.root_states.remove(host); self.sessions.retain(|key, _| &key.host_id != host); self.terminals.retain(|key, _| &key.host_id != host); self.changesets.retain(|key, _| &key.host_id != host); - self.comments.retain(|key, _| &key.host_id != host); + self.annotations.retain(|key, _| &key.host_id != host); } /// Drop every host's state. @@ -203,6 +203,6 @@ impl MultiHostStateMirror { self.sessions.clear(); self.terminals.clear(); self.changesets.clear(); - self.comments.clear(); + self.annotations.clear(); } } diff --git a/clients/rust/crates/ahp/src/reducers.rs b/clients/rust/crates/ahp/src/reducers.rs index d580dd60..fb2e0bdd 100644 --- a/clients/rust/crates/ahp/src/reducers.rs +++ b/clients/rust/crates/ahp/src/reducers.rs @@ -1258,7 +1258,7 @@ mod tests { agent: None, working_directory: None, changes: None, - comments: None, + annotations: None, }, lifecycle: SessionLifecycle::Creating, creation_error: None, @@ -1558,8 +1558,8 @@ mod tests { skipped += 1; continue; } - "comments" => { - // comments reducer not yet implemented in Rust; skip. + "annotations" => { + // annotations reducer not yet implemented in Rust; skip. skipped += 1; continue; } diff --git a/clients/rust/crates/ahp/tests/hosts.rs b/clients/rust/crates/ahp/tests/hosts.rs index d2923a84..534b6c06 100644 --- a/clients/rust/crates/ahp/tests/hosts.rs +++ b/clients/rust/crates/ahp/tests/hosts.rs @@ -1033,7 +1033,7 @@ fn make_summary(uri: &str, title: &str, modified_at: i64) -> ahp_types::state::S agent: None, working_directory: None, changes: None, - comments: None, + annotations: 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 c1baa64f..6d73e87f 100644 --- a/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs +++ b/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs @@ -57,7 +57,7 @@ fn session_state(title: &str, resource: &str) -> SessionState { agent: None, working_directory: None, changes: None, - comments: None, + annotations: None, }, lifecycle: SessionLifecycle::Ready, creation_error: None, @@ -366,7 +366,7 @@ fn non_action_event_is_ignored() { agent: None, working_directory: None, changes: None, - comments: None, + annotations: None, }, }), }; diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift index 385216a4..e41d4ac9 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift @@ -55,11 +55,10 @@ public enum ActionType: String, Codable, Sendable { case changesetOperationsChanged = "changeset/operationsChanged" case changesetOperationStatusChanged = "changeset/operationStatusChanged" case changesetCleared = "changeset/cleared" - case commentsThreadSet = "comments/threadSet" - case commentsThreadRemoved = "comments/threadRemoved" - case commentsCommentSet = "comments/commentSet" - case commentsCommentRemoved = "comments/commentRemoved" - case commentsCleared = "comments/cleared" + case annotationsSet = "annotations/set" + case annotationsRemoved = "annotations/removed" + case annotationsEntrySet = "annotations/entrySet" + case annotationsEntryRemoved = "annotations/entryRemoved" case rootTerminalsChanged = "root/terminalsChanged" case rootConfigChanged = "root/configChanged" case terminalData = "terminal/data" @@ -1141,77 +1140,67 @@ public struct ChangesetClearedAction: Codable, Sendable { } } -public struct CommentsThreadSetAction: Codable, Sendable { +public struct AnnotationsSetAction: Codable, Sendable { public var type: ActionType - /// The new or replacement thread. MUST contain at least one comment. - public var thread: CommentThread + /// The new or replacement annotation. MUST contain at least one entry. + public var annotation: Annotation public init( type: ActionType, - thread: CommentThread + annotation: Annotation ) { self.type = type - self.thread = thread + self.annotation = annotation } } -public struct CommentsThreadRemovedAction: Codable, Sendable { +public struct AnnotationsRemovedAction: Codable, Sendable { public var type: ActionType - /// The {@link CommentThread.id} of the thread to remove. - public var threadId: String + /// The {@link Annotation.id} of the annotation to remove. + public var annotationId: String public init( type: ActionType, - threadId: String + annotationId: String ) { self.type = type - self.threadId = threadId + self.annotationId = annotationId } } -public struct CommentsCommentSetAction: Codable, Sendable { +public struct AnnotationsEntrySetAction: Codable, Sendable { public var type: ActionType - /// The {@link CommentThread.id} the comment belongs to. - public var threadId: String - /// The new or replacement comment. - public var comment: Comment + /// The {@link Annotation.id} the entry belongs to. + public var annotationId: String + /// The new or replacement entry. + public var entry: AnnotationEntry public init( type: ActionType, - threadId: String, - comment: Comment + annotationId: String, + entry: AnnotationEntry ) { self.type = type - self.threadId = threadId - self.comment = comment + self.annotationId = annotationId + self.entry = entry } } -public struct CommentsCommentRemovedAction: Codable, Sendable { +public struct AnnotationsEntryRemovedAction: Codable, Sendable { public var type: ActionType - /// The {@link CommentThread.id} the comment belongs to. - public var threadId: String - /// The {@link Comment.id} to remove. - public var commentId: String + /// The {@link Annotation.id} the entry belongs to. + public var annotationId: String + /// The {@link AnnotationEntry.id} to remove. + public var entryId: String public init( type: ActionType, - threadId: String, - commentId: String - ) { - self.type = type - self.threadId = threadId - self.commentId = commentId - } -} - -public struct CommentsClearedAction: Codable, Sendable { - public var type: ActionType - - public init( - type: ActionType + annotationId: String, + entryId: String ) { self.type = type + self.annotationId = annotationId + self.entryId = entryId } } @@ -1483,11 +1472,10 @@ public enum StateAction: Codable, Sendable { case changesetOperationsChanged(ChangesetOperationsChangedAction) case changesetOperationStatusChanged(ChangesetOperationStatusChangedAction) case changesetCleared(ChangesetClearedAction) - case commentsThreadSet(CommentsThreadSetAction) - case commentsThreadRemoved(CommentsThreadRemovedAction) - case commentsCommentSet(CommentsCommentSetAction) - case commentsCommentRemoved(CommentsCommentRemovedAction) - case commentsCleared(CommentsClearedAction) + case annotationsSet(AnnotationsSetAction) + case annotationsRemoved(AnnotationsRemovedAction) + case annotationsEntrySet(AnnotationsEntrySetAction) + case annotationsEntryRemoved(AnnotationsEntryRemovedAction) case rootTerminalsChanged(RootTerminalsChangedAction) case rootConfigChanged(RootConfigChangedAction) case terminalData(TerminalDataAction) @@ -1609,16 +1597,14 @@ public enum StateAction: Codable, Sendable { self = .changesetOperationStatusChanged(try ChangesetOperationStatusChangedAction(from: decoder)) case "changeset/cleared": self = .changesetCleared(try ChangesetClearedAction(from: decoder)) - case "comments/threadSet": - self = .commentsThreadSet(try CommentsThreadSetAction(from: decoder)) - case "comments/threadRemoved": - self = .commentsThreadRemoved(try CommentsThreadRemovedAction(from: decoder)) - case "comments/commentSet": - self = .commentsCommentSet(try CommentsCommentSetAction(from: decoder)) - case "comments/commentRemoved": - self = .commentsCommentRemoved(try CommentsCommentRemovedAction(from: decoder)) - case "comments/cleared": - self = .commentsCleared(try CommentsClearedAction(from: decoder)) + case "annotations/set": + self = .annotationsSet(try AnnotationsSetAction(from: decoder)) + case "annotations/removed": + self = .annotationsRemoved(try AnnotationsRemovedAction(from: decoder)) + case "annotations/entrySet": + self = .annotationsEntrySet(try AnnotationsEntrySetAction(from: decoder)) + case "annotations/entryRemoved": + self = .annotationsEntryRemoved(try AnnotationsEntryRemovedAction(from: decoder)) case "root/terminalsChanged": self = .rootTerminalsChanged(try RootTerminalsChangedAction(from: decoder)) case "root/configChanged": @@ -1703,11 +1689,10 @@ public enum StateAction: Codable, Sendable { case .changesetOperationsChanged(let v): try v.encode(to: encoder) case .changesetOperationStatusChanged(let v): try v.encode(to: encoder) case .changesetCleared(let v): try v.encode(to: encoder) - case .commentsThreadSet(let v): try v.encode(to: encoder) - case .commentsThreadRemoved(let v): try v.encode(to: encoder) - case .commentsCommentSet(let v): try v.encode(to: encoder) - case .commentsCommentRemoved(let v): try v.encode(to: encoder) - case .commentsCleared(let v): try v.encode(to: encoder) + case .annotationsSet(let v): try v.encode(to: encoder) + case .annotationsRemoved(let v): try v.encode(to: encoder) + case .annotationsEntrySet(let v): try v.encode(to: encoder) + case .annotationsEntryRemoved(let v): try v.encode(to: encoder) case .rootTerminalsChanged(let v): try v.encode(to: encoder) case .rootConfigChanged(let v): try v.encode(to: encoder) case .terminalData(let v): try v.encode(to: encoder) diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift index fbed5ece..73f037f8 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift @@ -1174,73 +1174,73 @@ public struct InvokeChangesetOperationResult: Codable, Sendable { } } -public struct CreateCommentThreadParams: Codable, Sendable { +public struct CreateAnnotationParams: Codable, Sendable { /// Channel URI this command targets. public var channel: String /// Turn whose file versions {@link resource} + {@link range} address. public var turnId: String /// Anchored file URI. public var resource: String - /// Anchored range within {@link resource}. When omitted the thread is + /// Anchored range within {@link resource}. When omitted the annotation is /// anchored to the entire file. public var range: TextRange? - /// First comment in the thread. The server assigns its {@link Comment.id}. - public var comment: NewComment + /// First entry in the annotation. The server assigns its {@link AnnotationEntry.id}. + public var entry: NewAnnotationEntry public init( channel: String, turnId: String, resource: String, range: TextRange? = nil, - comment: NewComment + entry: NewAnnotationEntry ) { self.channel = channel self.turnId = turnId self.resource = resource self.range = range - self.comment = comment + self.entry = entry } } -public struct CreateCommentThreadResult: Codable, Sendable { - /// Server-assigned {@link CommentThread.id}. - public var threadId: String - /// Server-assigned {@link Comment.id} of the initial comment. - public var commentId: String +public struct CreateAnnotationResult: Codable, Sendable { + /// Server-assigned {@link Annotation.id}. + public var annotationId: String + /// Server-assigned {@link AnnotationEntry.id} of the initial entry. + public var entryId: String public init( - threadId: String, - commentId: String + annotationId: String, + entryId: String ) { - self.threadId = threadId - self.commentId = commentId + self.annotationId = annotationId + self.entryId = entryId } } -public struct UpdateCommentThreadParams: Codable, Sendable { +public struct UpdateAnnotationParams: Codable, Sendable { /// Channel URI this command targets. public var channel: String - /// The {@link CommentThread.id} to update. - public var threadId: String - /// New {@link CommentThread.turnId}, if changing. + /// The {@link Annotation.id} to update. + public var annotationId: String + /// New {@link Annotation.turnId}, if changing. public var turnId: String? /// New anchored file URI, if changing. public var resource: String? /// New anchored range, if changing. public var range: TextRange? - /// New {@link CommentThread.resolved} state, if changing. + /// New {@link Annotation.resolved} state, if changing. public var resolved: Bool? public init( channel: String, - threadId: String, + annotationId: String, turnId: String? = nil, resource: String? = nil, range: TextRange? = nil, resolved: Bool? = nil ) { self.channel = channel - self.threadId = threadId + self.annotationId = annotationId self.turnId = turnId self.resource = resource self.range = range @@ -1248,90 +1248,90 @@ public struct UpdateCommentThreadParams: Codable, Sendable { } } -public struct DeleteCommentThreadParams: Codable, Sendable { +public struct DeleteAnnotationParams: Codable, Sendable { /// Channel URI this command targets. public var channel: String - /// The {@link CommentThread.id} to delete. - public var threadId: String + /// The {@link Annotation.id} to delete. + public var annotationId: String public init( channel: String, - threadId: String + annotationId: String ) { self.channel = channel - self.threadId = threadId + self.annotationId = annotationId } } -public struct AddCommentParams: Codable, Sendable { +public struct AddAnnotationEntryParams: Codable, Sendable { /// Channel URI this command targets. public var channel: String - /// Thread that receives the new comment. - public var threadId: String - /// Comment payload — the server assigns the id. - public var comment: NewComment + /// Annotation that receives the new entry. + public var annotationId: String + /// Entry payload — the server assigns the id. + public var entry: NewAnnotationEntry public init( channel: String, - threadId: String, - comment: NewComment + annotationId: String, + entry: NewAnnotationEntry ) { self.channel = channel - self.threadId = threadId - self.comment = comment + self.annotationId = annotationId + self.entry = entry } } -public struct AddCommentResult: Codable, Sendable { - /// Server-assigned {@link Comment.id} of the new comment. - public var commentId: String +public struct AddAnnotationEntryResult: Codable, Sendable { + /// Server-assigned {@link AnnotationEntry.id} of the new entry. + public var entryId: String public init( - commentId: String + entryId: String ) { - self.commentId = commentId + self.entryId = entryId } } -public struct EditCommentParams: Codable, Sendable { +public struct EditAnnotationEntryParams: Codable, Sendable { /// Channel URI this command targets. public var channel: String - /// Enclosing thread. - public var threadId: String - /// {@link Comment.id} to edit. - public var commentId: String - /// New comment body. See {@link Comment.text}. + /// Enclosing annotation. + public var annotationId: String + /// {@link AnnotationEntry.id} to edit. + public var entryId: String + /// New entry body. See {@link AnnotationEntry.text}. public var text: StringOrMarkdown public init( channel: String, - threadId: String, - commentId: String, + annotationId: String, + entryId: String, text: StringOrMarkdown ) { self.channel = channel - self.threadId = threadId - self.commentId = commentId + self.annotationId = annotationId + self.entryId = entryId self.text = text } } -public struct DeleteCommentParams: Codable, Sendable { +public struct DeleteAnnotationEntryParams: Codable, Sendable { /// Channel URI this command targets. public var channel: String - /// Enclosing thread. - public var threadId: String - /// {@link Comment.id} to remove. - public var commentId: String + /// Enclosing annotation. + public var annotationId: String + /// {@link AnnotationEntry.id} to remove. + public var entryId: String public init( channel: String, - threadId: String, - commentId: String + annotationId: String, + entryId: String ) { self.channel = channel - self.threadId = threadId - self.commentId = commentId + self.annotationId = annotationId + self.entryId = entryId } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift index 75d23561..739e0234 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift @@ -169,11 +169,11 @@ public struct PartialSessionSummary: Codable, Sendable { /// session's footprint (e.g., for list rendering) without requiring the /// client to subscribe to a changeset. public var changes: ChangesSummary? - /// Lightweight summary of this session's inline comments channel - /// (`ahp-session://comments`). Surfaced so badge UI can render - /// thread / comment counts without subscribing. Absent when the session - /// does not expose a comments channel. - public var comments: CommentsSummary? + /// Lightweight summary of this session's inline annotations channel + /// (`ahp-session://annotations`). Surfaced so badge UI can render + /// annotation / entry counts without subscribing. Absent when the session + /// does not expose an annotations channel. + public var annotations: AnnotationsSummary? public init( resource: String? = nil, @@ -188,7 +188,7 @@ public struct PartialSessionSummary: Codable, Sendable { agent: AgentSelection? = nil, workingDirectory: String? = nil, changes: ChangesSummary? = nil, - comments: CommentsSummary? = nil + annotations: AnnotationsSummary? = nil ) { self.resource = resource self.provider = provider @@ -202,6 +202,6 @@ public struct PartialSessionSummary: Codable, Sendable { self.agent = agent self.workingDirectory = workingDirectory self.changes = changes - self.comments = comments + self.annotations = annotations } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index bf6b3277..0c3b3d01 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -133,8 +133,8 @@ public enum MessageAttachmentKind: String, Codable, Sendable { case embeddedResource = "embeddedResource" /// An attachment that references a resource by URI. case resource = "resource" - /// An attachment that references comment threads on a comments channel. - case comments = "comments" + /// An attachment that references annotations on an annotations channel. + case annotations = "annotations" } /// Discriminant for response part types. @@ -873,11 +873,11 @@ public struct SessionSummary: Codable, Sendable { /// session's footprint (e.g., for list rendering) without requiring the /// client to subscribe to a changeset. public var changes: ChangesSummary? - /// Lightweight summary of this session's inline comments channel - /// (`ahp-session://comments`). Surfaced so badge UI can render - /// thread / comment counts without subscribing. Absent when the session - /// does not expose a comments channel. - public var comments: CommentsSummary? + /// Lightweight summary of this session's inline annotations channel + /// (`ahp-session://annotations`). Surfaced so badge UI can render + /// annotation / entry counts without subscribing. Absent when the session + /// does not expose an annotations channel. + public var annotations: AnnotationsSummary? public init( resource: String, @@ -892,7 +892,7 @@ public struct SessionSummary: Codable, Sendable { agent: AgentSelection? = nil, workingDirectory: String? = nil, changes: ChangesSummary? = nil, - comments: CommentsSummary? = nil + annotations: AnnotationsSummary? = nil ) { self.resource = resource self.provider = provider @@ -906,7 +906,7 @@ public struct SessionSummary: Codable, Sendable { self.agent = agent self.workingDirectory = workingDirectory self.changes = changes - self.comments = comments + self.annotations = annotations } } @@ -1634,7 +1634,7 @@ public struct MessageResourceAttachment: Codable, Sendable { } } -public struct MessageCommentsAttachment: Codable, Sendable { +public struct MessageAnnotationsAttachment: Codable, Sendable { /// A human-readable label for the attachment (e.g. the filename of a file /// attachment). Used for display in UI. public var label: String @@ -1661,12 +1661,12 @@ public struct MessageCommentsAttachment: Codable, Sendable { public var meta: [String: AnyCodable]? /// Discriminant public var type: MessageAttachmentKind - /// The comments channel URI (typically `ahp-session://comments`). - /// Matches {@link CommentsSummary.resource}. + /// The annotations channel URI (typically `ahp-session://annotations`). + /// Matches {@link AnnotationsSummary.resource}. public var resource: String - /// Specific {@link CommentThread.id | thread ids} to reference. When - /// omitted, the attachment references all threads on the channel. - public var threadIds: [String]? + /// Specific {@link Annotation.id | annotation ids} to reference. When + /// omitted, the attachment references all annotations on the channel. + public var annotationIds: [String]? enum CodingKeys: String, CodingKey { case label @@ -1675,7 +1675,7 @@ public struct MessageCommentsAttachment: Codable, Sendable { case meta = "_meta" case type case resource - case threadIds + case annotationIds } public init( @@ -1685,7 +1685,7 @@ public struct MessageCommentsAttachment: Codable, Sendable { meta: [String: AnyCodable]? = nil, type: MessageAttachmentKind, resource: String, - threadIds: [String]? = nil + annotationIds: [String]? = nil ) { self.label = label self.range = range @@ -1693,7 +1693,7 @@ public struct MessageCommentsAttachment: Codable, Sendable { self.meta = meta self.type = type self.resource = resource - self.threadIds = threadIds + self.annotationIds = annotationIds } } @@ -3661,57 +3661,57 @@ public struct ChangesetOperation: Codable, Sendable { } } -public struct CommentsSummary: Codable, Sendable { - /// The subscribable comments channel URI for the owning session - /// (typically `ahp-session://comments`). Surfaced explicitly even +public struct AnnotationsSummary: Codable, Sendable { + /// The subscribable annotations channel URI for the owning session + /// (typically `ahp-session://annotations`). Surfaced explicitly even /// though it is derivable from the session URI so badge UI does not need /// to know the derivation rule. public var resource: String - /// Total number of {@link CommentThread} entries in the channel. - public var threadCount: Int - /// Total number of {@link Comment} entries across every thread. - public var commentCount: Int + /// Total number of {@link Annotation} entries in the channel. + public var annotationCount: Int + /// Total number of {@link AnnotationEntry} entries across every annotation. + public var entryCount: Int public init( resource: String, - threadCount: Int, - commentCount: Int + annotationCount: Int, + entryCount: Int ) { self.resource = resource - self.threadCount = threadCount - self.commentCount = commentCount + self.annotationCount = annotationCount + self.entryCount = entryCount } } -public struct CommentsState: Codable, Sendable { - /// Comment threads in this channel, keyed by {@link CommentThread.id}. - public var threads: [CommentThread] +public struct AnnotationsState: Codable, Sendable { + /// Annotations in this channel, keyed by {@link Annotation.id}. + public var annotations: [Annotation] public init( - threads: [CommentThread] + annotations: [Annotation] ) { - self.threads = threads + self.annotations = annotations } } -public struct CommentThread: Codable, Sendable { - /// Stable identifier within the comments channel. Server-assigned. +public struct Annotation: Codable, Sendable { + /// Stable identifier within the annotations channel. Server-assigned. public var id: String - /// Turn that produced the file versions this thread is anchored to. + /// Turn that produced the file versions this annotation is anchored to. /// Matches a {@link Turn.id} on the owning session. public var turnId: String - /// The file the thread is anchored to. + /// The file the annotation is anchored to. public var resource: String - /// Range within {@link resource} the thread is anchored to. When omitted - /// the thread is anchored to the entire file. + /// Range within {@link resource} the annotation is anchored to. When + /// omitted the annotation is anchored to the entire file. public var range: TextRange? - /// Whether the thread has been resolved. Newly created threads are always - /// unresolved (`false`); a client marks a thread resolved (or re-opens it) - /// through {@link UpdateCommentThreadParams | `updateCommentThread`}. + /// Whether the annotation has been resolved. Newly created annotations are + /// always unresolved (`false`); a client marks an annotation resolved (or + /// re-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}. public var resolved: Bool - /// Comments in this thread, in dispatch order (oldest first). MUST + /// Entries in this annotation, in dispatch order (oldest first). MUST /// contain at least one entry. - public var comments: [Comment] + public var entries: [AnnotationEntry] /// Server-defined opaque metadata, surfaced to tooling but not /// interpreted by the protocol. public var meta: [String: AnyCodable]? @@ -3722,7 +3722,7 @@ public struct CommentThread: Codable, Sendable { case resource case range case resolved - case comments + case entries case meta = "_meta" } @@ -3732,7 +3732,7 @@ public struct CommentThread: Codable, Sendable { resource: String, range: TextRange? = nil, resolved: Bool, - comments: [Comment], + entries: [AnnotationEntry], meta: [String: AnyCodable]? = nil ) { self.id = id @@ -3740,15 +3740,15 @@ public struct CommentThread: Codable, Sendable { self.resource = resource self.range = range self.resolved = resolved - self.comments = comments + self.entries = entries self.meta = meta } } -public struct Comment: Codable, Sendable { - /// Stable identifier within the enclosing thread. Server-assigned. +public struct AnnotationEntry: Codable, Sendable { + /// Stable identifier within the enclosing annotation. Server-assigned. public var id: String - /// Comment body. A bare `string` is rendered as plain text; pass + /// Entry body. A bare `string` is rendered as plain text; pass /// `{ markdown: "…" }` to opt into Markdown rendering. See /// {@link StringOrMarkdown}. public var text: StringOrMarkdown @@ -3773,11 +3773,11 @@ public struct Comment: Codable, Sendable { } } -public struct NewComment: Codable, Sendable { - /// Comment body. See {@link Comment.text}. +public struct NewAnnotationEntry: Codable, Sendable { + /// Entry body. See {@link AnnotationEntry.text}. public var text: StringOrMarkdown /// Server-defined opaque metadata, forwarded onto the resulting - /// {@link Comment._meta}. + /// {@link AnnotationEntry._meta}. public var meta: [String: AnyCodable]? enum CodingKeys: String, CodingKey { @@ -4147,7 +4147,7 @@ public enum MessageAttachment: Codable, Sendable { case simple(SimpleMessageAttachment) case embeddedResource(MessageEmbeddedResourceAttachment) case resource(MessageResourceAttachment) - case comments(MessageCommentsAttachment) + case annotations(MessageAnnotationsAttachment) private enum DiscriminantKey: String, CodingKey { case discriminant = "type" @@ -4163,8 +4163,8 @@ public enum MessageAttachment: Codable, Sendable { self = .embeddedResource(try MessageEmbeddedResourceAttachment(from: decoder)) case "resource": self = .resource(try MessageResourceAttachment(from: decoder)) - case "comments": - self = .comments(try MessageCommentsAttachment(from: decoder)) + case "annotations": + self = .annotations(try MessageAnnotationsAttachment(from: decoder)) default: throw DecodingError.dataCorruptedError(forKey: .discriminant, in: container, debugDescription: "Unknown MessageAttachment discriminant: \(discriminant)") } @@ -4175,7 +4175,7 @@ public enum MessageAttachment: Codable, Sendable { case .simple(let value): try value.encode(to: encoder) case .embeddedResource(let value): try value.encode(to: encoder) case .resource(let value): try value.encode(to: encoder) - case .comments(let value): try value.encode(to: encoder) + case .annotations(let value): try value.encode(to: encoder) } } } @@ -4419,13 +4419,13 @@ public enum ToolResultContent: Codable, Sendable { } } -/// The state payload of a snapshot — root, session, terminal, changeset, or comments state. +/// The state payload of a snapshot — root, session, terminal, changeset, or annotations state. public enum SnapshotState: Codable, Sendable { case root(RootState) case session(SessionState) case terminal(TerminalState) case changeset(ChangesetState) - case comments(CommentsState) + case annotations(AnnotationsState) public init(from decoder: Decoder) throws { // SessionState has required `summary` field, try it first @@ -4435,8 +4435,8 @@ public enum SnapshotState: Codable, Sendable { self = .terminal(terminal) } else if let changeset = try? ChangesetState(from: decoder) { self = .changeset(changeset) - } else if let comments = try? CommentsState(from: decoder) { - self = .comments(comments) + } else if let annotations = try? AnnotationsState(from: decoder) { + self = .annotations(annotations) } else { self = .root(try RootState(from: decoder)) } @@ -4448,7 +4448,7 @@ public enum SnapshotState: Codable, Sendable { case .session(let state): try state.encode(to: encoder) case .terminal(let state): try state.encode(to: encoder) case .changeset(let state): try state.encode(to: encoder) - case .comments(let state): try state.encode(to: encoder) + case .annotations(let state): try state.encode(to: encoder) } } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift index 569d3f10..7f6eed7c 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift @@ -15,7 +15,7 @@ public actor AHPStateMirror { public private(set) var sessions: [String: SessionState] = [:] public private(set) var terminals: [String: TerminalState] = [:] public private(set) var changesets: [String: ChangesetState] = [:] - public private(set) var comments: [String: CommentsState] = [:] + public private(set) var annotations: [String: AnnotationsState] = [:] public init() {} @@ -49,15 +49,15 @@ public actor AHPStateMirror { // mutated only when fresh snapshots arrive. return } - if comments[channel] != nil { - // Comments are also seeded by `applySnapshot` and currently + if annotations[channel] != nil { + // Annotations are also seeded by `applySnapshot` and currently // mutated only when fresh snapshots arrive. return } } /// Seed the mirror from a `Snapshot` — root, session, terminal, - /// changeset, or comments as the snapshot's `state` discriminator + /// changeset, or annotations as the snapshot's `state` discriminator /// dictates. public func applySnapshot(_ snapshot: Snapshot) { switch snapshot.state { @@ -69,8 +69,8 @@ public actor AHPStateMirror { terminals[snapshot.resource] = state case .changeset(let state): changesets[snapshot.resource] = state - case .comments(let state): - comments[snapshot.resource] = state + case .annotations(let state): + annotations[snapshot.resource] = state } } @@ -80,6 +80,6 @@ public actor AHPStateMirror { sessions.removeAll() terminals.removeAll() changesets.removeAll() - comments.removeAll() + annotations.removeAll() } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift index b7f44657..c6518405 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift @@ -47,7 +47,7 @@ public actor MultiHostStateMirror { public private(set) var sessions: [HostedResourceKey: SessionState] = [:] public private(set) var terminals: [HostedResourceKey: TerminalState] = [:] public private(set) var changesets: [HostedResourceKey: ChangesetState] = [:] - public private(set) var comments: [HostedResourceKey: CommentsState] = [:] + public private(set) var annotations: [HostedResourceKey: AnnotationsState] = [:] public init() {} @@ -88,8 +88,8 @@ public actor MultiHostStateMirror { // mutated only when fresh snapshots arrive. return } - if comments[key] != nil { - // Comments are also seeded by `applySnapshot` and currently + if annotations[key] != nil { + // Annotations are also seeded by `applySnapshot` and currently // mutated only when fresh snapshots arrive. return } @@ -98,7 +98,7 @@ public actor MultiHostStateMirror { } /// Seed the mirror from a `Snapshot` scoped to `host` — root, - /// session, terminal, changeset, or comments as the snapshot's + /// session, terminal, changeset, or annotations as the snapshot's /// `state` discriminator dictates. public func applySnapshot(host: HostId, snapshot: Snapshot) { let key = HostedResourceKey(hostId: host, uri: snapshot.resource) @@ -111,21 +111,21 @@ public actor MultiHostStateMirror { terminals[key] = state case .changeset(let state): changesets[key] = state - case .comments(let state): - comments[key] = state + case .annotations(let state): + annotations[key] = state } } /// Reset every slot for `host` — drops the root state, all sessions /// keyed under that host, all terminals keyed under that host, all - /// changesets keyed under that host, and all comments keyed under + /// changesets keyed under that host, and all annotations keyed under /// that host. public func reset(host: HostId) { rootStates.removeValue(forKey: host) sessions = sessions.filter { $0.key.hostId != host } terminals = terminals.filter { $0.key.hostId != host } changesets = changesets.filter { $0.key.hostId != host } - comments = comments.filter { $0.key.hostId != host } + annotations = annotations.filter { $0.key.hostId != host } } /// Reset every host's state. @@ -134,6 +134,6 @@ public actor MultiHostStateMirror { sessions.removeAll() terminals.removeAll() changesets.removeAll() - comments.removeAll() + annotations.removeAll() } } diff --git a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift index 12c65ac7..98432e9c 100644 --- a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift +++ b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift @@ -89,9 +89,9 @@ final class FixtureDrivenReducerTests: XCTestCase { var skipped: [(file: String, description: String, message: String)] = [] for (file, fixture) in Self.fixtures { - // Skip terminal/changeset/comments/resourceWatch fixtures — + // Skip terminal/changeset/annotations/resourceWatch fixtures — // those reducers are not yet implemented in Swift - if fixture.reducer == "terminal" || fixture.reducer == "changeset" || fixture.reducer == "comments" || fixture.reducer == "resourceWatch" { + if fixture.reducer == "terminal" || fixture.reducer == "changeset" || fixture.reducer == "annotations" || fixture.reducer == "resourceWatch" { continue } diff --git a/clients/swift/CHANGELOG.md b/clients/swift/CHANGELOG.md index 44c33bde..b21343a2 100644 --- a/clients/swift/CHANGELOG.md +++ b/clients/swift/CHANGELOG.md @@ -42,18 +42,18 @@ the tag matches the version pinned in [`VERSION`](VERSION). `idle → running → error` lifecycle of a changeset operation. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. -- New comments channel wire types (`ahp-session://comments`): - `CommentsState`, `CommentThread`, `Comment`, `NewComment`, - `CommentsSummary`; the - `comments/threadSet` / `comments/threadRemoved` / `comments/commentSet` - / `comments/commentRemoved` / `comments/cleared` cases on `StateAction`; - `CreateCommentThreadParams/Result`, `UpdateCommentThreadParams`, - `DeleteCommentThreadParams`, `AddCommentParams/Result`, - `EditCommentParams`, `DeleteCommentParams`; and `SnapshotState.comments`. +- New annotations channel wire types (`ahp-session://annotations`): + `AnnotationsState`, `Annotation`, `AnnotationEntry`, `NewAnnotationEntry`, + `AnnotationsSummary`; the + `annotations/set` / `annotations/removed` / `annotations/entrySet` + / `annotations/entryRemoved` cases on `StateAction`; + `CreateAnnotationParams/Result`, `UpdateAnnotationParams`, + `DeleteAnnotationParams`, `AddAnnotationEntryParams/Result`, + `EditAnnotationEntryParams`, `DeleteAnnotationEntryParams`; and `SnapshotState.annotations`. Reducer logic is deferred (matches the changeset/resource-watch parity). -- `MessageCommentsAttachment` (`comments` `MessageAttachment` variant) - referencing comment threads on a session's comments channel by `resource` - URI, optionally narrowed to a `threadIds` array. +- `MessageAnnotationsAttachment` (`annotations` `MessageAttachment` variant) + referencing annotations on a session's annotations channel by `resource` + URI, optionally narrowed to an `annotationIds` array. ### Changed diff --git a/clients/typescript/CHANGELOG.md b/clients/typescript/CHANGELOG.md index 41a52714..0b48b0fa 100644 --- a/clients/typescript/CHANGELOG.md +++ b/clients/typescript/CHANGELOG.md @@ -45,16 +45,16 @@ hotfix escape hatch. `idle → running → error` lifecycle of a changeset operation. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. -- New comments channel (`ahp-session://comments`): `CommentsState`, - `CommentThread`, `Comment`, `NewComment`, `CommentsSummary`, - the `commentsReducer`, the `comments/threadSet`, `comments/threadRemoved`, - `comments/commentSet`, `comments/commentRemoved`, `comments/cleared` actions, - and the `createCommentThread`, `updateCommentThread`, `deleteCommentThread`, - `addComment`, `editComment`, `deleteComment` commands. `SessionSummary.comments` - surfaces the per-session `CommentsSummary` for badge UI. -- `MessageCommentsAttachment` (`comments` `MessageAttachment` variant) - referencing comment threads on a session's comments channel by `resource` - URI, optionally narrowed to a `threadIds` array. +- New annotations channel (`ahp-session://annotations`): `AnnotationsState`, + `Annotation`, `AnnotationEntry`, `NewAnnotationEntry`, `AnnotationsSummary`, + the `annotationsReducer`, the `annotations/set`, `annotations/removed`, + `annotations/entrySet`, and `annotations/entryRemoved` actions, + and the `createAnnotation`, `updateAnnotation`, `deleteAnnotation`, + `addAnnotationEntry`, `editAnnotationEntry`, `deleteAnnotationEntry` commands. `SessionSummary.annotations` + surfaces the per-session `AnnotationsSummary` for badge UI. +- `MessageAnnotationsAttachment` (`annotations` `MessageAttachment` variant) + referencing annotations on a session's annotations channel by `resource` + URI, optionally narrowed to an `annotationIds` array. ### Changed diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index f78ba404..6b988f5b 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -89,7 +89,7 @@ export default withMermaid(defineConfig({ { text: 'Session Channel', link: '/reference/session' }, { text: 'Terminal Channel', link: '/reference/terminal' }, { text: 'Changeset Channel', link: '/reference/changeset' }, - { text: 'Comments Channel', link: '/reference/comments' }, + { text: 'Annotations Channel', link: '/reference/annotations' }, { text: 'Telemetry Channel', link: '/reference/otlp' }, ], }, diff --git a/schema/actions.schema.json b/schema/actions.schema.json index d3d7a2ad..fb449a23 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -1500,94 +1500,82 @@ "type" ] }, - "CommentsThreadSetAction": { + "AnnotationsSetAction": { "type": "object", - "description": "Upsert a {@link CommentThread} in the comments channel — adds a new\nthread, or replaces an existing one identified by\n{@link CommentThread.id}. When replacing, the full thread payload\n(including its {@link CommentThread.comments | comments} list) is\nsubstituted; producers SHOULD prefer {@link CommentsCommentSetAction}\nfor per-comment edits to keep wire updates small.", + "description": "Upsert an {@link Annotation} in the annotations channel — adds a new\nannotation, or replaces an existing one identified by\n{@link Annotation.id}. When replacing, the full annotation payload\n(including its {@link Annotation.entries | entries} list) is\nsubstituted; producers SHOULD prefer {@link AnnotationsEntrySetAction}\nfor per-entry edits to keep wire updates small.", "properties": { "type": { - "$ref": "#/$defs/ActionType.CommentsThreadSet" + "$ref": "#/$defs/ActionType.AnnotationsSet" }, - "thread": { - "$ref": "#/$defs/CommentThread", - "description": "The new or replacement thread. MUST contain at least one comment." + "annotation": { + "$ref": "#/$defs/Annotation", + "description": "The new or replacement annotation. MUST contain at least one entry." } }, "required": [ "type", - "thread" + "annotation" ] }, - "CommentsThreadRemovedAction": { + "AnnotationsRemovedAction": { "type": "object", - "description": "Remove a {@link CommentThread} from the channel by its id.\n\nThe server emits this in two cases:\n1. The client explicitly invoked\n {@link DeleteCommentThreadParams | `deleteCommentThread`}.\n2. The client invoked {@link DeleteCommentParams | `deleteComment`} on\n the last remaining comment in the thread — the protocol collapses\n the thread rather than leaving an empty one behind.", + "description": "Remove an {@link Annotation} from the channel by its id.\n\nThe server emits this in two cases:\n1. The client explicitly invoked\n {@link DeleteAnnotationParams | `deleteAnnotation`}.\n2. The client invoked {@link DeleteAnnotationEntryParams |\n `deleteAnnotationEntry`} on the last remaining entry in the\n annotation — the protocol collapses the annotation rather than\n leaving an empty one behind.", "properties": { "type": { - "$ref": "#/$defs/ActionType.CommentsThreadRemoved" + "$ref": "#/$defs/ActionType.AnnotationsRemoved" }, - "threadId": { + "annotationId": { "type": "string", - "description": "The {@link CommentThread.id} of the thread to remove." + "description": "The {@link Annotation.id} of the annotation to remove." } }, "required": [ "type", - "threadId" + "annotationId" ] }, - "CommentsCommentSetAction": { + "AnnotationsEntrySetAction": { "type": "object", - "description": "Upsert a {@link Comment} within an existing thread — adds a new\ncomment, or replaces one identified by {@link Comment.id}. If\n{@link threadId} does not match any current thread the action is a\nno-op.", + "description": "Upsert an {@link AnnotationEntry} within an existing annotation — adds a\nnew entry, or replaces one identified by {@link AnnotationEntry.id}. If\n{@link annotationId} does not match any current annotation the action is\na no-op.", "properties": { "type": { - "$ref": "#/$defs/ActionType.CommentsCommentSet" + "$ref": "#/$defs/ActionType.AnnotationsEntrySet" }, - "threadId": { + "annotationId": { "type": "string", - "description": "The {@link CommentThread.id} the comment belongs to." + "description": "The {@link Annotation.id} the entry belongs to." }, - "comment": { - "$ref": "#/$defs/Comment", - "description": "The new or replacement comment." + "entry": { + "$ref": "#/$defs/AnnotationEntry", + "description": "The new or replacement entry." } }, "required": [ "type", - "threadId", - "comment" + "annotationId", + "entry" ] }, - "CommentsCommentRemovedAction": { + "AnnotationsEntryRemovedAction": { "type": "object", - "description": "Remove a single {@link Comment} from a thread without collapsing the\nthread itself. Used when more than one comment remains — the server\nMUST dispatch {@link CommentsThreadRemovedAction} instead when removing\nthe last comment would otherwise leave the thread empty.\n\nIf either {@link threadId} or {@link commentId} does not match the\ncurrent state the action is a no-op.", + "description": "Remove a single {@link AnnotationEntry} from an annotation without\ncollapsing the annotation itself. Used when more than one entry remains\n— the server MUST dispatch {@link AnnotationsRemovedAction} instead when\nremoving the last entry would otherwise leave the annotation empty.\n\nIf either {@link annotationId} or {@link entryId} does not match the\ncurrent state the action is a no-op.", "properties": { "type": { - "$ref": "#/$defs/ActionType.CommentsCommentRemoved" + "$ref": "#/$defs/ActionType.AnnotationsEntryRemoved" }, - "threadId": { + "annotationId": { "type": "string", - "description": "The {@link CommentThread.id} the comment belongs to." + "description": "The {@link Annotation.id} the entry belongs to." }, - "commentId": { + "entryId": { "type": "string", - "description": "The {@link Comment.id} to remove." + "description": "The {@link AnnotationEntry.id} to remove." } }, "required": [ "type", - "threadId", - "commentId" - ] - }, - "CommentsClearedAction": { - "type": "object", - "description": "Drop every thread from the comments channel.\n\nDispatched when the owning session is going away and the channel is\nabout to become un-subscribable. Clients SHOULD release references on\nreceipt and react to the corresponding session-level lifecycle signal\n(e.g. `root/sessionRemoved`) to fully tear down UI.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.CommentsCleared" - } - }, - "required": [ - "type" + "annotationId", + "entryId" ] }, "ResourceWatchChangedAction": { @@ -2034,7 +2022,7 @@ "$ref": "#/$defs/ChangesetState" }, { - "$ref": "#/$defs/CommentsState" + "$ref": "#/$defs/AnnotationsState" } ], "description": "The current state of the resource" @@ -2412,9 +2400,9 @@ "$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." }, - "comments": { - "$ref": "#/$defs/CommentsSummary", - "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." + "annotations": { + "$ref": "#/$defs/AnnotationsSummary", + "description": "Lightweight summary of this session's inline annotations channel\n(`ahp-session://annotations`). Surfaced so badge UI can render\nannotation / entry counts without subscribing. Absent when the session\ndoes not expose an annotations channel." } }, "required": [ @@ -3296,9 +3284,9 @@ "type" ] }, - "MessageCommentsAttachment": { + "MessageAnnotationsAttachment": { "type": "object", - "description": "An attachment that references comment threads on a session's comments\nchannel (see {@link CommentsState}).\n\nWhen {@link threadIds} is omitted the attachment references every thread\non the channel; when present it references only the listed\n{@link CommentThread.id | thread ids}.", + "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", "properties": { "label": { "type": "string", @@ -3318,19 +3306,19 @@ "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, "type": { - "$ref": "#/$defs/MessageAttachmentKind.Comments", + "$ref": "#/$defs/MessageAttachmentKind.Annotations", "description": "Discriminant" }, "resource": { "$ref": "#/$defs/URI", - "description": "The comments channel URI (typically `ahp-session://comments`).\nMatches {@link CommentsSummary.resource}." + "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." }, - "threadIds": { + "annotationIds": { "type": "array", "items": { "type": "string" }, - "description": "Specific {@link CommentThread.id | thread ids} to reference. When\nomitted, the attachment references all threads on the channel." + "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." } }, "required": [ @@ -5351,75 +5339,75 @@ "status" ] }, - "CommentsSummary": { + "AnnotationsSummary": { "type": "object", - "description": "Lightweight per-session summary of the comments channel, surfaced on\n{@link SessionSummary.comments} so badge UI can render thread / comment\ncounts without subscribing to the channel itself.", + "description": "Lightweight per-session summary of the annotations channel, surfaced on\n{@link SessionSummary.annotations} so badge UI can render annotation /\nentry counts without subscribing to the channel itself.", "properties": { "resource": { "$ref": "#/$defs/URI", - "description": "The subscribable comments channel URI for the owning session\n(typically `ahp-session://comments`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." + "description": "The subscribable annotations channel URI for the owning session\n(typically `ahp-session://annotations`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." }, - "threadCount": { + "annotationCount": { "type": "number", - "description": "Total number of {@link CommentThread} entries in the channel." + "description": "Total number of {@link Annotation} entries in the channel." }, - "commentCount": { + "entryCount": { "type": "number", - "description": "Total number of {@link Comment} entries across every thread." + "description": "Total number of {@link AnnotationEntry} entries across every annotation." } }, "required": [ "resource", - "threadCount", - "commentCount" + "annotationCount", + "entryCount" ] }, - "CommentsState": { + "AnnotationsState": { "type": "object", - "description": "Full state for a session's comments channel, returned when a client\nsubscribes to an `ahp-session://comments` URI.", + "description": "Full state for a session's annotations channel, returned when a client\nsubscribes to an `ahp-session://annotations` URI.", "properties": { - "threads": { + "annotations": { "type": "array", "items": { - "$ref": "#/$defs/CommentThread" + "$ref": "#/$defs/Annotation" }, - "description": "Comment threads in this channel, keyed by {@link CommentThread.id}." + "description": "Annotations in this channel, keyed by {@link Annotation.id}." } }, "required": [ - "threads" + "annotations" ] }, - "CommentThread": { + "Annotation": { "type": "object", - "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the thread is anchored to the entire file.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. The\nserver enforces this invariant: {@link CreateAnnotationParams |\n`createAnnotation`} requires an initial entry, and deleting the\nlast remaining entry collapses the annotation into a\n{@link AnnotationsRemovedAction} rather than leaving an empty annotation\nbehind.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the comments channel. Server-assigned." + "description": "Stable identifier within the annotations channel. Server-assigned." }, "turnId": { "type": "string", - "description": "Turn that produced the file versions this thread is anchored to.\nMatches a {@link Turn.id} on the owning session." + "description": "Turn that produced the file versions this annotation is anchored to.\nMatches a {@link Turn.id} on the owning session." }, "resource": { "$ref": "#/$defs/URI", - "description": "The file the thread is anchored to." + "description": "The file the annotation is anchored to." }, "range": { "$ref": "#/$defs/TextRange", - "description": "Range within {@link resource} the thread is anchored to. When omitted\nthe thread is anchored to the entire file." + "description": "Range within {@link resource} the annotation is anchored to. When\nomitted the annotation is anchored to the entire file." }, "resolved": { "type": "boolean", - "description": "Whether the thread has been resolved. Newly created threads are always\nunresolved (`false`); a client marks a thread resolved (or re-opens it)\nthrough {@link UpdateCommentThreadParams | `updateCommentThread`}." + "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}." }, - "comments": { + "entries": { "type": "array", "items": { - "$ref": "#/$defs/Comment" + "$ref": "#/$defs/AnnotationEntry" }, - "description": "Comments in this thread, in dispatch order (oldest first). MUST\ncontain at least one entry." + "description": "Entries in this annotation, in dispatch order (oldest first). MUST\ncontain at least one entry." }, "_meta": { "type": "object", @@ -5432,20 +5420,20 @@ "turnId", "resource", "resolved", - "comments" + "entries" ] }, - "Comment": { + "AnnotationEntry": { "type": "object", - "description": "A single comment within a {@link CommentThread}.", + "description": "A single entry within an {@link Annotation}.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the enclosing thread. Server-assigned." + "description": "Stable identifier within the enclosing annotation. Server-assigned." }, "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "Comment body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." + "description": "Entry body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." }, "_meta": { "type": "object", @@ -5458,18 +5446,18 @@ "text" ] }, - "NewComment": { + "NewAnnotationEntry": { "type": "object", - "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", + "description": "Input shape passed to {@link CreateAnnotationParams | `createAnnotation`}\nand {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server\nassigns the resulting {@link AnnotationEntry.id}.", "properties": { "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "Comment body. See {@link Comment.text}." + "description": "Entry body. See {@link AnnotationEntry.text}." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link Comment._meta}." + "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link AnnotationEntry._meta}." } }, "required": [ @@ -5636,7 +5624,7 @@ "$ref": "#/$defs/MessageResourceAttachment" }, { - "$ref": "#/$defs/MessageCommentsAttachment" + "$ref": "#/$defs/MessageAnnotationsAttachment" } ], "description": "An attachment associated with a {@link Message}." @@ -6004,19 +5992,16 @@ "$ref": "#/$defs/ChangesetClearedAction" }, { - "$ref": "#/$defs/CommentsThreadSetAction" - }, - { - "$ref": "#/$defs/CommentsThreadRemovedAction" + "$ref": "#/$defs/AnnotationsSetAction" }, { - "$ref": "#/$defs/CommentsCommentSetAction" + "$ref": "#/$defs/AnnotationsRemovedAction" }, { - "$ref": "#/$defs/CommentsCommentRemovedAction" + "$ref": "#/$defs/AnnotationsEntrySetAction" }, { - "$ref": "#/$defs/CommentsClearedAction" + "$ref": "#/$defs/AnnotationsEntryRemovedAction" }, { "$ref": "#/$defs/TerminalDataAction" diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 4be1f3e6..21016185 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -1116,13 +1116,13 @@ } } }, - "CreateCommentThreadParams": { + "CreateAnnotationParams": { "type": "object", - "description": "Create a new {@link CommentThread} anchored to a file from a specific\nturn, optionally narrowed to a range within that file.\n\nThe initial comment is required — the protocol forbids empty threads,\nso thread creation and first-comment creation are fused into one\ncommand. The created thread always starts unresolved\n({@link CommentThread.resolved} is `false`). The server assigns both\n{@link CreateCommentThreadResult.threadId} and\n{@link CreateCommentThreadResult.commentId}, then broadcasts a\n{@link CommentsThreadSetAction} on the channel.", + "description": "Create a new {@link Annotation} anchored to a file from a specific\nturn, optionally narrowed to a range within that file.\n\nThe initial entry is required — the protocol forbids empty annotations,\nso annotation creation and first-entry creation are fused into one\ncommand. The created annotation always starts unresolved\n({@link Annotation.resolved} is `false`). The server assigns both\n{@link CreateAnnotationResult.annotationId} and\n{@link CreateAnnotationResult.entryId}, then broadcasts an\n{@link AnnotationsSetAction} on the channel.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI, e.g. `ahp-session://comments`." + "description": "The annotations channel URI, e.g. `ahp-session://annotations`." }, "turnId": { "type": "string", @@ -1134,53 +1134,53 @@ }, "range": { "$ref": "#/$defs/TextRange", - "description": "Anchored range within {@link resource}. When omitted the thread is\nanchored to the entire file." + "description": "Anchored range within {@link resource}. When omitted the annotation is\nanchored to the entire file." }, - "comment": { - "$ref": "#/$defs/NewComment", - "description": "First comment in the thread. The server assigns its {@link Comment.id}." + "entry": { + "$ref": "#/$defs/NewAnnotationEntry", + "description": "First entry in the annotation. The server assigns its {@link AnnotationEntry.id}." } }, "required": [ "channel", "turnId", "resource", - "comment" + "entry" ] }, - "CreateCommentThreadResult": { + "CreateAnnotationResult": { "type": "object", - "description": "Result of {@link CreateCommentThreadParams | `createCommentThread`}.", + "description": "Result of {@link CreateAnnotationParams | `createAnnotation`}.", "properties": { - "threadId": { + "annotationId": { "type": "string", - "description": "Server-assigned {@link CommentThread.id}." + "description": "Server-assigned {@link Annotation.id}." }, - "commentId": { + "entryId": { "type": "string", - "description": "Server-assigned {@link Comment.id} of the initial comment." + "description": "Server-assigned {@link AnnotationEntry.id} of the initial entry." } }, "required": [ - "threadId", - "commentId" + "annotationId", + "entryId" ] }, - "UpdateCommentThreadParams": { + "UpdateAnnotationParams": { "type": "object", - "description": "Re-anchor or resolve an existing {@link CommentThread} — typically used\nto re-pin a thread to a different range or a newer turn after an edit,\nor to mark the thread {@link CommentThread.resolved | resolved} (or\nre-open it). Comments themselves are not modified by this command; use\n{@link AddCommentParams | `addComment`},\n{@link EditCommentParams | `editComment`}, or\n{@link DeleteCommentParams | `deleteComment`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting thread state as a {@link CommentsThreadSetAction}.", + "description": "Re-anchor or resolve an existing {@link Annotation} — typically used\nto re-pin an annotation to a different range or a newer turn after an\nedit, or to mark the annotation {@link Annotation.resolved | resolved}\n(or re-open it). Entries themselves are not modified by this command;\nuse {@link AddAnnotationEntryParams | `addAnnotationEntry`},\n{@link EditAnnotationEntryParams | `editAnnotationEntry`}, or\n{@link DeleteAnnotationEntryParams | `deleteAnnotationEntry`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting annotation state as an {@link AnnotationsSetAction}.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI." + "description": "The annotations channel URI." }, - "threadId": { + "annotationId": { "type": "string", - "description": "The {@link CommentThread.id} to update." + "description": "The {@link Annotation.id} to update." }, "turnId": { "type": "string", - "description": "New {@link CommentThread.turnId}, if changing." + "description": "New {@link Annotation.turnId}, if changing." }, "resource": { "$ref": "#/$defs/URI", @@ -1192,117 +1192,117 @@ }, "resolved": { "type": "boolean", - "description": "New {@link CommentThread.resolved} state, if changing." + "description": "New {@link Annotation.resolved} state, if changing." } }, "required": [ "channel", - "threadId" + "annotationId" ] }, - "DeleteCommentThreadParams": { + "DeleteAnnotationParams": { "type": "object", - "description": "Delete an entire comment thread (and every comment it contains). The\nserver echoes a {@link CommentsThreadRemovedAction} on the channel.", + "description": "Delete an entire annotation (and every entry it contains). The\nserver echoes an {@link AnnotationsRemovedAction} on the channel.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI." + "description": "The annotations channel URI." }, - "threadId": { + "annotationId": { "type": "string", - "description": "The {@link CommentThread.id} to delete." + "description": "The {@link Annotation.id} to delete." } }, "required": [ "channel", - "threadId" + "annotationId" ] }, - "AddCommentParams": { + "AddAnnotationEntryParams": { "type": "object", - "description": "Append a new {@link Comment} to an existing thread. The server assigns\nthe resulting {@link Comment.id} and echoes a\n{@link CommentsCommentSetAction}.", + "description": "Append a new {@link AnnotationEntry} to an existing annotation. The\nserver assigns the resulting {@link AnnotationEntry.id} and echoes an\n{@link AnnotationsEntrySetAction}.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI." + "description": "The annotations channel URI." }, - "threadId": { + "annotationId": { "type": "string", - "description": "Thread that receives the new comment." + "description": "Annotation that receives the new entry." }, - "comment": { - "$ref": "#/$defs/NewComment", - "description": "Comment payload — the server assigns the id." + "entry": { + "$ref": "#/$defs/NewAnnotationEntry", + "description": "Entry payload — the server assigns the id." } }, "required": [ "channel", - "threadId", - "comment" + "annotationId", + "entry" ] }, - "AddCommentResult": { + "AddAnnotationEntryResult": { "type": "object", - "description": "Result of {@link AddCommentParams | `addComment`}.", + "description": "Result of {@link AddAnnotationEntryParams | `addAnnotationEntry`}.", "properties": { - "commentId": { + "entryId": { "type": "string", - "description": "Server-assigned {@link Comment.id} of the new comment." + "description": "Server-assigned {@link AnnotationEntry.id} of the new entry." } }, "required": [ - "commentId" + "entryId" ] }, - "EditCommentParams": { + "EditAnnotationEntryParams": { "type": "object", - "description": "Edit the body of an existing comment in place. The server echoes a\n{@link CommentsCommentSetAction} carrying the updated comment.\n\nOnly the body is mutable through this command; to change\n{@link Comment.source} or {@link Comment._meta} delete and re-create\nthe comment.", + "description": "Edit the body of an existing entry in place. The server echoes an\n{@link AnnotationsEntrySetAction} carrying the updated entry.\n\nOnly the body is mutable through this command; to change\n{@link AnnotationEntry._meta} delete and re-create the entry.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI." + "description": "The annotations channel URI." }, - "threadId": { + "annotationId": { "type": "string", - "description": "Enclosing thread." + "description": "Enclosing annotation." }, - "commentId": { + "entryId": { "type": "string", - "description": "{@link Comment.id} to edit." + "description": "{@link AnnotationEntry.id} to edit." }, "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "New comment body. See {@link Comment.text}." + "description": "New entry body. See {@link AnnotationEntry.text}." } }, "required": [ "channel", - "threadId", - "commentId", + "annotationId", + "entryId", "text" ] }, - "DeleteCommentParams": { + "DeleteAnnotationEntryParams": { "type": "object", - "description": "Remove a single comment from a thread.\n\nIf the removal would leave the thread empty (i.e. the targeted comment\nis the only one remaining), the server collapses the thread instead\n— it dispatches a {@link CommentsThreadRemovedAction} and the thread\ndisappears from {@link CommentsState.threads}. Otherwise the server\nechoes a {@link CommentsCommentRemovedAction}.", + "description": "Remove a single entry from an annotation.\n\nIf the removal would leave the annotation empty (i.e. the targeted entry\nis the only one remaining), the server collapses the annotation instead\n— it dispatches an {@link AnnotationsRemovedAction} and the annotation\ndisappears from {@link AnnotationsState.annotations}. Otherwise the server\nechoes an {@link AnnotationsEntryRemovedAction}.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI." + "description": "The annotations channel URI." }, - "threadId": { + "annotationId": { "type": "string", - "description": "Enclosing thread." + "description": "Enclosing annotation." }, - "commentId": { + "entryId": { "type": "string", - "description": "{@link Comment.id} to remove." + "description": "{@link AnnotationEntry.id} to remove." } }, "required": [ "channel", - "threadId", - "commentId" + "annotationId", + "entryId" ] }, "CreateResourceWatchParams": { @@ -1773,7 +1773,7 @@ "$ref": "#/$defs/ChangesetState" }, { - "$ref": "#/$defs/CommentsState" + "$ref": "#/$defs/AnnotationsState" } ], "description": "The current state of the resource" @@ -2151,9 +2151,9 @@ "$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." }, - "comments": { - "$ref": "#/$defs/CommentsSummary", - "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." + "annotations": { + "$ref": "#/$defs/AnnotationsSummary", + "description": "Lightweight summary of this session's inline annotations channel\n(`ahp-session://annotations`). Surfaced so badge UI can render\nannotation / entry counts without subscribing. Absent when the session\ndoes not expose an annotations channel." } }, "required": [ @@ -3035,9 +3035,9 @@ "type" ] }, - "MessageCommentsAttachment": { + "MessageAnnotationsAttachment": { "type": "object", - "description": "An attachment that references comment threads on a session's comments\nchannel (see {@link CommentsState}).\n\nWhen {@link threadIds} is omitted the attachment references every thread\non the channel; when present it references only the listed\n{@link CommentThread.id | thread ids}.", + "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", "properties": { "label": { "type": "string", @@ -3057,19 +3057,19 @@ "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, "type": { - "$ref": "#/$defs/MessageAttachmentKind.Comments", + "$ref": "#/$defs/MessageAttachmentKind.Annotations", "description": "Discriminant" }, "resource": { "$ref": "#/$defs/URI", - "description": "The comments channel URI (typically `ahp-session://comments`).\nMatches {@link CommentsSummary.resource}." + "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." }, - "threadIds": { + "annotationIds": { "type": "array", "items": { "type": "string" }, - "description": "Specific {@link CommentThread.id | thread ids} to reference. When\nomitted, the attachment references all threads on the channel." + "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." } }, "required": [ @@ -5090,75 +5090,75 @@ "status" ] }, - "CommentsSummary": { + "AnnotationsSummary": { "type": "object", - "description": "Lightweight per-session summary of the comments channel, surfaced on\n{@link SessionSummary.comments} so badge UI can render thread / comment\ncounts without subscribing to the channel itself.", + "description": "Lightweight per-session summary of the annotations channel, surfaced on\n{@link SessionSummary.annotations} so badge UI can render annotation /\nentry counts without subscribing to the channel itself.", "properties": { "resource": { "$ref": "#/$defs/URI", - "description": "The subscribable comments channel URI for the owning session\n(typically `ahp-session://comments`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." + "description": "The subscribable annotations channel URI for the owning session\n(typically `ahp-session://annotations`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." }, - "threadCount": { + "annotationCount": { "type": "number", - "description": "Total number of {@link CommentThread} entries in the channel." + "description": "Total number of {@link Annotation} entries in the channel." }, - "commentCount": { + "entryCount": { "type": "number", - "description": "Total number of {@link Comment} entries across every thread." + "description": "Total number of {@link AnnotationEntry} entries across every annotation." } }, "required": [ "resource", - "threadCount", - "commentCount" + "annotationCount", + "entryCount" ] }, - "CommentsState": { + "AnnotationsState": { "type": "object", - "description": "Full state for a session's comments channel, returned when a client\nsubscribes to an `ahp-session://comments` URI.", + "description": "Full state for a session's annotations channel, returned when a client\nsubscribes to an `ahp-session://annotations` URI.", "properties": { - "threads": { + "annotations": { "type": "array", "items": { - "$ref": "#/$defs/CommentThread" + "$ref": "#/$defs/Annotation" }, - "description": "Comment threads in this channel, keyed by {@link CommentThread.id}." + "description": "Annotations in this channel, keyed by {@link Annotation.id}." } }, "required": [ - "threads" + "annotations" ] }, - "CommentThread": { + "Annotation": { "type": "object", - "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the thread is anchored to the entire file.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. The\nserver enforces this invariant: {@link CreateAnnotationParams |\n`createAnnotation`} requires an initial entry, and deleting the\nlast remaining entry collapses the annotation into a\n{@link AnnotationsRemovedAction} rather than leaving an empty annotation\nbehind.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the comments channel. Server-assigned." + "description": "Stable identifier within the annotations channel. Server-assigned." }, "turnId": { "type": "string", - "description": "Turn that produced the file versions this thread is anchored to.\nMatches a {@link Turn.id} on the owning session." + "description": "Turn that produced the file versions this annotation is anchored to.\nMatches a {@link Turn.id} on the owning session." }, "resource": { "$ref": "#/$defs/URI", - "description": "The file the thread is anchored to." + "description": "The file the annotation is anchored to." }, "range": { "$ref": "#/$defs/TextRange", - "description": "Range within {@link resource} the thread is anchored to. When omitted\nthe thread is anchored to the entire file." + "description": "Range within {@link resource} the annotation is anchored to. When\nomitted the annotation is anchored to the entire file." }, "resolved": { "type": "boolean", - "description": "Whether the thread has been resolved. Newly created threads are always\nunresolved (`false`); a client marks a thread resolved (or re-opens it)\nthrough {@link UpdateCommentThreadParams | `updateCommentThread`}." + "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}." }, - "comments": { + "entries": { "type": "array", "items": { - "$ref": "#/$defs/Comment" + "$ref": "#/$defs/AnnotationEntry" }, - "description": "Comments in this thread, in dispatch order (oldest first). MUST\ncontain at least one entry." + "description": "Entries in this annotation, in dispatch order (oldest first). MUST\ncontain at least one entry." }, "_meta": { "type": "object", @@ -5171,20 +5171,20 @@ "turnId", "resource", "resolved", - "comments" + "entries" ] }, - "Comment": { + "AnnotationEntry": { "type": "object", - "description": "A single comment within a {@link CommentThread}.", + "description": "A single entry within an {@link Annotation}.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the enclosing thread. Server-assigned." + "description": "Stable identifier within the enclosing annotation. Server-assigned." }, "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "Comment body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." + "description": "Entry body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." }, "_meta": { "type": "object", @@ -5197,18 +5197,18 @@ "text" ] }, - "NewComment": { + "NewAnnotationEntry": { "type": "object", - "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", + "description": "Input shape passed to {@link CreateAnnotationParams | `createAnnotation`}\nand {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server\nassigns the resulting {@link AnnotationEntry.id}.", "properties": { "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "Comment body. See {@link Comment.text}." + "description": "Entry body. See {@link AnnotationEntry.text}." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link Comment._meta}." + "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link AnnotationEntry._meta}." } }, "required": [ @@ -6788,94 +6788,82 @@ "type" ] }, - "CommentsThreadSetAction": { + "AnnotationsSetAction": { "type": "object", - "description": "Upsert a {@link CommentThread} in the comments channel — adds a new\nthread, or replaces an existing one identified by\n{@link CommentThread.id}. When replacing, the full thread payload\n(including its {@link CommentThread.comments | comments} list) is\nsubstituted; producers SHOULD prefer {@link CommentsCommentSetAction}\nfor per-comment edits to keep wire updates small.", + "description": "Upsert an {@link Annotation} in the annotations channel — adds a new\nannotation, or replaces an existing one identified by\n{@link Annotation.id}. When replacing, the full annotation payload\n(including its {@link Annotation.entries | entries} list) is\nsubstituted; producers SHOULD prefer {@link AnnotationsEntrySetAction}\nfor per-entry edits to keep wire updates small.", "properties": { "type": { - "$ref": "#/$defs/ActionType.CommentsThreadSet" + "$ref": "#/$defs/ActionType.AnnotationsSet" }, - "thread": { - "$ref": "#/$defs/CommentThread", - "description": "The new or replacement thread. MUST contain at least one comment." + "annotation": { + "$ref": "#/$defs/Annotation", + "description": "The new or replacement annotation. MUST contain at least one entry." } }, "required": [ "type", - "thread" + "annotation" ] }, - "CommentsThreadRemovedAction": { + "AnnotationsRemovedAction": { "type": "object", - "description": "Remove a {@link CommentThread} from the channel by its id.\n\nThe server emits this in two cases:\n1. The client explicitly invoked\n {@link DeleteCommentThreadParams | `deleteCommentThread`}.\n2. The client invoked {@link DeleteCommentParams | `deleteComment`} on\n the last remaining comment in the thread — the protocol collapses\n the thread rather than leaving an empty one behind.", + "description": "Remove an {@link Annotation} from the channel by its id.\n\nThe server emits this in two cases:\n1. The client explicitly invoked\n {@link DeleteAnnotationParams | `deleteAnnotation`}.\n2. The client invoked {@link DeleteAnnotationEntryParams |\n `deleteAnnotationEntry`} on the last remaining entry in the\n annotation — the protocol collapses the annotation rather than\n leaving an empty one behind.", "properties": { "type": { - "$ref": "#/$defs/ActionType.CommentsThreadRemoved" + "$ref": "#/$defs/ActionType.AnnotationsRemoved" }, - "threadId": { + "annotationId": { "type": "string", - "description": "The {@link CommentThread.id} of the thread to remove." + "description": "The {@link Annotation.id} of the annotation to remove." } }, "required": [ "type", - "threadId" + "annotationId" ] }, - "CommentsCommentSetAction": { + "AnnotationsEntrySetAction": { "type": "object", - "description": "Upsert a {@link Comment} within an existing thread — adds a new\ncomment, or replaces one identified by {@link Comment.id}. If\n{@link threadId} does not match any current thread the action is a\nno-op.", + "description": "Upsert an {@link AnnotationEntry} within an existing annotation — adds a\nnew entry, or replaces one identified by {@link AnnotationEntry.id}. If\n{@link annotationId} does not match any current annotation the action is\na no-op.", "properties": { "type": { - "$ref": "#/$defs/ActionType.CommentsCommentSet" + "$ref": "#/$defs/ActionType.AnnotationsEntrySet" }, - "threadId": { + "annotationId": { "type": "string", - "description": "The {@link CommentThread.id} the comment belongs to." + "description": "The {@link Annotation.id} the entry belongs to." }, - "comment": { - "$ref": "#/$defs/Comment", - "description": "The new or replacement comment." + "entry": { + "$ref": "#/$defs/AnnotationEntry", + "description": "The new or replacement entry." } }, "required": [ "type", - "threadId", - "comment" + "annotationId", + "entry" ] }, - "CommentsCommentRemovedAction": { + "AnnotationsEntryRemovedAction": { "type": "object", - "description": "Remove a single {@link Comment} from a thread without collapsing the\nthread itself. Used when more than one comment remains — the server\nMUST dispatch {@link CommentsThreadRemovedAction} instead when removing\nthe last comment would otherwise leave the thread empty.\n\nIf either {@link threadId} or {@link commentId} does not match the\ncurrent state the action is a no-op.", + "description": "Remove a single {@link AnnotationEntry} from an annotation without\ncollapsing the annotation itself. Used when more than one entry remains\n— the server MUST dispatch {@link AnnotationsRemovedAction} instead when\nremoving the last entry would otherwise leave the annotation empty.\n\nIf either {@link annotationId} or {@link entryId} does not match the\ncurrent state the action is a no-op.", "properties": { "type": { - "$ref": "#/$defs/ActionType.CommentsCommentRemoved" + "$ref": "#/$defs/ActionType.AnnotationsEntryRemoved" }, - "threadId": { + "annotationId": { "type": "string", - "description": "The {@link CommentThread.id} the comment belongs to." + "description": "The {@link Annotation.id} the entry belongs to." }, - "commentId": { + "entryId": { "type": "string", - "description": "The {@link Comment.id} to remove." + "description": "The {@link AnnotationEntry.id} to remove." } }, "required": [ "type", - "threadId", - "commentId" - ] - }, - "CommentsClearedAction": { - "type": "object", - "description": "Drop every thread from the comments channel.\n\nDispatched when the owning session is going away and the channel is\nabout to become un-subscribable. Clients SHOULD release references on\nreceipt and react to the corresponding session-level lifecycle signal\n(e.g. `root/sessionRemoved`) to fully tear down UI.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.CommentsCleared" - } - }, - "required": [ - "type" + "annotationId", + "entryId" ] }, "ResourceWatchChangedAction": { diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 82cc4690..c854a7c2 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -501,7 +501,7 @@ "$ref": "#/$defs/ChangesetState" }, { - "$ref": "#/$defs/CommentsState" + "$ref": "#/$defs/AnnotationsState" } ], "description": "The current state of the resource" @@ -879,9 +879,9 @@ "$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." }, - "comments": { - "$ref": "#/$defs/CommentsSummary", - "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." + "annotations": { + "$ref": "#/$defs/AnnotationsSummary", + "description": "Lightweight summary of this session's inline annotations channel\n(`ahp-session://annotations`). Surfaced so badge UI can render\nannotation / entry counts without subscribing. Absent when the session\ndoes not expose an annotations channel." } }, "required": [ @@ -1763,9 +1763,9 @@ "type" ] }, - "MessageCommentsAttachment": { + "MessageAnnotationsAttachment": { "type": "object", - "description": "An attachment that references comment threads on a session's comments\nchannel (see {@link CommentsState}).\n\nWhen {@link threadIds} is omitted the attachment references every thread\non the channel; when present it references only the listed\n{@link CommentThread.id | thread ids}.", + "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", "properties": { "label": { "type": "string", @@ -1785,19 +1785,19 @@ "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, "type": { - "$ref": "#/$defs/MessageAttachmentKind.Comments", + "$ref": "#/$defs/MessageAttachmentKind.Annotations", "description": "Discriminant" }, "resource": { "$ref": "#/$defs/URI", - "description": "The comments channel URI (typically `ahp-session://comments`).\nMatches {@link CommentsSummary.resource}." + "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." }, - "threadIds": { + "annotationIds": { "type": "array", "items": { "type": "string" }, - "description": "Specific {@link CommentThread.id | thread ids} to reference. When\nomitted, the attachment references all threads on the channel." + "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." } }, "required": [ @@ -3818,75 +3818,75 @@ "status" ] }, - "CommentsSummary": { + "AnnotationsSummary": { "type": "object", - "description": "Lightweight per-session summary of the comments channel, surfaced on\n{@link SessionSummary.comments} so badge UI can render thread / comment\ncounts without subscribing to the channel itself.", + "description": "Lightweight per-session summary of the annotations channel, surfaced on\n{@link SessionSummary.annotations} so badge UI can render annotation /\nentry counts without subscribing to the channel itself.", "properties": { "resource": { "$ref": "#/$defs/URI", - "description": "The subscribable comments channel URI for the owning session\n(typically `ahp-session://comments`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." + "description": "The subscribable annotations channel URI for the owning session\n(typically `ahp-session://annotations`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." }, - "threadCount": { + "annotationCount": { "type": "number", - "description": "Total number of {@link CommentThread} entries in the channel." + "description": "Total number of {@link Annotation} entries in the channel." }, - "commentCount": { + "entryCount": { "type": "number", - "description": "Total number of {@link Comment} entries across every thread." + "description": "Total number of {@link AnnotationEntry} entries across every annotation." } }, "required": [ "resource", - "threadCount", - "commentCount" + "annotationCount", + "entryCount" ] }, - "CommentsState": { + "AnnotationsState": { "type": "object", - "description": "Full state for a session's comments channel, returned when a client\nsubscribes to an `ahp-session://comments` URI.", + "description": "Full state for a session's annotations channel, returned when a client\nsubscribes to an `ahp-session://annotations` URI.", "properties": { - "threads": { + "annotations": { "type": "array", "items": { - "$ref": "#/$defs/CommentThread" + "$ref": "#/$defs/Annotation" }, - "description": "Comment threads in this channel, keyed by {@link CommentThread.id}." + "description": "Annotations in this channel, keyed by {@link Annotation.id}." } }, "required": [ - "threads" + "annotations" ] }, - "CommentThread": { + "Annotation": { "type": "object", - "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the thread is anchored to the entire file.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. The\nserver enforces this invariant: {@link CreateAnnotationParams |\n`createAnnotation`} requires an initial entry, and deleting the\nlast remaining entry collapses the annotation into a\n{@link AnnotationsRemovedAction} rather than leaving an empty annotation\nbehind.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the comments channel. Server-assigned." + "description": "Stable identifier within the annotations channel. Server-assigned." }, "turnId": { "type": "string", - "description": "Turn that produced the file versions this thread is anchored to.\nMatches a {@link Turn.id} on the owning session." + "description": "Turn that produced the file versions this annotation is anchored to.\nMatches a {@link Turn.id} on the owning session." }, "resource": { "$ref": "#/$defs/URI", - "description": "The file the thread is anchored to." + "description": "The file the annotation is anchored to." }, "range": { "$ref": "#/$defs/TextRange", - "description": "Range within {@link resource} the thread is anchored to. When omitted\nthe thread is anchored to the entire file." + "description": "Range within {@link resource} the annotation is anchored to. When\nomitted the annotation is anchored to the entire file." }, "resolved": { "type": "boolean", - "description": "Whether the thread has been resolved. Newly created threads are always\nunresolved (`false`); a client marks a thread resolved (or re-opens it)\nthrough {@link UpdateCommentThreadParams | `updateCommentThread`}." + "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}." }, - "comments": { + "entries": { "type": "array", "items": { - "$ref": "#/$defs/Comment" + "$ref": "#/$defs/AnnotationEntry" }, - "description": "Comments in this thread, in dispatch order (oldest first). MUST\ncontain at least one entry." + "description": "Entries in this annotation, in dispatch order (oldest first). MUST\ncontain at least one entry." }, "_meta": { "type": "object", @@ -3899,20 +3899,20 @@ "turnId", "resource", "resolved", - "comments" + "entries" ] }, - "Comment": { + "AnnotationEntry": { "type": "object", - "description": "A single comment within a {@link CommentThread}.", + "description": "A single entry within an {@link Annotation}.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the enclosing thread. Server-assigned." + "description": "Stable identifier within the enclosing annotation. Server-assigned." }, "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "Comment body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." + "description": "Entry body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." }, "_meta": { "type": "object", @@ -3925,18 +3925,18 @@ "text" ] }, - "NewComment": { + "NewAnnotationEntry": { "type": "object", - "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", + "description": "Input shape passed to {@link CreateAnnotationParams | `createAnnotation`}\nand {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server\nassigns the resulting {@link AnnotationEntry.id}.", "properties": { "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "Comment body. See {@link Comment.text}." + "description": "Entry body. See {@link AnnotationEntry.text}." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link Comment._meta}." + "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link AnnotationEntry._meta}." } }, "required": [ @@ -5132,13 +5132,13 @@ } } }, - "CreateCommentThreadParams": { + "CreateAnnotationParams": { "type": "object", - "description": "Create a new {@link CommentThread} anchored to a file from a specific\nturn, optionally narrowed to a range within that file.\n\nThe initial comment is required — the protocol forbids empty threads,\nso thread creation and first-comment creation are fused into one\ncommand. The created thread always starts unresolved\n({@link CommentThread.resolved} is `false`). The server assigns both\n{@link CreateCommentThreadResult.threadId} and\n{@link CreateCommentThreadResult.commentId}, then broadcasts a\n{@link CommentsThreadSetAction} on the channel.", + "description": "Create a new {@link Annotation} anchored to a file from a specific\nturn, optionally narrowed to a range within that file.\n\nThe initial entry is required — the protocol forbids empty annotations,\nso annotation creation and first-entry creation are fused into one\ncommand. The created annotation always starts unresolved\n({@link Annotation.resolved} is `false`). The server assigns both\n{@link CreateAnnotationResult.annotationId} and\n{@link CreateAnnotationResult.entryId}, then broadcasts an\n{@link AnnotationsSetAction} on the channel.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI, e.g. `ahp-session://comments`." + "description": "The annotations channel URI, e.g. `ahp-session://annotations`." }, "turnId": { "type": "string", @@ -5150,53 +5150,53 @@ }, "range": { "$ref": "#/$defs/TextRange", - "description": "Anchored range within {@link resource}. When omitted the thread is\nanchored to the entire file." + "description": "Anchored range within {@link resource}. When omitted the annotation is\nanchored to the entire file." }, - "comment": { - "$ref": "#/$defs/NewComment", - "description": "First comment in the thread. The server assigns its {@link Comment.id}." + "entry": { + "$ref": "#/$defs/NewAnnotationEntry", + "description": "First entry in the annotation. The server assigns its {@link AnnotationEntry.id}." } }, "required": [ "channel", "turnId", "resource", - "comment" + "entry" ] }, - "CreateCommentThreadResult": { + "CreateAnnotationResult": { "type": "object", - "description": "Result of {@link CreateCommentThreadParams | `createCommentThread`}.", + "description": "Result of {@link CreateAnnotationParams | `createAnnotation`}.", "properties": { - "threadId": { + "annotationId": { "type": "string", - "description": "Server-assigned {@link CommentThread.id}." + "description": "Server-assigned {@link Annotation.id}." }, - "commentId": { + "entryId": { "type": "string", - "description": "Server-assigned {@link Comment.id} of the initial comment." + "description": "Server-assigned {@link AnnotationEntry.id} of the initial entry." } }, "required": [ - "threadId", - "commentId" + "annotationId", + "entryId" ] }, - "UpdateCommentThreadParams": { + "UpdateAnnotationParams": { "type": "object", - "description": "Re-anchor or resolve an existing {@link CommentThread} — typically used\nto re-pin a thread to a different range or a newer turn after an edit,\nor to mark the thread {@link CommentThread.resolved | resolved} (or\nre-open it). Comments themselves are not modified by this command; use\n{@link AddCommentParams | `addComment`},\n{@link EditCommentParams | `editComment`}, or\n{@link DeleteCommentParams | `deleteComment`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting thread state as a {@link CommentsThreadSetAction}.", + "description": "Re-anchor or resolve an existing {@link Annotation} — typically used\nto re-pin an annotation to a different range or a newer turn after an\nedit, or to mark the annotation {@link Annotation.resolved | resolved}\n(or re-open it). Entries themselves are not modified by this command;\nuse {@link AddAnnotationEntryParams | `addAnnotationEntry`},\n{@link EditAnnotationEntryParams | `editAnnotationEntry`}, or\n{@link DeleteAnnotationEntryParams | `deleteAnnotationEntry`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting annotation state as an {@link AnnotationsSetAction}.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI." + "description": "The annotations channel URI." }, - "threadId": { + "annotationId": { "type": "string", - "description": "The {@link CommentThread.id} to update." + "description": "The {@link Annotation.id} to update." }, "turnId": { "type": "string", - "description": "New {@link CommentThread.turnId}, if changing." + "description": "New {@link Annotation.turnId}, if changing." }, "resource": { "$ref": "#/$defs/URI", @@ -5208,117 +5208,117 @@ }, "resolved": { "type": "boolean", - "description": "New {@link CommentThread.resolved} state, if changing." + "description": "New {@link Annotation.resolved} state, if changing." } }, "required": [ "channel", - "threadId" + "annotationId" ] }, - "DeleteCommentThreadParams": { + "DeleteAnnotationParams": { "type": "object", - "description": "Delete an entire comment thread (and every comment it contains). The\nserver echoes a {@link CommentsThreadRemovedAction} on the channel.", + "description": "Delete an entire annotation (and every entry it contains). The\nserver echoes an {@link AnnotationsRemovedAction} on the channel.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI." + "description": "The annotations channel URI." }, - "threadId": { + "annotationId": { "type": "string", - "description": "The {@link CommentThread.id} to delete." + "description": "The {@link Annotation.id} to delete." } }, "required": [ "channel", - "threadId" + "annotationId" ] }, - "AddCommentParams": { + "AddAnnotationEntryParams": { "type": "object", - "description": "Append a new {@link Comment} to an existing thread. The server assigns\nthe resulting {@link Comment.id} and echoes a\n{@link CommentsCommentSetAction}.", + "description": "Append a new {@link AnnotationEntry} to an existing annotation. The\nserver assigns the resulting {@link AnnotationEntry.id} and echoes an\n{@link AnnotationsEntrySetAction}.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI." + "description": "The annotations channel URI." }, - "threadId": { + "annotationId": { "type": "string", - "description": "Thread that receives the new comment." + "description": "Annotation that receives the new entry." }, - "comment": { - "$ref": "#/$defs/NewComment", - "description": "Comment payload — the server assigns the id." + "entry": { + "$ref": "#/$defs/NewAnnotationEntry", + "description": "Entry payload — the server assigns the id." } }, "required": [ "channel", - "threadId", - "comment" + "annotationId", + "entry" ] }, - "AddCommentResult": { + "AddAnnotationEntryResult": { "type": "object", - "description": "Result of {@link AddCommentParams | `addComment`}.", + "description": "Result of {@link AddAnnotationEntryParams | `addAnnotationEntry`}.", "properties": { - "commentId": { + "entryId": { "type": "string", - "description": "Server-assigned {@link Comment.id} of the new comment." + "description": "Server-assigned {@link AnnotationEntry.id} of the new entry." } }, "required": [ - "commentId" + "entryId" ] }, - "EditCommentParams": { + "EditAnnotationEntryParams": { "type": "object", - "description": "Edit the body of an existing comment in place. The server echoes a\n{@link CommentsCommentSetAction} carrying the updated comment.\n\nOnly the body is mutable through this command; to change\n{@link Comment.source} or {@link Comment._meta} delete and re-create\nthe comment.", + "description": "Edit the body of an existing entry in place. The server echoes an\n{@link AnnotationsEntrySetAction} carrying the updated entry.\n\nOnly the body is mutable through this command; to change\n{@link AnnotationEntry._meta} delete and re-create the entry.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI." + "description": "The annotations channel URI." }, - "threadId": { + "annotationId": { "type": "string", - "description": "Enclosing thread." + "description": "Enclosing annotation." }, - "commentId": { + "entryId": { "type": "string", - "description": "{@link Comment.id} to edit." + "description": "{@link AnnotationEntry.id} to edit." }, "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "New comment body. See {@link Comment.text}." + "description": "New entry body. See {@link AnnotationEntry.text}." } }, "required": [ "channel", - "threadId", - "commentId", + "annotationId", + "entryId", "text" ] }, - "DeleteCommentParams": { + "DeleteAnnotationEntryParams": { "type": "object", - "description": "Remove a single comment from a thread.\n\nIf the removal would leave the thread empty (i.e. the targeted comment\nis the only one remaining), the server collapses the thread instead\n— it dispatches a {@link CommentsThreadRemovedAction} and the thread\ndisappears from {@link CommentsState.threads}. Otherwise the server\nechoes a {@link CommentsCommentRemovedAction}.", + "description": "Remove a single entry from an annotation.\n\nIf the removal would leave the annotation empty (i.e. the targeted entry\nis the only one remaining), the server collapses the annotation instead\n— it dispatches an {@link AnnotationsRemovedAction} and the annotation\ndisappears from {@link AnnotationsState.annotations}. Otherwise the server\nechoes an {@link AnnotationsEntryRemovedAction}.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The comments channel URI." + "description": "The annotations channel URI." }, - "threadId": { + "annotationId": { "type": "string", - "description": "Enclosing thread." + "description": "Enclosing annotation." }, - "commentId": { + "entryId": { "type": "string", - "description": "{@link Comment.id} to remove." + "description": "{@link AnnotationEntry.id} to remove." } }, "required": [ "channel", - "threadId", - "commentId" + "annotationId", + "entryId" ] }, "CreateResourceWatchParams": { diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index b0fbda2f..10724f7b 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -126,9 +126,9 @@ "$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." }, - "comments": { - "$ref": "#/$defs/CommentsSummary", - "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." + "annotations": { + "$ref": "#/$defs/AnnotationsSummary", + "description": "Lightweight summary of this session's inline annotations channel\n(`ahp-session://annotations`). Surfaced so badge UI can render\nannotation / entry counts without subscribing. Absent when the session\ndoes not expose an annotations channel." } }, "description": "Mutable summary fields that changed; omitted fields are unchanged.\n\nIdentity fields (`resource`, `provider`, `createdAt`) never change and\nMUST be omitted by senders; receivers SHOULD ignore them if present." @@ -630,7 +630,7 @@ "$ref": "#/$defs/ChangesetState" }, { - "$ref": "#/$defs/CommentsState" + "$ref": "#/$defs/AnnotationsState" } ], "description": "The current state of the resource" @@ -1008,9 +1008,9 @@ "$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." }, - "comments": { - "$ref": "#/$defs/CommentsSummary", - "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." + "annotations": { + "$ref": "#/$defs/AnnotationsSummary", + "description": "Lightweight summary of this session's inline annotations channel\n(`ahp-session://annotations`). Surfaced so badge UI can render\nannotation / entry counts without subscribing. Absent when the session\ndoes not expose an annotations channel." } }, "required": [ @@ -1892,9 +1892,9 @@ "type" ] }, - "MessageCommentsAttachment": { + "MessageAnnotationsAttachment": { "type": "object", - "description": "An attachment that references comment threads on a session's comments\nchannel (see {@link CommentsState}).\n\nWhen {@link threadIds} is omitted the attachment references every thread\non the channel; when present it references only the listed\n{@link CommentThread.id | thread ids}.", + "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", "properties": { "label": { "type": "string", @@ -1914,19 +1914,19 @@ "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, "type": { - "$ref": "#/$defs/MessageAttachmentKind.Comments", + "$ref": "#/$defs/MessageAttachmentKind.Annotations", "description": "Discriminant" }, "resource": { "$ref": "#/$defs/URI", - "description": "The comments channel URI (typically `ahp-session://comments`).\nMatches {@link CommentsSummary.resource}." + "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." }, - "threadIds": { + "annotationIds": { "type": "array", "items": { "type": "string" }, - "description": "Specific {@link CommentThread.id | thread ids} to reference. When\nomitted, the attachment references all threads on the channel." + "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." } }, "required": [ @@ -3947,75 +3947,75 @@ "status" ] }, - "CommentsSummary": { + "AnnotationsSummary": { "type": "object", - "description": "Lightweight per-session summary of the comments channel, surfaced on\n{@link SessionSummary.comments} so badge UI can render thread / comment\ncounts without subscribing to the channel itself.", + "description": "Lightweight per-session summary of the annotations channel, surfaced on\n{@link SessionSummary.annotations} so badge UI can render annotation /\nentry counts without subscribing to the channel itself.", "properties": { "resource": { "$ref": "#/$defs/URI", - "description": "The subscribable comments channel URI for the owning session\n(typically `ahp-session://comments`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." + "description": "The subscribable annotations channel URI for the owning session\n(typically `ahp-session://annotations`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." }, - "threadCount": { + "annotationCount": { "type": "number", - "description": "Total number of {@link CommentThread} entries in the channel." + "description": "Total number of {@link Annotation} entries in the channel." }, - "commentCount": { + "entryCount": { "type": "number", - "description": "Total number of {@link Comment} entries across every thread." + "description": "Total number of {@link AnnotationEntry} entries across every annotation." } }, "required": [ "resource", - "threadCount", - "commentCount" + "annotationCount", + "entryCount" ] }, - "CommentsState": { + "AnnotationsState": { "type": "object", - "description": "Full state for a session's comments channel, returned when a client\nsubscribes to an `ahp-session://comments` URI.", + "description": "Full state for a session's annotations channel, returned when a client\nsubscribes to an `ahp-session://annotations` URI.", "properties": { - "threads": { + "annotations": { "type": "array", "items": { - "$ref": "#/$defs/CommentThread" + "$ref": "#/$defs/Annotation" }, - "description": "Comment threads in this channel, keyed by {@link CommentThread.id}." + "description": "Annotations in this channel, keyed by {@link Annotation.id}." } }, "required": [ - "threads" + "annotations" ] }, - "CommentThread": { + "Annotation": { "type": "object", - "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the thread is anchored to the entire file.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. The\nserver enforces this invariant: {@link CreateAnnotationParams |\n`createAnnotation`} requires an initial entry, and deleting the\nlast remaining entry collapses the annotation into a\n{@link AnnotationsRemovedAction} rather than leaving an empty annotation\nbehind.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the comments channel. Server-assigned." + "description": "Stable identifier within the annotations channel. Server-assigned." }, "turnId": { "type": "string", - "description": "Turn that produced the file versions this thread is anchored to.\nMatches a {@link Turn.id} on the owning session." + "description": "Turn that produced the file versions this annotation is anchored to.\nMatches a {@link Turn.id} on the owning session." }, "resource": { "$ref": "#/$defs/URI", - "description": "The file the thread is anchored to." + "description": "The file the annotation is anchored to." }, "range": { "$ref": "#/$defs/TextRange", - "description": "Range within {@link resource} the thread is anchored to. When omitted\nthe thread is anchored to the entire file." + "description": "Range within {@link resource} the annotation is anchored to. When\nomitted the annotation is anchored to the entire file." }, "resolved": { "type": "boolean", - "description": "Whether the thread has been resolved. Newly created threads are always\nunresolved (`false`); a client marks a thread resolved (or re-opens it)\nthrough {@link UpdateCommentThreadParams | `updateCommentThread`}." + "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}." }, - "comments": { + "entries": { "type": "array", "items": { - "$ref": "#/$defs/Comment" + "$ref": "#/$defs/AnnotationEntry" }, - "description": "Comments in this thread, in dispatch order (oldest first). MUST\ncontain at least one entry." + "description": "Entries in this annotation, in dispatch order (oldest first). MUST\ncontain at least one entry." }, "_meta": { "type": "object", @@ -4028,20 +4028,20 @@ "turnId", "resource", "resolved", - "comments" + "entries" ] }, - "Comment": { + "AnnotationEntry": { "type": "object", - "description": "A single comment within a {@link CommentThread}.", + "description": "A single entry within an {@link Annotation}.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the enclosing thread. Server-assigned." + "description": "Stable identifier within the enclosing annotation. Server-assigned." }, "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "Comment body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." + "description": "Entry body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." }, "_meta": { "type": "object", @@ -4054,18 +4054,18 @@ "text" ] }, - "NewComment": { + "NewAnnotationEntry": { "type": "object", - "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", + "description": "Input shape passed to {@link CreateAnnotationParams | `createAnnotation`}\nand {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server\nassigns the resulting {@link AnnotationEntry.id}.", "properties": { "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "Comment body. See {@link Comment.text}." + "description": "Entry body. See {@link AnnotationEntry.text}." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link Comment._meta}." + "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link AnnotationEntry._meta}." } }, "required": [ diff --git a/schema/state.schema.json b/schema/state.schema.json index fd5fdcc5..937a2ecd 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -412,7 +412,7 @@ "$ref": "#/$defs/ChangesetState" }, { - "$ref": "#/$defs/CommentsState" + "$ref": "#/$defs/AnnotationsState" } ], "description": "The current state of the resource" @@ -790,9 +790,9 @@ "$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." }, - "comments": { - "$ref": "#/$defs/CommentsSummary", - "description": "Lightweight summary of this session's inline comments channel\n(`ahp-session://comments`). Surfaced so badge UI can render\nthread / comment counts without subscribing. Absent when the session\ndoes not expose a comments channel." + "annotations": { + "$ref": "#/$defs/AnnotationsSummary", + "description": "Lightweight summary of this session's inline annotations channel\n(`ahp-session://annotations`). Surfaced so badge UI can render\nannotation / entry counts without subscribing. Absent when the session\ndoes not expose an annotations channel." } }, "required": [ @@ -1674,9 +1674,9 @@ "type" ] }, - "MessageCommentsAttachment": { + "MessageAnnotationsAttachment": { "type": "object", - "description": "An attachment that references comment threads on a session's comments\nchannel (see {@link CommentsState}).\n\nWhen {@link threadIds} is omitted the attachment references every thread\non the channel; when present it references only the listed\n{@link CommentThread.id | thread ids}.", + "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", "properties": { "label": { "type": "string", @@ -1696,19 +1696,19 @@ "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, "type": { - "$ref": "#/$defs/MessageAttachmentKind.Comments", + "$ref": "#/$defs/MessageAttachmentKind.Annotations", "description": "Discriminant" }, "resource": { "$ref": "#/$defs/URI", - "description": "The comments channel URI (typically `ahp-session://comments`).\nMatches {@link CommentsSummary.resource}." + "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." }, - "threadIds": { + "annotationIds": { "type": "array", "items": { "type": "string" }, - "description": "Specific {@link CommentThread.id | thread ids} to reference. When\nomitted, the attachment references all threads on the channel." + "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." } }, "required": [ @@ -3729,75 +3729,75 @@ "status" ] }, - "CommentsSummary": { + "AnnotationsSummary": { "type": "object", - "description": "Lightweight per-session summary of the comments channel, surfaced on\n{@link SessionSummary.comments} so badge UI can render thread / comment\ncounts without subscribing to the channel itself.", + "description": "Lightweight per-session summary of the annotations channel, surfaced on\n{@link SessionSummary.annotations} so badge UI can render annotation /\nentry counts without subscribing to the channel itself.", "properties": { "resource": { "$ref": "#/$defs/URI", - "description": "The subscribable comments channel URI for the owning session\n(typically `ahp-session://comments`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." + "description": "The subscribable annotations channel URI for the owning session\n(typically `ahp-session://annotations`). Surfaced explicitly even\nthough it is derivable from the session URI so badge UI does not need\nto know the derivation rule." }, - "threadCount": { + "annotationCount": { "type": "number", - "description": "Total number of {@link CommentThread} entries in the channel." + "description": "Total number of {@link Annotation} entries in the channel." }, - "commentCount": { + "entryCount": { "type": "number", - "description": "Total number of {@link Comment} entries across every thread." + "description": "Total number of {@link AnnotationEntry} entries across every annotation." } }, "required": [ "resource", - "threadCount", - "commentCount" + "annotationCount", + "entryCount" ] }, - "CommentsState": { + "AnnotationsState": { "type": "object", - "description": "Full state for a session's comments channel, returned when a client\nsubscribes to an `ahp-session://comments` URI.", + "description": "Full state for a session's annotations channel, returned when a client\nsubscribes to an `ahp-session://annotations` URI.", "properties": { - "threads": { + "annotations": { "type": "array", "items": { - "$ref": "#/$defs/CommentThread" + "$ref": "#/$defs/Annotation" }, - "description": "Comment threads in this channel, keyed by {@link CommentThread.id}." + "description": "Annotations in this channel, keyed by {@link Annotation.id}." } }, "required": [ - "threads" + "annotations" ] }, - "CommentThread": { + "Annotation": { "type": "object", - "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the thread to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the comment's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the thread is anchored to the entire file.\n\nEvery thread MUST contain at least one {@link Comment}. The server\nenforces this invariant: {@link CreateCommentThreadParams |\n`createCommentThread`} requires an initial comment, and deleting the\nlast remaining comment collapses the thread into a\n{@link CommentsThreadRemovedAction} rather than leaving an empty thread\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. The\nserver enforces this invariant: {@link CreateAnnotationParams |\n`createAnnotation`} requires an initial entry, and deleting the\nlast remaining entry collapses the annotation into a\n{@link AnnotationsRemovedAction} rather than leaving an empty annotation\nbehind.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the comments channel. Server-assigned." + "description": "Stable identifier within the annotations channel. Server-assigned." }, "turnId": { "type": "string", - "description": "Turn that produced the file versions this thread is anchored to.\nMatches a {@link Turn.id} on the owning session." + "description": "Turn that produced the file versions this annotation is anchored to.\nMatches a {@link Turn.id} on the owning session." }, "resource": { "$ref": "#/$defs/URI", - "description": "The file the thread is anchored to." + "description": "The file the annotation is anchored to." }, "range": { "$ref": "#/$defs/TextRange", - "description": "Range within {@link resource} the thread is anchored to. When omitted\nthe thread is anchored to the entire file." + "description": "Range within {@link resource} the annotation is anchored to. When\nomitted the annotation is anchored to the entire file." }, "resolved": { "type": "boolean", - "description": "Whether the thread has been resolved. Newly created threads are always\nunresolved (`false`); a client marks a thread resolved (or re-opens it)\nthrough {@link UpdateCommentThreadParams | `updateCommentThread`}." + "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}." }, - "comments": { + "entries": { "type": "array", "items": { - "$ref": "#/$defs/Comment" + "$ref": "#/$defs/AnnotationEntry" }, - "description": "Comments in this thread, in dispatch order (oldest first). MUST\ncontain at least one entry." + "description": "Entries in this annotation, in dispatch order (oldest first). MUST\ncontain at least one entry." }, "_meta": { "type": "object", @@ -3810,20 +3810,20 @@ "turnId", "resource", "resolved", - "comments" + "entries" ] }, - "Comment": { + "AnnotationEntry": { "type": "object", - "description": "A single comment within a {@link CommentThread}.", + "description": "A single entry within an {@link Annotation}.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the enclosing thread. Server-assigned." + "description": "Stable identifier within the enclosing annotation. Server-assigned." }, "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "Comment body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." + "description": "Entry body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." }, "_meta": { "type": "object", @@ -3836,18 +3836,18 @@ "text" ] }, - "NewComment": { + "NewAnnotationEntry": { "type": "object", - "description": "Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`}\nand {@link AddCommentParams | `addComment`}. The server assigns the\nresulting {@link Comment.id}.", + "description": "Input shape passed to {@link CreateAnnotationParams | `createAnnotation`}\nand {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server\nassigns the resulting {@link AnnotationEntry.id}.", "properties": { "text": { "$ref": "#/$defs/StringOrMarkdown", - "description": "Comment body. See {@link Comment.text}." + "description": "Entry body. See {@link AnnotationEntry.text}." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link Comment._meta}." + "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link AnnotationEntry._meta}." } }, "required": [ @@ -4014,7 +4014,7 @@ "$ref": "#/$defs/MessageResourceAttachment" }, { - "$ref": "#/$defs/MessageCommentsAttachment" + "$ref": "#/$defs/MessageAnnotationsAttachment" } ], "description": "An attachment associated with a {@link Message}." diff --git a/scripts/find-protocol-sources.ts b/scripts/find-protocol-sources.ts index f77ee23c..40777394 100644 --- a/scripts/find-protocol-sources.ts +++ b/scripts/find-protocol-sources.ts @@ -20,7 +20,7 @@ export const PROTOCOL_SOURCE_DIRS: readonly string[] = [ 'channels-session', 'channels-terminal', 'channels-changeset', - 'channels-comments', + 'channels-annotations', 'channels-otlp', 'channels-resource-watch', ]; diff --git a/scripts/generate-action-origin.ts b/scripts/generate-action-origin.ts index 31002113..b641f496 100644 --- a/scripts/generate-action-origin.ts +++ b/scripts/generate-action-origin.ts @@ -17,7 +17,7 @@ const GENERATED_HEADER = `// Generated from types/actions.ts — do not edit // Run \`npm run generate\` to regenerate. `; -type ActionScope = 'root' | 'session' | 'terminal' | 'changeset' | 'comments' | 'resourceWatch'; +type ActionScope = 'root' | 'session' | 'terminal' | 'changeset' | 'annotations' | 'resourceWatch'; interface ActionInfo { /** The interface name (e.g. 'RootAgentsChangedAction') */ @@ -152,7 +152,7 @@ export function generateActionOrigin(project: Project, outDir: string): void { const scope: ActionScope = category === 'Root Actions' ? 'root' : category === 'Terminal Actions' ? 'terminal' : category === 'Changeset Actions' ? 'changeset' - : category === 'Comments Actions' ? 'comments' + : category === 'Annotations Actions' ? 'annotations' : category === 'Resource Watch Actions' ? 'resourceWatch' : 'session'; const isClientDispatchable = hasJsDocTag(node as any, 'clientDispatchable'); @@ -203,7 +203,7 @@ export function generateActionOrigin(project: Project, outDir: string): void { const sessionActions = actions.filter(a => a.scope === 'session'); const terminalActions = actions.filter(a => a.scope === 'terminal'); const changesetActions = actions.filter(a => a.scope === 'changeset'); - const commentsActions = actions.filter(a => a.scope === 'comments'); + const annotationsActions = actions.filter(a => a.scope === 'annotations'); const resourceWatchActions = actions.filter(a => a.scope === 'resourceWatch'); const clientRootActions = rootActions.filter(a => a.isClientDispatchable); const serverRootActions = rootActions.filter(a => !a.isClientDispatchable); @@ -213,8 +213,8 @@ export function generateActionOrigin(project: Project, outDir: string): void { const serverTerminalActions = terminalActions.filter(a => !a.isClientDispatchable); const clientChangesetActions = changesetActions.filter(a => a.isClientDispatchable); const serverChangesetActions = changesetActions.filter(a => !a.isClientDispatchable); - const clientCommentsActions = commentsActions.filter(a => a.isClientDispatchable); - const serverCommentsActions = commentsActions.filter(a => !a.isClientDispatchable); + const clientAnnotationsActions = annotationsActions.filter(a => a.isClientDispatchable); + const serverAnnotationsActions = annotationsActions.filter(a => !a.isClientDispatchable); const clientResourceWatchActions = resourceWatchActions.filter(a => a.isClientDispatchable); const serverResourceWatchActions = resourceWatchActions.filter(a => !a.isClientDispatchable); @@ -349,40 +349,40 @@ export function generateActionOrigin(project: Project, outDir: string): void { lines.push(`;`); lines.push(``); - // CommentsAction - lines.push(`/** Union of all comments-scoped actions. */`); - lines.push(`export type CommentsAction =`); - if (commentsActions.length === 0) { + // AnnotationsAction + lines.push(`/** Union of all annotations-scoped actions. */`); + lines.push(`export type AnnotationsAction =`); + if (annotationsActions.length === 0) { lines.push(` never`); } else { - for (let i = 0; i < commentsActions.length; i++) { - lines.push(` | ${commentsActions[i].name}`); + for (let i = 0; i < annotationsActions.length; i++) { + lines.push(` | ${annotationsActions[i].name}`); } } lines.push(`;`); lines.push(``); - // ClientCommentsAction - lines.push(`/** Union of comments actions that clients may dispatch. */`); - lines.push(`export type ClientCommentsAction =`); - if (clientCommentsActions.length === 0) { + // ClientAnnotationsAction + lines.push(`/** Union of annotations actions that clients may dispatch. */`); + lines.push(`export type ClientAnnotationsAction =`); + if (clientAnnotationsActions.length === 0) { lines.push(` never`); } else { - for (let i = 0; i < clientCommentsActions.length; i++) { - lines.push(` | ${clientCommentsActions[i].name}`); + for (let i = 0; i < clientAnnotationsActions.length; i++) { + lines.push(` | ${clientAnnotationsActions[i].name}`); } } lines.push(`;`); lines.push(``); - // ServerCommentsAction - lines.push(`/** Union of comments actions that only the server may produce. */`); - lines.push(`export type ServerCommentsAction =`); - if (serverCommentsActions.length === 0) { + // ServerAnnotationsAction + lines.push(`/** Union of annotations actions that only the server may produce. */`); + lines.push(`export type ServerAnnotationsAction =`); + if (serverAnnotationsActions.length === 0) { lines.push(` never`); } else { - for (let i = 0; i < serverCommentsActions.length; i++) { - lines.push(` | ${serverCommentsActions[i].name}`); + for (let i = 0; i < serverAnnotationsActions.length; i++) { + lines.push(` | ${serverAnnotationsActions[i].name}`); } } lines.push(`;`); diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 1663fa09..aad3fe3e 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -158,7 +158,7 @@ function mapType(tsType: string): string { tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' || tsType === 'RootState | SessionState | TerminalState | ChangesetState' - || tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState' + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | AnnotationsState' ) { return 'SnapshotState'; } @@ -680,7 +680,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'SimpleMessageAttachment' }, { name: 'MessageEmbeddedResourceAttachment' }, { name: 'MessageResourceAttachment' }, - { name: 'MessageCommentsAttachment' }, + { name: 'MessageAnnotationsAttachment' }, { name: 'MarkdownResponsePart' }, { name: 'ContentRef' }, { name: 'ResourceReponsePart', goName: 'ResourceResponsePart' }, @@ -739,11 +739,11 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'ChangesetState' }, { name: 'ChangesetFile' }, { name: 'ChangesetOperation' }, - { name: 'CommentsSummary' }, - { name: 'CommentsState' }, - { name: 'CommentThread' }, - { name: 'Comment' }, - { name: 'NewComment' }, + { name: 'AnnotationsSummary' }, + { name: 'AnnotationsState' }, + { name: 'Annotation' }, + { name: 'AnnotationEntry' }, + { name: 'NewAnnotationEntry' }, { name: 'TelemetryCapabilities' }, { name: 'ResourceWatchState' }, { name: 'ResourceChange' }, @@ -864,7 +864,7 @@ const MESSAGE_ATTACHMENT_UNION: UnionConfig = { { variantName: 'Simple', innerType: 'SimpleMessageAttachment', wireValue: 'simple' }, { variantName: 'EmbeddedResource', innerType: 'MessageEmbeddedResourceAttachment', wireValue: 'embeddedResource' }, { variantName: 'Resource', innerType: 'MessageResourceAttachment', wireValue: 'resource' }, - { variantName: 'Comments', innerType: 'MessageCommentsAttachment', wireValue: 'comments' }, + { variantName: 'Annotations', innerType: 'MessageAnnotationsAttachment', wireValue: 'annotations' }, ], unknown: true, }; @@ -936,15 +936,15 @@ const TOOL_CALL_CONTRIBUTOR_UNION: UnionConfig = { function generateSnapshotState(): string { return `// SnapshotState is the state payload of a snapshot — root, session, -// terminal, changeset, or comments state. The active variant is chosen by which +// terminal, changeset, or annotations state. The active variant is chosen by which // pointer field is non-nil; UnmarshalJSON probes for required fields in -// the canonical order (session → terminal → changeset → comments → root). +// the canonical order (session → terminal → changeset → annotations → root). type SnapshotState struct { \tRoot *RootState \`json:"-"\` \tSession *SessionState \`json:"-"\` \tTerminal *TerminalState \`json:"-"\` \tChangeset *ChangesetState \`json:"-"\` - Comments *CommentsState \`json:"-"\` + Annotations *AnnotationsState \`json:"-"\` } // MarshalJSON encodes whichever variant is currently populated. @@ -956,8 +956,8 @@ func (s SnapshotState) MarshalJSON() ([]byte, error) { \t\treturn json.Marshal(s.Terminal) \tcase s.Changeset != nil: \t\treturn json.Marshal(s.Changeset) - case s.Comments != nil: - return json.Marshal(s.Comments) + case s.Annotations != nil: + return json.Marshal(s.Annotations) \tcase s.Root != nil: \t\treturn json.Marshal(s.Root) \tdefault: @@ -992,12 +992,12 @@ func (s *SnapshotState) UnmarshalJSON(data []byte) error { \t\t\treturn err \t\t} \t\ts.Changeset = &v - case containsAll(probe, "threads"): - var v CommentsState + case containsAll(probe, "annotations"): + var v AnnotationsState if err := json.Unmarshal(data, &v); err != nil { return err } - s.Comments = &v + s.Annotations = &v \tdefault: \t\tvar v RootState \t\tif err := json.Unmarshal(data, &v); err != nil { @@ -1137,11 +1137,10 @@ const ACTION_VARIANTS: { { type: 'changeset/operationsChanged', variantName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', variantName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', variantName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, - { type: 'comments/threadSet', variantName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, - { type: 'comments/threadRemoved', variantName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, - { type: 'comments/commentSet', variantName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, - { type: 'comments/commentRemoved', variantName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, - { type: 'comments/cleared', variantName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, + { type: 'annotations/set', variantName: 'AnnotationsSet', tsInterface: 'AnnotationsSetAction' }, + { type: 'annotations/removed', variantName: 'AnnotationsRemoved', tsInterface: 'AnnotationsRemovedAction' }, + { type: 'annotations/entrySet', variantName: 'AnnotationsEntrySet', tsInterface: 'AnnotationsEntrySetAction' }, + { type: 'annotations/entryRemoved', variantName: 'AnnotationsEntryRemoved', tsInterface: 'AnnotationsEntryRemovedAction' }, { type: 'root/terminalsChanged', variantName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'terminal/data', variantName: 'TerminalData', tsInterface: 'TerminalDataAction' }, { type: 'terminal/input', variantName: 'TerminalInput', tsInterface: 'TerminalInputAction' }, @@ -1287,10 +1286,10 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: str { name: 'CompletionsParams' }, { name: 'CompletionItem' }, { name: 'CompletionsResult' }, { name: 'InvokeChangesetOperationParams' }, { name: 'InvokeChangesetOperationResult' }, { name: 'ChangesetOperationFollowUp' }, - { name: 'CreateCommentThreadParams' }, { name: 'CreateCommentThreadResult' }, - { name: 'UpdateCommentThreadParams' }, { name: 'DeleteCommentThreadParams' }, - { name: 'AddCommentParams' }, { name: 'AddCommentResult' }, - { name: 'EditCommentParams' }, { name: 'DeleteCommentParams' }, + { name: 'CreateAnnotationParams' }, { name: 'CreateAnnotationResult' }, + { name: 'UpdateAnnotationParams' }, { name: 'DeleteAnnotationParams' }, + { name: 'AddAnnotationEntryParams' }, { name: 'AddAnnotationEntryResult' }, + { name: 'EditAnnotationEntryParams' }, { name: 'DeleteAnnotationEntryParams' }, ]; const RECONNECT_RESULT_UNION: UnionConfig = { diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 03f5e1a5..6ef01194 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -141,7 +141,7 @@ function mapType(tsType: string): string { tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' || tsType === 'RootState | SessionState | TerminalState | ChangesetState' - || tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState' + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | AnnotationsState' ) { return 'SnapshotState'; } @@ -638,7 +638,7 @@ internal object StringOrMarkdownSerializer : KSerializer { function generateSnapshotState(): string { return `/** * The state payload of a snapshot — root, session, terminal, changeset, - * or comments state. + * or annotations state. */ @Serializable(with = SnapshotStateSerializer::class) sealed interface SnapshotState { @@ -646,7 +646,7 @@ sealed interface SnapshotState { @JvmInline value class Session(val value: SessionState) : SnapshotState @JvmInline value class Terminal(val value: TerminalState) : SnapshotState @JvmInline value class Changeset(val value: ChangesetState) : SnapshotState - @JvmInline value class Comments(val value: CommentsState) : SnapshotState + @JvmInline value class Annotations(val value: AnnotationsState) : SnapshotState } internal object SnapshotStateSerializer : KSerializer { @@ -661,14 +661,14 @@ internal object SnapshotStateSerializer : KSerializer { ?: error("Expected JsonObject for SnapshotState") // Try the most distinctive shape first. SessionState has required // \`summary\`; ChangesetState has required \`status\` + \`files\`; - // CommentsState has required \`threads\`; TerminalState has \`uri\` + // AnnotationsState has required \`annotations\`; TerminalState has \`uri\` // / \`size\` / \`buffer\`; RootState is the catch-all. return when { obj.containsKey("summary") -> SnapshotState.Session(input.json.decodeFromJsonElement(SessionState.serializer(), element)) obj.containsKey("status") && obj.containsKey("files") -> SnapshotState.Changeset(input.json.decodeFromJsonElement(ChangesetState.serializer(), element)) - obj.containsKey("threads") -> - SnapshotState.Comments(input.json.decodeFromJsonElement(CommentsState.serializer(), element)) + obj.containsKey("annotations") -> + SnapshotState.Annotations(input.json.decodeFromJsonElement(AnnotationsState.serializer(), element)) obj.containsKey("size") || obj.containsKey("uri") || obj.containsKey("buffer") -> SnapshotState.Terminal(input.json.decodeFromJsonElement(TerminalState.serializer(), element)) else -> SnapshotState.Root(input.json.decodeFromJsonElement(RootState.serializer(), element)) @@ -683,7 +683,7 @@ internal object SnapshotStateSerializer : KSerializer { is SnapshotState.Session -> output.json.encodeToJsonElement(SessionState.serializer(), value.value) is SnapshotState.Terminal -> output.json.encodeToJsonElement(TerminalState.serializer(), value.value) is SnapshotState.Changeset -> output.json.encodeToJsonElement(ChangesetState.serializer(), value.value) - is SnapshotState.Comments -> output.json.encodeToJsonElement(CommentsState.serializer(), value.value) + is SnapshotState.Annotations -> output.json.encodeToJsonElement(AnnotationsState.serializer(), value.value) } output.encodeJsonElement(element) } @@ -779,7 +779,7 @@ const STATE_STRUCTS = [ 'SessionInputRequest', 'TextPosition', 'TextRange', 'TextSelection', 'SimpleMessageAttachment', 'MessageEmbeddedResourceAttachment', 'MessageResourceAttachment', - 'MessageCommentsAttachment', + 'MessageAnnotationsAttachment', 'MarkdownResponsePart', 'ContentRef', 'ResourceReponsePart', 'ToolCallResponsePart', 'ReasoningResponsePart', 'SystemNotificationResponsePart', @@ -804,7 +804,7 @@ const STATE_STRUCTS = [ 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', 'Changeset', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', - 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', 'NewComment', + 'AnnotationsSummary', 'AnnotationsState', 'Annotation', 'AnnotationEntry', 'NewAnnotationEntry', 'TelemetryCapabilities', 'ResourceWatchState', 'ResourceChange', ]; @@ -903,7 +903,7 @@ const MESSAGE_ATTACHMENT_UNION: UnionConfig = { { caseName: 'Simple', structName: 'SimpleMessageAttachment', discriminantValue: 'simple' }, { caseName: 'EmbeddedResource', structName: 'MessageEmbeddedResourceAttachment', discriminantValue: 'embeddedResource' }, { caseName: 'Resource', structName: 'MessageResourceAttachment', discriminantValue: 'resource' }, - { caseName: 'Comments', structName: 'MessageCommentsAttachment', discriminantValue: 'comments' }, + { caseName: 'Annotations', structName: 'MessageAnnotationsAttachment', discriminantValue: 'annotations' }, ], unknown: true, }; @@ -1091,11 +1091,10 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'changeset/operationsChanged', caseName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', caseName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', caseName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, - { type: 'comments/threadSet', caseName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, - { type: 'comments/threadRemoved', caseName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, - { type: 'comments/commentSet', caseName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, - { type: 'comments/commentRemoved', caseName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, - { type: 'comments/cleared', caseName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, + { type: 'annotations/set', caseName: 'AnnotationsSet', tsInterface: 'AnnotationsSetAction' }, + { type: 'annotations/removed', caseName: 'AnnotationsRemoved', tsInterface: 'AnnotationsRemovedAction' }, + { type: 'annotations/entrySet', caseName: 'AnnotationsEntrySet', tsInterface: 'AnnotationsEntrySetAction' }, + { type: 'annotations/entryRemoved', caseName: 'AnnotationsEntryRemoved', tsInterface: 'AnnotationsEntryRemovedAction' }, { type: 'root/terminalsChanged', caseName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'root/configChanged', caseName: 'RootConfigChanged', tsInterface: 'RootConfigChangedAction' }, { type: 'terminal/data', caseName: 'TerminalData', tsInterface: 'TerminalDataAction' }, @@ -1274,12 +1273,12 @@ const COMMAND_STRUCTS = [ 'SessionConfigValueItem', 'CompletionsParams', 'CompletionItem', 'CompletionsResult', 'InvokeChangesetOperationParams', 'InvokeChangesetOperationResult', - 'CreateCommentThreadParams', 'CreateCommentThreadResult', - 'UpdateCommentThreadParams', - 'DeleteCommentThreadParams', - 'AddCommentParams', 'AddCommentResult', - 'EditCommentParams', - 'DeleteCommentParams', + 'CreateAnnotationParams', 'CreateAnnotationResult', + 'UpdateAnnotationParams', + 'DeleteAnnotationParams', + 'AddAnnotationEntryParams', 'AddAnnotationEntryResult', + 'EditAnnotationEntryParams', + 'DeleteAnnotationEntryParams', 'ChangesetOperationFollowUp', ]; @@ -1671,23 +1670,23 @@ object AhpCommands { fun invokeChangesetOperation(id: Long, params: InvokeChangesetOperationParams): JsonRpcRequest = JsonRpcRequest(id = id, method = "invokeChangesetOperation", params = params) - fun createCommentThread(id: Long, params: CreateCommentThreadParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "createCommentThread", params = params) + fun createAnnotation(id: Long, params: CreateAnnotationParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "createAnnotation", params = params) - fun updateCommentThread(id: Long, params: UpdateCommentThreadParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "updateCommentThread", params = params) + fun updateAnnotation(id: Long, params: UpdateAnnotationParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "updateAnnotation", params = params) - fun deleteCommentThread(id: Long, params: DeleteCommentThreadParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "deleteCommentThread", params = params) + fun deleteAnnotation(id: Long, params: DeleteAnnotationParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "deleteAnnotation", params = params) - fun addComment(id: Long, params: AddCommentParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "addComment", params = params) + fun addAnnotationEntry(id: Long, params: AddAnnotationEntryParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "addAnnotationEntry", params = params) - fun editComment(id: Long, params: EditCommentParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "editComment", params = params) + fun editAnnotationEntry(id: Long, params: EditAnnotationEntryParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "editAnnotationEntry", params = params) - fun deleteComment(id: Long, params: DeleteCommentParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "deleteComment", params = params) + fun deleteAnnotationEntry(id: Long, params: DeleteAnnotationEntryParams): JsonRpcRequest = + JsonRpcRequest(id = id, method = "deleteAnnotationEntry", params = params) } /** diff --git a/scripts/generate-markdown.ts b/scripts/generate-markdown.ts index 3f1fbf3f..da05e715 100644 --- a/scripts/generate-markdown.ts +++ b/scripts/generate-markdown.ts @@ -53,7 +53,7 @@ const DIR_TO_PAGE: Record = { 'channels-session': 'session', 'channels-terminal': 'terminal', 'channels-changeset': 'changeset', - 'channels-comments': 'comments', + 'channels-annotations': 'annotations', 'channels-otlp': 'otlp', }; @@ -1004,15 +1004,15 @@ function generateChangesetChannelPage(project: Project): string { return lines.join('\n'); } -function generateCommentsChannelPage(project: Project): string { - currentPage = 'comments'; - const stateSf = findChannelSourceFile(project, 'channels-comments', 'state.ts'); - const actionsSf = findChannelSourceFile(project, 'channels-comments', 'actions.ts'); - const commandsSf = findChannelSourceFile(project, 'channels-comments', 'commands.ts'); +function generateAnnotationsChannelPage(project: Project): string { + currentPage = 'annotations'; + const stateSf = findChannelSourceFile(project, 'channels-annotations', 'state.ts'); + const actionsSf = findChannelSourceFile(project, 'channels-annotations', 'actions.ts'); + const commandsSf = findChannelSourceFile(project, 'channels-annotations', 'commands.ts'); const lines: string[] = [GENERATED_HEADER]; - lines.push('# Comments Channel\n'); - lines.push('Reference for the `ahp-session://comments` channel — server-owned comment threads anchored to file ranges within a session turn. Clients mutate comments through commands; servers echo state changes as comments actions.\n'); + lines.push('# Annotations Channel\n'); + lines.push('Reference for the `ahp-session://annotations` channel — server-owned annotations anchored to file ranges within a session turn. Clients mutate annotations through commands; servers echo state changes as annotations actions.\n'); lines.push(schemaLink('state.schema.json')); if (stateSf) { @@ -1021,7 +1021,7 @@ function generateCommentsChannelPage(project: Project): string { } if (actionsSf) { lines.push('## Actions\n'); - lines.push('Mutate `CommentsState`. Scoped to a comments channel URI via the enclosing `ActionEnvelope.channel`.\n'); + lines.push('Mutate `AnnotationsState`. Scoped to an annotations channel URI via the enclosing `ActionEnvelope.channel`.\n'); lines.push(schemaLink('actions.schema.json')); lines.push(emitActionsSection([actionsSf])); } @@ -1273,7 +1273,7 @@ export function generateMarkdownDocs(project: Project, outDir: string): void { { filename: 'session.md', generator: generateSessionChannelPage }, { filename: 'terminal.md', generator: generateTerminalChannelPage }, { filename: 'changeset.md', generator: generateChangesetChannelPage }, - { filename: 'comments.md', generator: generateCommentsChannelPage }, + { filename: 'annotations.md', generator: generateAnnotationsChannelPage }, { filename: 'otlp.md', generator: generateOtlpChannelPage }, { filename: 'messages.md', generator: generateMessagesPage }, { filename: 'error-codes.md', generator: generateErrorCodesPage }, diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index cd251a3a..e20f9f02 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -147,7 +147,7 @@ function mapType(tsType: string, propName?: string, containerName?: string): str if (tsType === 'IRootState | ISessionState' || tsType === 'IRootState | ISessionState | ITerminalState' || tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' || tsType === 'RootState | SessionState | TerminalState | ChangesetState' - || tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState') { + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | AnnotationsState') { return 'SnapshotState'; } @@ -580,7 +580,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'SimpleMessageAttachment', omitDiscriminants: true }, { name: 'MessageEmbeddedResourceAttachment', omitDiscriminants: true }, { name: 'MessageResourceAttachment', omitDiscriminants: true }, - { name: 'MessageCommentsAttachment', omitDiscriminants: true }, + { name: 'MessageAnnotationsAttachment', omitDiscriminants: true }, { name: 'MarkdownResponsePart', omitDiscriminants: true }, { name: 'ContentRef' }, { name: 'ResourceReponsePart', omitDiscriminants: true, rustName: 'ResourceResponsePart' }, @@ -639,11 +639,11 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'ChangesetState' }, { name: 'ChangesetFile' }, { name: 'ChangesetOperation' }, - { name: 'CommentsSummary' }, - { name: 'CommentsState' }, - { name: 'CommentThread' }, - { name: 'Comment' }, - { name: 'NewComment' }, + { name: 'AnnotationsSummary' }, + { name: 'AnnotationsState' }, + { name: 'Annotation' }, + { name: 'AnnotationEntry' }, + { name: 'NewAnnotationEntry' }, { name: 'TelemetryCapabilities' }, { name: 'ResourceWatchState' }, { name: 'ResourceChange' }, @@ -764,7 +764,7 @@ const MESSAGE_ATTACHMENT_UNION: UnionConfig = { { variantName: 'Simple', innerType: 'SimpleMessageAttachment', wireValue: 'simple' }, { variantName: 'EmbeddedResource', innerType: 'MessageEmbeddedResourceAttachment', wireValue: 'embeddedResource' }, { variantName: 'Resource', innerType: 'MessageResourceAttachment', wireValue: 'resource' }, - { variantName: 'Comments', innerType: 'MessageCommentsAttachment', wireValue: 'comments' }, + { variantName: 'Annotations', innerType: 'MessageAnnotationsAttachment', wireValue: 'annotations' }, ], unknown: true, }; @@ -841,11 +841,11 @@ const TOOL_CALL_CONTRIBUTOR_UNION: UnionConfig = { function generateSnapshotState(): string { return `/// The state payload of a snapshot — root, session, terminal, -/// changeset, or comments state. +/// changeset, or annotations state. /// /// Deserialized by trying session first (has required \`summary\`), then /// terminal (has required \`content\`), then changeset (has required -/// \`status\` and \`files\`), then comments (has required \`threads\`), +/// \`status\` and \`files\`), then annotations (has required \`annotations\`), /// then root. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] @@ -853,7 +853,7 @@ pub enum SnapshotState { Session(Box), Terminal(Box), Changeset(Box), - Comments(Box), + Annotations(Box), Root(Box), }`; } @@ -978,11 +978,10 @@ const ACTION_VARIANTS: { { type: 'changeset/operationsChanged', variantName: 'ChangesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', variantName: 'ChangesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', variantName: 'ChangesetCleared', tsInterface: 'ChangesetClearedAction' }, - { type: 'comments/threadSet', variantName: 'CommentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, - { type: 'comments/threadRemoved', variantName: 'CommentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, - { type: 'comments/commentSet', variantName: 'CommentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, - { type: 'comments/commentRemoved', variantName: 'CommentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, - { type: 'comments/cleared', variantName: 'CommentsCleared', tsInterface: 'CommentsClearedAction' }, + { type: 'annotations/set', variantName: 'AnnotationsSet', tsInterface: 'AnnotationsSetAction' }, + { type: 'annotations/removed', variantName: 'AnnotationsRemoved', tsInterface: 'AnnotationsRemovedAction' }, + { type: 'annotations/entrySet', variantName: 'AnnotationsEntrySet', tsInterface: 'AnnotationsEntrySetAction' }, + { type: 'annotations/entryRemoved', variantName: 'AnnotationsEntryRemoved', tsInterface: 'AnnotationsEntryRemovedAction' }, { type: 'root/terminalsChanged', variantName: 'RootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'terminal/data', variantName: 'TerminalData', tsInterface: 'TerminalDataAction' }, { type: 'terminal/input', variantName: 'TerminalInput', tsInterface: 'TerminalInputAction' }, @@ -1033,7 +1032,7 @@ pub struct SessionToolCallConfirmedAction { function generateActionsFile(project: Project): string { const lines: string[] = [GENERATED_HEADER]; - lines.push('use crate::state::{AgentInfo, AgentSelection, Comment, CommentThread, ConfirmationOption, Customization, ErrorInfo, McpServerState, ModelSelection, ResponsePart, SessionActiveClient, SessionInputAnswer, SessionInputRequest, SessionInputResponseKind, TerminalClaim, TerminalInfo, ToolCallContributor, ToolCallResult, ToolCallConfirmationReason, ToolCallCancellationReason, ToolDefinition, ToolResultContent, UsageInfo, Message, PendingMessageKind, ChangesetStatus, ChangesetFile, ChangesetOperation, ChangesetOperationStatus, Changeset};'); + lines.push('use crate::state::{AgentInfo, AgentSelection, Annotation, AnnotationEntry, ConfirmationOption, Customization, ErrorInfo, McpServerState, ModelSelection, ResponsePart, SessionActiveClient, SessionInputAnswer, SessionInputRequest, SessionInputResponseKind, TerminalClaim, TerminalInfo, ToolCallContributor, ToolCallResult, ToolCallConfirmationReason, ToolCallCancellationReason, ToolDefinition, ToolResultContent, UsageInfo, Message, PendingMessageKind, ChangesetStatus, ChangesetFile, ChangesetOperation, ChangesetOperationStatus, Changeset};'); lines.push(''); // ActionType enum @@ -1143,12 +1142,12 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: s { name: 'CompletionsParams' }, { name: 'CompletionItem' }, { name: 'CompletionsResult' }, { name: 'InvokeChangesetOperationParams' }, { name: 'InvokeChangesetOperationResult' }, { name: 'ChangesetOperationFollowUp' }, - { name: 'CreateCommentThreadParams' }, { name: 'CreateCommentThreadResult' }, - { name: 'UpdateCommentThreadParams' }, - { name: 'DeleteCommentThreadParams' }, - { name: 'AddCommentParams' }, { name: 'AddCommentResult' }, - { name: 'EditCommentParams' }, - { name: 'DeleteCommentParams' }, + { name: 'CreateAnnotationParams' }, { name: 'CreateAnnotationResult' }, + { name: 'UpdateAnnotationParams' }, + { name: 'DeleteAnnotationParams' }, + { name: 'AddAnnotationEntryParams' }, { name: 'AddAnnotationEntryResult' }, + { name: 'EditAnnotationEntryParams' }, + { name: 'DeleteAnnotationEntryParams' }, ]; const RECONNECT_RESULT_UNION: UnionConfig = { @@ -1166,7 +1165,7 @@ function generateCommandsFile(project: Project): string { lines.push('#[allow(unused_imports)]'); lines.push('use crate::actions::{ActionEnvelope, StateAction};'); lines.push('#[allow(unused_imports)]'); - lines.push('use crate::state::{AgentSelection, ContentRef, MessageAttachment, ModelSelection, NewComment, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, TextRange, Turn};'); + lines.push('use crate::state::{AgentSelection, ContentRef, MessageAttachment, ModelSelection, NewAnnotationEntry, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, TextRange, Turn};'); lines.push(''); lines.push('// ─── Enums ────────────────────────────────────────────────────────────\n'); @@ -1249,7 +1248,7 @@ const NOTIFICATION_STRUCTS = [ function generateNotificationsFile(project: Project): string { const lines: string[] = [GENERATED_HEADER]; lines.push('#[allow(unused_imports)]'); - lines.push('use crate::state::{AgentSelection, ChangesSummary, Changeset, CommentsSummary, FileEdit, ModelSelection, ProjectInfo, SessionStatus, SessionSummary};'); + lines.push('use crate::state::{AgentSelection, AnnotationsSummary, ChangesSummary, Changeset, FileEdit, ModelSelection, ProjectInfo, SessionStatus, SessionSummary};'); lines.push(''); lines.push('// ─── Enums ────────────────────────────────────────────────────────────\n'); diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index cc087ad4..22b3fd0b 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -107,7 +107,7 @@ function mapType(tsType: string, propName?: string, containerName?: string): str if (tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' || tsType === 'RootState | SessionState | TerminalState | ChangesetState' - || tsType === 'RootState | SessionState | TerminalState | ChangesetState | CommentsState') return 'SnapshotState'; + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | AnnotationsState') return 'SnapshotState'; // T | null → T? const nullMatch = tsType.match(/^(.+?)\s*\|\s*null$/); @@ -532,7 +532,7 @@ const STATE_STRUCTS = [ 'SessionInputRequest', 'TextPosition', 'TextRange', 'TextSelection', 'SimpleMessageAttachment', 'MessageEmbeddedResourceAttachment', 'MessageResourceAttachment', - 'MessageCommentsAttachment', + 'MessageAnnotationsAttachment', 'MarkdownResponsePart', 'ContentRef', 'ResourceReponsePart', 'ToolCallResponsePart', 'ReasoningResponsePart', 'SystemNotificationResponsePart', @@ -557,7 +557,7 @@ const STATE_STRUCTS = [ 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', 'Changeset', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', - 'CommentsSummary', 'CommentsState', 'CommentThread', 'Comment', 'NewComment', + 'AnnotationsSummary', 'AnnotationsState', 'Annotation', 'AnnotationEntry', 'NewAnnotationEntry', 'TelemetryCapabilities', 'ResourceWatchState', 'ResourceChange', ]; @@ -647,7 +647,7 @@ const MESSAGE_ATTACHMENT_UNION: UnionConfig = { { caseName: 'simple', structName: 'SimpleMessageAttachment', discriminantValue: 'simple' }, { caseName: 'embeddedResource', structName: 'MessageEmbeddedResourceAttachment', discriminantValue: 'embeddedResource' }, { caseName: 'resource', structName: 'MessageResourceAttachment', discriminantValue: 'resource' }, - { caseName: 'comments', structName: 'MessageCommentsAttachment', discriminantValue: 'comments' }, + { caseName: 'annotations', structName: 'MessageAnnotationsAttachment', discriminantValue: 'annotations' }, ], }; @@ -795,13 +795,13 @@ public enum StringOrMarkdown: Codable, Sendable, Equatable { } function generateSnapshotState(): string { - return `/// The state payload of a snapshot — root, session, terminal, changeset, or comments state. + return `/// The state payload of a snapshot — root, session, terminal, changeset, or annotations state. public enum SnapshotState: Codable, Sendable { case root(RootState) case session(SessionState) case terminal(TerminalState) case changeset(ChangesetState) - case comments(CommentsState) + case annotations(AnnotationsState) public init(from decoder: Decoder) throws { // SessionState has required \`summary\` field, try it first @@ -811,8 +811,8 @@ public enum SnapshotState: Codable, Sendable { self = .terminal(terminal) } else if let changeset = try? ChangesetState(from: decoder) { self = .changeset(changeset) - } else if let comments = try? CommentsState(from: decoder) { - self = .comments(comments) + } else if let annotations = try? AnnotationsState(from: decoder) { + self = .annotations(annotations) } else { self = .root(try RootState(from: decoder)) } @@ -824,7 +824,7 @@ public enum SnapshotState: Codable, Sendable { case .session(let state): try state.encode(to: encoder) case .terminal(let state): try state.encode(to: encoder) case .changeset(let state): try state.encode(to: encoder) - case .comments(let state): try state.encode(to: encoder) + case .annotations(let state): try state.encode(to: encoder) } } }`; @@ -948,11 +948,10 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'changeset/operationsChanged', caseName: 'changesetOperationsChanged', tsInterface: 'ChangesetOperationsChangedAction' }, { type: 'changeset/operationStatusChanged', caseName: 'changesetOperationStatusChanged', tsInterface: 'ChangesetOperationStatusChangedAction' }, { type: 'changeset/cleared', caseName: 'changesetCleared', tsInterface: 'ChangesetClearedAction' }, - { type: 'comments/threadSet', caseName: 'commentsThreadSet', tsInterface: 'CommentsThreadSetAction' }, - { type: 'comments/threadRemoved', caseName: 'commentsThreadRemoved', tsInterface: 'CommentsThreadRemovedAction' }, - { type: 'comments/commentSet', caseName: 'commentsCommentSet', tsInterface: 'CommentsCommentSetAction' }, - { type: 'comments/commentRemoved', caseName: 'commentsCommentRemoved', tsInterface: 'CommentsCommentRemovedAction' }, - { type: 'comments/cleared', caseName: 'commentsCleared', tsInterface: 'CommentsClearedAction' }, + { type: 'annotations/set', caseName: 'annotationsSet', tsInterface: 'AnnotationsSetAction' }, + { type: 'annotations/removed', caseName: 'annotationsRemoved', tsInterface: 'AnnotationsRemovedAction' }, + { type: 'annotations/entrySet', caseName: 'annotationsEntrySet', tsInterface: 'AnnotationsEntrySetAction' }, + { type: 'annotations/entryRemoved', caseName: 'annotationsEntryRemoved', tsInterface: 'AnnotationsEntryRemovedAction' }, { type: 'root/terminalsChanged', caseName: 'rootTerminalsChanged', tsInterface: 'RootTerminalsChangedAction' }, { type: 'root/configChanged', caseName: 'rootConfigChanged', tsInterface: 'RootConfigChangedAction' }, { type: 'terminal/data', caseName: 'terminalData', tsInterface: 'TerminalDataAction' }, @@ -1136,12 +1135,12 @@ const COMMAND_STRUCTS = [ 'SessionConfigValueItem', 'CompletionsParams', 'CompletionItem', 'CompletionsResult', 'InvokeChangesetOperationParams', 'InvokeChangesetOperationResult', - 'CreateCommentThreadParams', 'CreateCommentThreadResult', - 'UpdateCommentThreadParams', - 'DeleteCommentThreadParams', - 'AddCommentParams', 'AddCommentResult', - 'EditCommentParams', - 'DeleteCommentParams', + 'CreateAnnotationParams', 'CreateAnnotationResult', + 'UpdateAnnotationParams', + 'DeleteAnnotationParams', + 'AddAnnotationEntryParams', 'AddAnnotationEntryResult', + 'EditAnnotationEntryParams', + 'DeleteAnnotationEntryParams', 'ChangesetOperationFollowUp', ]; diff --git a/types/action-origin.generated.ts b/types/action-origin.generated.ts index 8a76783e..33473692 100644 --- a/types/action-origin.generated.ts +++ b/types/action-origin.generated.ts @@ -54,11 +54,10 @@ import type { ChangesetOperationsChangedAction, ChangesetOperationStatusChangedAction, ChangesetClearedAction, - CommentsThreadSetAction, - CommentsThreadRemovedAction, - CommentsCommentSetAction, - CommentsCommentRemovedAction, - CommentsClearedAction, + AnnotationsSetAction, + AnnotationsRemovedAction, + AnnotationsEntrySetAction, + AnnotationsEntryRemovedAction, TerminalDataAction, TerminalInputAction, TerminalResizedAction, @@ -250,27 +249,25 @@ export type ServerChangesetAction = | ChangesetClearedAction ; -/** Union of all comments-scoped actions. */ -export type CommentsAction = - | CommentsThreadSetAction - | CommentsThreadRemovedAction - | CommentsCommentSetAction - | CommentsCommentRemovedAction - | CommentsClearedAction +/** Union of all annotations-scoped actions. */ +export type AnnotationsAction = + | AnnotationsSetAction + | AnnotationsRemovedAction + | AnnotationsEntrySetAction + | AnnotationsEntryRemovedAction ; -/** Union of comments actions that clients may dispatch. */ -export type ClientCommentsAction = +/** Union of annotations actions that clients may dispatch. */ +export type ClientAnnotationsAction = never ; -/** Union of comments actions that only the server may produce. */ -export type ServerCommentsAction = - | CommentsThreadSetAction - | CommentsThreadRemovedAction - | CommentsCommentSetAction - | CommentsCommentRemovedAction - | CommentsClearedAction +/** Union of annotations actions that only the server may produce. */ +export type ServerAnnotationsAction = + | AnnotationsSetAction + | AnnotationsRemovedAction + | AnnotationsEntrySetAction + | AnnotationsEntryRemovedAction ; /** Union of all resource-watch-scoped actions. */ @@ -346,11 +343,10 @@ export const IS_CLIENT_DISPATCHABLE: { readonly [K in StateAction['type']]: bool [ActionType.ChangesetOperationsChanged]: false, [ActionType.ChangesetOperationStatusChanged]: false, [ActionType.ChangesetCleared]: false, - [ActionType.CommentsThreadSet]: false, - [ActionType.CommentsThreadRemoved]: false, - [ActionType.CommentsCommentSet]: false, - [ActionType.CommentsCommentRemoved]: false, - [ActionType.CommentsCleared]: false, + [ActionType.AnnotationsSet]: false, + [ActionType.AnnotationsRemoved]: false, + [ActionType.AnnotationsEntrySet]: false, + [ActionType.AnnotationsEntryRemoved]: false, [ActionType.TerminalData]: false, [ActionType.TerminalInput]: true, [ActionType.TerminalResized]: true, diff --git a/types/actions.ts b/types/actions.ts index 1601b5d9..66498160 100644 --- a/types/actions.ts +++ b/types/actions.ts @@ -12,5 +12,5 @@ export * from './channels-root/actions.js'; export * from './channels-session/actions.js'; export * from './channels-terminal/actions.js'; export * from './channels-changeset/actions.js'; -export * from './channels-comments/actions.js'; +export * from './channels-annotations/actions.js'; export * from './channels-resource-watch/actions.js'; diff --git a/types/channels-annotations/actions.ts b/types/channels-annotations/actions.ts new file mode 100644 index 00000000..c8df1e6c --- /dev/null +++ b/types/channels-annotations/actions.ts @@ -0,0 +1,91 @@ +/** + * Annotations Channel Actions — Mutations of an `ahp-session://annotations` + * channel's state. + * + * Every annotations action is server-only: clients drive mutations through + * the {@link CreateAnnotationParams | `createAnnotation`}, + * {@link AddAnnotationEntryParams | `addAnnotationEntry`}, etc. commands, and + * the server echoes the resulting state change as one of these actions. + * Mirrors the shape of the `changeset/*` action family. + * + * @module channels-annotations/actions + */ + +import { ActionType } from '../common/actions.js'; +import type { AnnotationEntry, Annotation } from './state.js'; + +// ─── Annotations Actions ───────────────────────────────────────────────────── + +/** + * Upsert an {@link Annotation} in the annotations channel — adds a new + * annotation, or replaces an existing one identified by + * {@link Annotation.id}. When replacing, the full annotation payload + * (including its {@link Annotation.entries | entries} list) is + * substituted; producers SHOULD prefer {@link AnnotationsEntrySetAction} + * for per-entry edits to keep wire updates small. + * + * @category Annotations Actions + * @version 3 + */ +export interface AnnotationsSetAction { + type: ActionType.AnnotationsSet; + /** The new or replacement annotation. MUST contain at least one entry. */ + annotation: Annotation; +} + +/** + * Remove an {@link Annotation} from the channel by its id. + * + * The server emits this in two cases: + * 1. The client explicitly invoked + * {@link DeleteAnnotationParams | `deleteAnnotation`}. + * 2. The client invoked {@link DeleteAnnotationEntryParams | + * `deleteAnnotationEntry`} on the last remaining entry in the + * annotation — the protocol collapses the annotation rather than + * leaving an empty one behind. + * + * @category Annotations Actions + * @version 3 + */ +export interface AnnotationsRemovedAction { + type: ActionType.AnnotationsRemoved; + /** The {@link Annotation.id} of the annotation to remove. */ + annotationId: string; +} + +/** + * Upsert an {@link AnnotationEntry} within an existing annotation — adds a + * new entry, or replaces one identified by {@link AnnotationEntry.id}. If + * {@link annotationId} does not match any current annotation the action is + * a no-op. + * + * @category Annotations Actions + * @version 3 + */ +export interface AnnotationsEntrySetAction { + type: ActionType.AnnotationsEntrySet; + /** The {@link Annotation.id} the entry belongs to. */ + annotationId: string; + /** The new or replacement entry. */ + entry: AnnotationEntry; +} + +/** + * Remove a single {@link AnnotationEntry} from an annotation without + * collapsing the annotation itself. Used when more than one entry remains + * — the server MUST dispatch {@link AnnotationsRemovedAction} instead when + * removing the last entry would otherwise leave the annotation empty. + * + * If either {@link annotationId} or {@link entryId} does not match the + * current state the action is a no-op. + * + * @category Annotations Actions + * @version 3 + */ +export interface AnnotationsEntryRemovedAction { + type: ActionType.AnnotationsEntryRemoved; + /** The {@link Annotation.id} the entry belongs to. */ + annotationId: string; + /** The {@link AnnotationEntry.id} to remove. */ + entryId: string; +} diff --git a/types/channels-annotations/commands.ts b/types/channels-annotations/commands.ts new file mode 100644 index 00000000..d4636399 --- /dev/null +++ b/types/channels-annotations/commands.ts @@ -0,0 +1,203 @@ +/** + * Annotations Channel Commands — Client-driven mutations of an + * `ahp-session://annotations` channel. + * + * The protocol forbids empty annotations, so annotation and first-entry + * creation are fused into {@link CreateAnnotationParams | + * `createAnnotation`} and the server collapses an annotation whose last + * entry is deleted into an {@link AnnotationsRemovedAction}. Every + * accepted command echoes back through the normal `annotations/*` action + * stream on the channel. + * + * @module channels-annotations/commands + */ + +import type { URI, StringOrMarkdown, TextRange } from '../common/state.js'; +import type { BaseParams } from '../common/commands.js'; +import type { NewAnnotationEntry } from './state.js'; + +// ─── createAnnotation ───────────────────────────────────────────────────── + +/** + * Create a new {@link Annotation} anchored to a file from a specific + * turn, optionally narrowed to a range within that file. + * + * The initial entry is required — the protocol forbids empty annotations, + * so annotation creation and first-entry creation are fused into one + * command. The created annotation always starts unresolved + * ({@link Annotation.resolved} is `false`). The server assigns both + * {@link CreateAnnotationResult.annotationId} and + * {@link CreateAnnotationResult.entryId}, then broadcasts an + * {@link AnnotationsSetAction} on the channel. + * + * @category Commands + * @method createAnnotation + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface CreateAnnotationParams extends BaseParams { + /** The annotations channel URI, e.g. `ahp-session://annotations`. */ + channel: URI; + /** Turn whose file versions {@link resource} + {@link range} address. */ + turnId: string; + /** Anchored file URI. */ + resource: URI; + /** + * Anchored range within {@link resource}. When omitted the annotation is + * anchored to the entire file. + */ + range?: TextRange; + /** First entry in the annotation. The server assigns its {@link AnnotationEntry.id}. */ + entry: NewAnnotationEntry; +} + +/** + * Result of {@link CreateAnnotationParams | `createAnnotation`}. + * + * @category Commands + */ +export interface CreateAnnotationResult { + /** Server-assigned {@link Annotation.id}. */ + annotationId: string; + /** Server-assigned {@link AnnotationEntry.id} of the initial entry. */ + entryId: string; +} + +// ─── updateAnnotation ───────────────────────────────────────────────────── + +/** + * Re-anchor or resolve an existing {@link Annotation} — typically used + * to re-pin an annotation to a different range or a newer turn after an + * edit, or to mark the annotation {@link Annotation.resolved | resolved} + * (or re-open it). Entries themselves are not modified by this command; + * use {@link AddAnnotationEntryParams | `addAnnotationEntry`}, + * {@link EditAnnotationEntryParams | `editAnnotationEntry`}, or + * {@link DeleteAnnotationEntryParams | `deleteAnnotationEntry`} for that. + * + * Omitted optional fields preserve their current value. The server + * echoes the resulting annotation state as an {@link AnnotationsSetAction}. + * + * @category Commands + * @method updateAnnotation + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface UpdateAnnotationParams extends BaseParams { + /** The annotations channel URI. */ + channel: URI; + /** The {@link Annotation.id} to update. */ + annotationId: string; + /** New {@link Annotation.turnId}, if changing. */ + turnId?: string; + /** New anchored file URI, if changing. */ + resource?: URI; + /** New anchored range, if changing. */ + range?: TextRange; + /** New {@link Annotation.resolved} state, if changing. */ + resolved?: boolean; +} + +// ─── deleteAnnotation ───────────────────────────────────────────────────── + +/** + * Delete an entire annotation (and every entry it contains). The + * server echoes an {@link AnnotationsRemovedAction} on the channel. + * + * @category Commands + * @method deleteAnnotation + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface DeleteAnnotationParams extends BaseParams { + /** The annotations channel URI. */ + channel: URI; + /** The {@link Annotation.id} to delete. */ + annotationId: string; +} + +// ─── addAnnotationEntry ────────────────────────────────────────────────────────────── + +/** + * Append a new {@link AnnotationEntry} to an existing annotation. The + * server assigns the resulting {@link AnnotationEntry.id} and echoes an + * {@link AnnotationsEntrySetAction}. + * + * @category Commands + * @method addAnnotationEntry + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface AddAnnotationEntryParams extends BaseParams { + /** The annotations channel URI. */ + channel: URI; + /** Annotation that receives the new entry. */ + annotationId: string; + /** Entry payload — the server assigns the id. */ + entry: NewAnnotationEntry; +} + +/** + * Result of {@link AddAnnotationEntryParams | `addAnnotationEntry`}. + * + * @category Commands + */ +export interface AddAnnotationEntryResult { + /** Server-assigned {@link AnnotationEntry.id} of the new entry. */ + entryId: string; +} + +// ─── editAnnotationEntry ───────────────────────────────────────────────────────────── + +/** + * Edit the body of an existing entry in place. The server echoes an + * {@link AnnotationsEntrySetAction} carrying the updated entry. + * + * Only the body is mutable through this command; to change + * {@link AnnotationEntry._meta} delete and re-create the entry. + * + * @category Commands + * @method editAnnotationEntry + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface EditAnnotationEntryParams extends BaseParams { + /** The annotations channel URI. */ + channel: URI; + /** Enclosing annotation. */ + annotationId: string; + /** {@link AnnotationEntry.id} to edit. */ + entryId: string; + /** New entry body. See {@link AnnotationEntry.text}. */ + text: StringOrMarkdown; +} + +// ─── deleteAnnotationEntry ─────────────────────────────────────────────────────────── + +/** + * Remove a single entry from an annotation. + * + * If the removal would leave the annotation empty (i.e. the targeted entry + * is the only one remaining), the server collapses the annotation instead + * — it dispatches an {@link AnnotationsRemovedAction} and the annotation + * disappears from {@link AnnotationsState.annotations}. Otherwise the server + * echoes an {@link AnnotationsEntryRemovedAction}. + * + * @category Commands + * @method deleteAnnotationEntry + * @direction Client → Server + * @messageType Request + * @version 3 + */ +export interface DeleteAnnotationEntryParams extends BaseParams { + /** The annotations channel URI. */ + channel: URI; + /** Enclosing annotation. */ + annotationId: string; + /** {@link AnnotationEntry.id} to remove. */ + entryId: string; +} diff --git a/types/channels-annotations/reducer.ts b/types/channels-annotations/reducer.ts new file mode 100644 index 00000000..d7bde323 --- /dev/null +++ b/types/channels-annotations/reducer.ts @@ -0,0 +1,88 @@ +/** + * Annotations Channel Reducer — Pure reducer for `AnnotationsState`. + * + * @module channels-annotations/reducer + */ + +import { ActionType } from '../common/actions.js'; +import type { AnnotationEntry, Annotation, AnnotationsState } from './state.js'; +import type { AnnotationsAction } from '../action-origin.generated.js'; +import { softAssertNever } from '../common/reducer-helpers.js'; + +/** + * Pure reducer for annotations state. Handles every {@link AnnotationsAction} + * variant. + * + * Per the spec, every annotations action is server-only. The reducer + * preserves the dispatch order of annotations (and of entries within an + * annotation): new entries are appended; `*Set` actions with a matching id + * replace in place, while actions whose target id is unknown are no-ops + * (mirroring `changeset/fileRemoved` semantics). The single-entry + * minimum invariant is enforced by the server, not the reducer — a + * malformed server that removes an annotation's last entry via + * {@link AnnotationsEntryRemovedAction} would leave an empty annotation, + * which is observable but not catastrophic. + */ +export function annotationsReducer(state: AnnotationsState, action: AnnotationsAction, log?: (msg: string) => void): AnnotationsState { + switch (action.type) { + case ActionType.AnnotationsSet: { + const idx = state.annotations.findIndex(t => t.id === action.annotation.id); + if (idx < 0) { + return { ...state, annotations: [...state.annotations, action.annotation] }; + } + const next: Annotation[] = [...state.annotations]; + next[idx] = action.annotation; + return { ...state, annotations: next }; + } + + case ActionType.AnnotationsRemoved: { + const idx = state.annotations.findIndex(t => t.id === action.annotationId); + if (idx < 0) { + return state; + } + const next: Annotation[] = [...state.annotations]; + next.splice(idx, 1); + return { ...state, annotations: next }; + } + + case ActionType.AnnotationsEntrySet: { + const tIdx = state.annotations.findIndex(t => t.id === action.annotationId); + if (tIdx < 0) { + return state; + } + const annotation = state.annotations[tIdx]; + const cIdx = annotation.entries.findIndex(c => c.id === action.entry.id); + let nextEntries: AnnotationEntry[]; + if (cIdx < 0) { + nextEntries = [...annotation.entries, action.entry]; + } else { + nextEntries = [...annotation.entries]; + nextEntries[cIdx] = action.entry; + } + const nextAnnotations: Annotation[] = [...state.annotations]; + nextAnnotations[tIdx] = { ...annotation, entries: nextEntries }; + return { ...state, annotations: nextAnnotations }; + } + + case ActionType.AnnotationsEntryRemoved: { + const tIdx = state.annotations.findIndex(t => t.id === action.annotationId); + if (tIdx < 0) { + return state; + } + const annotation = state.annotations[tIdx]; + const cIdx = annotation.entries.findIndex(c => c.id === action.entryId); + if (cIdx < 0) { + return state; + } + const nextEntries: AnnotationEntry[] = [...annotation.entries]; + nextEntries.splice(cIdx, 1); + const nextAnnotations: Annotation[] = [...state.annotations]; + nextAnnotations[tIdx] = { ...annotation, entries: nextEntries }; + return { ...state, annotations: nextAnnotations }; + } + + default: + softAssertNever(action, log); + return state; + } +} diff --git a/types/channels-annotations/state.ts b/types/channels-annotations/state.ts new file mode 100644 index 00000000..d05a0d4e --- /dev/null +++ b/types/channels-annotations/state.ts @@ -0,0 +1,144 @@ +/** + * Annotations Channel State Types — Per-session inline file-annotation state + * exposed on the `ahp-session://annotations` channel. + * + * Each session owns at most one annotations channel. The channel URI is + * derived from the session URI by appending `/annotations` and is also + * surfaced explicitly on {@link AnnotationsSummary.resource} for badge UI. + * + * @module channels-annotations/state + */ + +import type { URI, StringOrMarkdown, TextRange } from '../common/state.js'; + +// ─── Annotations Summary ───────────────────────────────────────────────────── + +/** + * Lightweight per-session summary of the annotations channel, surfaced on + * {@link SessionSummary.annotations} so badge UI can render annotation / + * entry counts without subscribing to the channel itself. + * + * @category Annotations + */ +export interface AnnotationsSummary { + /** + * The subscribable annotations channel URI for the owning session + * (typically `ahp-session://annotations`). Surfaced explicitly even + * though it is derivable from the session URI so badge UI does not need + * to know the derivation rule. + */ + resource: URI; + /** Total number of {@link Annotation} entries in the channel. */ + annotationCount: number; + /** Total number of {@link AnnotationEntry} entries across every annotation. */ + entryCount: number; +} + +// ─── Annotations State ─────────────────────────────────────────────────────── + +/** + * Full state for a session's annotations channel, returned when a client + * subscribes to an `ahp-session://annotations` URI. + * + * @category Annotations + */ +export interface AnnotationsState { + /** Annotations in this channel, keyed by {@link Annotation.id}. */ + annotations: Annotation[]; +} + +// ─── Annotation ────────────────────────────────────────────────────────────── + +/** + * A conversation anchored to a specific file produced by a specific turn, + * optionally narrowed to a range within that file. + * + * {@link turnId} anchors the annotation to the file versions that turn + * produced, so a later turn that rewrites the same file does not silently + * invalidate the annotation's anchor — clients can resolve {@link resource} + * and {@link range} against the turn's changeset. When {@link range} is + * omitted the annotation is anchored to the entire file. + * + * Every annotation MUST contain at least one {@link AnnotationEntry}. The + * server enforces this invariant: {@link CreateAnnotationParams | + * `createAnnotation`} requires an initial entry, and deleting the + * last remaining entry collapses the annotation into a + * {@link AnnotationsRemovedAction} rather than leaving an empty annotation + * behind. + * + * @category Annotations + */ +export interface Annotation { + /** Stable identifier within the annotations channel. Server-assigned. */ + id: string; + /** + * Turn that produced the file versions this annotation is anchored to. + * Matches a {@link Turn.id} on the owning session. + */ + turnId: string; + /** The file the annotation is anchored to. */ + resource: URI; + /** + * Range within {@link resource} the annotation is anchored to. When + * omitted the annotation is anchored to the entire file. + */ + range?: TextRange; + /** + * Whether the annotation has been resolved. Newly created annotations are + * always unresolved (`false`); a client marks an annotation resolved (or + * re-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}. + */ + resolved: boolean; + /** + * Entries in this annotation, in dispatch order (oldest first). MUST + * contain at least one entry. + */ + entries: AnnotationEntry[]; + /** + * Server-defined opaque metadata, surfaced to tooling but not + * interpreted by the protocol. + */ + _meta?: Record; +} + +// ─── Annotation Entry ──────────────────────────────────────────────────────── + +/** + * A single entry within an {@link Annotation}. + * + * @category Annotations + */ +export interface AnnotationEntry { + /** Stable identifier within the enclosing annotation. Server-assigned. */ + id: string; + /** + * Entry body. A bare `string` is rendered as plain text; pass + * `{ markdown: "…" }` to opt into Markdown rendering. See + * {@link StringOrMarkdown}. + */ + text: StringOrMarkdown; + /** + * Server-defined opaque metadata, surfaced to tooling but not + * interpreted by the protocol. + */ + _meta?: Record; +} + +// ─── New Annotation Entry ──────────────────────────────────────────────────── + +/** + * Input shape passed to {@link CreateAnnotationParams | `createAnnotation`} + * and {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server + * assigns the resulting {@link AnnotationEntry.id}. + * + * @category Annotations + */ +export interface NewAnnotationEntry { + /** Entry body. See {@link AnnotationEntry.text}. */ + text: StringOrMarkdown; + /** + * Server-defined opaque metadata, forwarded onto the resulting + * {@link AnnotationEntry._meta}. + */ + _meta?: Record; +} diff --git a/types/channels-comments/actions.ts b/types/channels-comments/actions.ts deleted file mode 100644 index edd82fed..00000000 --- a/types/channels-comments/actions.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Comments Channel Actions — Mutations of an `ahp-session://comments` - * channel's state. - * - * Every comments action is server-only: clients drive mutations through - * the {@link CreateCommentThreadParams | `createCommentThread`}, - * {@link AddCommentParams | `addComment`}, etc. commands, and the server - * echoes the resulting state change as one of these actions. Mirrors the - * shape of the `channeset/*` action family. - * - * @module channels-comments/actions - */ - -import { ActionType } from '../common/actions.js'; -import type { Comment, CommentThread } from './state.js'; - -// ─── Comments Actions ──────────────────────────────────────────────────────── - -/** - * Upsert a {@link CommentThread} in the comments channel — adds a new - * thread, or replaces an existing one identified by - * {@link CommentThread.id}. When replacing, the full thread payload - * (including its {@link CommentThread.comments | comments} list) is - * substituted; producers SHOULD prefer {@link CommentsCommentSetAction} - * for per-comment edits to keep wire updates small. - * - * @category Comments Actions - * @version 3 - */ -export interface CommentsThreadSetAction { - type: ActionType.CommentsThreadSet; - /** The new or replacement thread. MUST contain at least one comment. */ - thread: CommentThread; -} - -/** - * Remove a {@link CommentThread} from the channel by its id. - * - * The server emits this in two cases: - * 1. The client explicitly invoked - * {@link DeleteCommentThreadParams | `deleteCommentThread`}. - * 2. The client invoked {@link DeleteCommentParams | `deleteComment`} on - * the last remaining comment in the thread — the protocol collapses - * the thread rather than leaving an empty one behind. - * - * @category Comments Actions - * @version 3 - */ -export interface CommentsThreadRemovedAction { - type: ActionType.CommentsThreadRemoved; - /** The {@link CommentThread.id} of the thread to remove. */ - threadId: string; -} - -/** - * Upsert a {@link Comment} within an existing thread — adds a new - * comment, or replaces one identified by {@link Comment.id}. If - * {@link threadId} does not match any current thread the action is a - * no-op. - * - * @category Comments Actions - * @version 3 - */ -export interface CommentsCommentSetAction { - type: ActionType.CommentsCommentSet; - /** The {@link CommentThread.id} the comment belongs to. */ - threadId: string; - /** The new or replacement comment. */ - comment: Comment; -} - -/** - * Remove a single {@link Comment} from a thread without collapsing the - * thread itself. Used when more than one comment remains — the server - * MUST dispatch {@link CommentsThreadRemovedAction} instead when removing - * the last comment would otherwise leave the thread empty. - * - * If either {@link threadId} or {@link commentId} does not match the - * current state the action is a no-op. - * - * @category Comments Actions - * @version 3 - */ -export interface CommentsCommentRemovedAction { - type: ActionType.CommentsCommentRemoved; - /** The {@link CommentThread.id} the comment belongs to. */ - threadId: string; - /** The {@link Comment.id} to remove. */ - commentId: string; -} - -/** - * Drop every thread from the comments channel. - * - * Dispatched when the owning session is going away and the channel is - * about to become un-subscribable. Clients SHOULD release references on - * receipt and react to the corresponding session-level lifecycle signal - * (e.g. `root/sessionRemoved`) to fully tear down UI. - * - * @category Comments Actions - * @version 3 - */ -export interface CommentsClearedAction { - type: ActionType.CommentsCleared; -} diff --git a/types/channels-comments/commands.ts b/types/channels-comments/commands.ts deleted file mode 100644 index 9d7405fe..00000000 --- a/types/channels-comments/commands.ts +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Comments Channel Commands — Client-driven mutations of an - * `ahp-session://comments` channel. - * - * The protocol forbids empty threads, so thread and first-comment - * creation are fused into {@link CreateCommentThreadParams | - * `createCommentThread`} and the server collapses a thread whose last - * comment is deleted into a {@link CommentsThreadRemovedAction}. Every - * accepted command echoes back through the normal `comments/*` action - * stream on the channel. - * - * @module channels-comments/commands - */ - -import type { URI, StringOrMarkdown, TextRange } from '../common/state.js'; -import type { BaseParams } from '../common/commands.js'; -import type { NewComment } from './state.js'; - -// ─── createCommentThread ───────────────────────────────────────────────────── - -/** - * Create a new {@link CommentThread} anchored to a file from a specific - * turn, optionally narrowed to a range within that file. - * - * The initial comment is required — the protocol forbids empty threads, - * so thread creation and first-comment creation are fused into one - * command. The created thread always starts unresolved - * ({@link CommentThread.resolved} is `false`). The server assigns both - * {@link CreateCommentThreadResult.threadId} and - * {@link CreateCommentThreadResult.commentId}, then broadcasts a - * {@link CommentsThreadSetAction} on the channel. - * - * @category Commands - * @method createCommentThread - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface CreateCommentThreadParams extends BaseParams { - /** The comments channel URI, e.g. `ahp-session://comments`. */ - channel: URI; - /** Turn whose file versions {@link resource} + {@link range} address. */ - turnId: string; - /** Anchored file URI. */ - resource: URI; - /** - * Anchored range within {@link resource}. When omitted the thread is - * anchored to the entire file. - */ - range?: TextRange; - /** First comment in the thread. The server assigns its {@link Comment.id}. */ - comment: NewComment; -} - -/** - * Result of {@link CreateCommentThreadParams | `createCommentThread`}. - * - * @category Commands - */ -export interface CreateCommentThreadResult { - /** Server-assigned {@link CommentThread.id}. */ - threadId: string; - /** Server-assigned {@link Comment.id} of the initial comment. */ - commentId: string; -} - -// ─── updateCommentThread ───────────────────────────────────────────────────── - -/** - * Re-anchor or resolve an existing {@link CommentThread} — typically used - * to re-pin a thread to a different range or a newer turn after an edit, - * or to mark the thread {@link CommentThread.resolved | resolved} (or - * re-open it). Comments themselves are not modified by this command; use - * {@link AddCommentParams | `addComment`}, - * {@link EditCommentParams | `editComment`}, or - * {@link DeleteCommentParams | `deleteComment`} for that. - * - * Omitted optional fields preserve their current value. The server - * echoes the resulting thread state as a {@link CommentsThreadSetAction}. - * - * @category Commands - * @method updateCommentThread - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface UpdateCommentThreadParams extends BaseParams { - /** The comments channel URI. */ - channel: URI; - /** The {@link CommentThread.id} to update. */ - threadId: string; - /** New {@link CommentThread.turnId}, if changing. */ - turnId?: string; - /** New anchored file URI, if changing. */ - resource?: URI; - /** New anchored range, if changing. */ - range?: TextRange; - /** New {@link CommentThread.resolved} state, if changing. */ - resolved?: boolean; -} - -// ─── deleteCommentThread ───────────────────────────────────────────────────── - -/** - * Delete an entire comment thread (and every comment it contains). The - * server echoes a {@link CommentsThreadRemovedAction} on the channel. - * - * @category Commands - * @method deleteCommentThread - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface DeleteCommentThreadParams extends BaseParams { - /** The comments channel URI. */ - channel: URI; - /** The {@link CommentThread.id} to delete. */ - threadId: string; -} - -// ─── addComment ────────────────────────────────────────────────────────────── - -/** - * Append a new {@link Comment} to an existing thread. The server assigns - * the resulting {@link Comment.id} and echoes a - * {@link CommentsCommentSetAction}. - * - * @category Commands - * @method addComment - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface AddCommentParams extends BaseParams { - /** The comments channel URI. */ - channel: URI; - /** Thread that receives the new comment. */ - threadId: string; - /** Comment payload — the server assigns the id. */ - comment: NewComment; -} - -/** - * Result of {@link AddCommentParams | `addComment`}. - * - * @category Commands - */ -export interface AddCommentResult { - /** Server-assigned {@link Comment.id} of the new comment. */ - commentId: string; -} - -// ─── editComment ───────────────────────────────────────────────────────────── - -/** - * Edit the body of an existing comment in place. The server echoes a - * {@link CommentsCommentSetAction} carrying the updated comment. - * - * Only the body is mutable through this command; to change - * {@link Comment.source} or {@link Comment._meta} delete and re-create - * the comment. - * - * @category Commands - * @method editComment - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface EditCommentParams extends BaseParams { - /** The comments channel URI. */ - channel: URI; - /** Enclosing thread. */ - threadId: string; - /** {@link Comment.id} to edit. */ - commentId: string; - /** New comment body. See {@link Comment.text}. */ - text: StringOrMarkdown; -} - -// ─── deleteComment ─────────────────────────────────────────────────────────── - -/** - * Remove a single comment from a thread. - * - * If the removal would leave the thread empty (i.e. the targeted comment - * is the only one remaining), the server collapses the thread instead - * — it dispatches a {@link CommentsThreadRemovedAction} and the thread - * disappears from {@link CommentsState.threads}. Otherwise the server - * echoes a {@link CommentsCommentRemovedAction}. - * - * @category Commands - * @method deleteComment - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface DeleteCommentParams extends BaseParams { - /** The comments channel URI. */ - channel: URI; - /** Enclosing thread. */ - threadId: string; - /** {@link Comment.id} to remove. */ - commentId: string; -} diff --git a/types/channels-comments/reducer.ts b/types/channels-comments/reducer.ts deleted file mode 100644 index 4a01711f..00000000 --- a/types/channels-comments/reducer.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Comments Channel Reducer — Pure reducer for `CommentsState`. - * - * @module channels-comments/reducer - */ - -import { ActionType } from '../common/actions.js'; -import type { Comment, CommentThread, CommentsState } from './state.js'; -import type { CommentsAction } from '../action-origin.generated.js'; -import { softAssertNever } from '../common/reducer-helpers.js'; - -/** - * Pure reducer for comments state. Handles every {@link CommentsAction} - * variant. - * - * Per the spec, every comments action is server-only. The reducer - * preserves the dispatch order of threads (and of comments within a - * thread): new entries are appended; `*Set` actions with a matching id - * replace in place, while actions whose target id is unknown are no-ops - * (mirroring `changeset/fileRemoved` semantics). The single-comment - * minimum invariant is enforced by the server, not the reducer — a - * malformed server that removes a thread's last comment via - * {@link CommentsCommentRemovedAction} would leave an empty thread, - * which is observable but not catastrophic. - */ -export function commentsReducer(state: CommentsState, action: CommentsAction, log?: (msg: string) => void): CommentsState { - switch (action.type) { - case ActionType.CommentsThreadSet: { - const idx = state.threads.findIndex(t => t.id === action.thread.id); - if (idx < 0) { - return { ...state, threads: [...state.threads, action.thread] }; - } - const next: CommentThread[] = [...state.threads]; - next[idx] = action.thread; - return { ...state, threads: next }; - } - - case ActionType.CommentsThreadRemoved: { - const idx = state.threads.findIndex(t => t.id === action.threadId); - if (idx < 0) { - return state; - } - const next: CommentThread[] = [...state.threads]; - next.splice(idx, 1); - return { ...state, threads: next }; - } - - case ActionType.CommentsCommentSet: { - const tIdx = state.threads.findIndex(t => t.id === action.threadId); - if (tIdx < 0) { - return state; - } - const thread = state.threads[tIdx]; - const cIdx = thread.comments.findIndex(c => c.id === action.comment.id); - let nextComments: Comment[]; - if (cIdx < 0) { - nextComments = [...thread.comments, action.comment]; - } else { - nextComments = [...thread.comments]; - nextComments[cIdx] = action.comment; - } - const nextThreads: CommentThread[] = [...state.threads]; - nextThreads[tIdx] = { ...thread, comments: nextComments }; - return { ...state, threads: nextThreads }; - } - - case ActionType.CommentsCommentRemoved: { - const tIdx = state.threads.findIndex(t => t.id === action.threadId); - if (tIdx < 0) { - return state; - } - const thread = state.threads[tIdx]; - const cIdx = thread.comments.findIndex(c => c.id === action.commentId); - if (cIdx < 0) { - return state; - } - const nextComments: Comment[] = [...thread.comments]; - nextComments.splice(cIdx, 1); - const nextThreads: CommentThread[] = [...state.threads]; - nextThreads[tIdx] = { ...thread, comments: nextComments }; - return { ...state, threads: nextThreads }; - } - - case ActionType.CommentsCleared: - if (state.threads.length === 0) { - return state; - } - return { ...state, threads: [] }; - - default: - softAssertNever(action, log); - return state; - } -} diff --git a/types/channels-comments/state.ts b/types/channels-comments/state.ts deleted file mode 100644 index 89c2662a..00000000 --- a/types/channels-comments/state.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Comments Channel State Types — Per-session inline file-comment state - * exposed on the `ahp-session://comments` channel. - * - * Each session owns at most one comments channel. The channel URI is - * derived from the session URI by appending `/comments` and is also - * surfaced explicitly on {@link CommentsSummary.resource} for badge UI. - * - * @module channels-comments/state - */ - -import type { URI, StringOrMarkdown, TextRange } from '../common/state.js'; - -// ─── Comments Summary ──────────────────────────────────────────────────────── - -/** - * Lightweight per-session summary of the comments channel, surfaced on - * {@link SessionSummary.comments} so badge UI can render thread / comment - * counts without subscribing to the channel itself. - * - * @category Comments - */ -export interface CommentsSummary { - /** - * The subscribable comments channel URI for the owning session - * (typically `ahp-session://comments`). Surfaced explicitly even - * though it is derivable from the session URI so badge UI does not need - * to know the derivation rule. - */ - resource: URI; - /** Total number of {@link CommentThread} entries in the channel. */ - threadCount: number; - /** Total number of {@link Comment} entries across every thread. */ - commentCount: number; -} - -// ─── Comments State ────────────────────────────────────────────────────────── - -/** - * Full state for a session's comments channel, returned when a client - * subscribes to an `ahp-session://comments` URI. - * - * @category Comments - */ -export interface CommentsState { - /** Comment threads in this channel, keyed by {@link CommentThread.id}. */ - threads: CommentThread[]; -} - -// ─── Comment Thread ────────────────────────────────────────────────────────── - -/** - * A conversation anchored to a specific file produced by a specific turn, - * optionally narrowed to a range within that file. - * - * {@link turnId} anchors the thread to the file versions that turn - * produced, so a later turn that rewrites the same file does not silently - * invalidate the comment's anchor — clients can resolve {@link resource} - * and {@link range} against the turn's changeset. When {@link range} is - * omitted the thread is anchored to the entire file. - * - * Every thread MUST contain at least one {@link Comment}. The server - * enforces this invariant: {@link CreateCommentThreadParams | - * `createCommentThread`} requires an initial comment, and deleting the - * last remaining comment collapses the thread into a - * {@link CommentsThreadRemovedAction} rather than leaving an empty thread - * behind. - * - * @category Comments - */ -export interface CommentThread { - /** Stable identifier within the comments channel. Server-assigned. */ - id: string; - /** - * Turn that produced the file versions this thread is anchored to. - * Matches a {@link Turn.id} on the owning session. - */ - turnId: string; - /** The file the thread is anchored to. */ - resource: URI; - /** - * Range within {@link resource} the thread is anchored to. When omitted - * the thread is anchored to the entire file. - */ - range?: TextRange; - /** - * Whether the thread has been resolved. Newly created threads are always - * unresolved (`false`); a client marks a thread resolved (or re-opens it) - * through {@link UpdateCommentThreadParams | `updateCommentThread`}. - */ - resolved: boolean; - /** - * Comments in this thread, in dispatch order (oldest first). MUST - * contain at least one entry. - */ - comments: Comment[]; - /** - * Server-defined opaque metadata, surfaced to tooling but not - * interpreted by the protocol. - */ - _meta?: Record; -} - -// ─── Comment ───────────────────────────────────────────────────────────────── - -/** - * A single comment within a {@link CommentThread}. - * - * @category Comments - */ -export interface Comment { - /** Stable identifier within the enclosing thread. Server-assigned. */ - id: string; - /** - * Comment body. A bare `string` is rendered as plain text; pass - * `{ markdown: "…" }` to opt into Markdown rendering. See - * {@link StringOrMarkdown}. - */ - text: StringOrMarkdown; - /** - * Server-defined opaque metadata, surfaced to tooling but not - * interpreted by the protocol. - */ - _meta?: Record; -} - -// ─── New Comment ───────────────────────────────────────────────────────────── - -/** - * Input shape passed to {@link CreateCommentThreadParams | `createCommentThread`} - * and {@link AddCommentParams | `addComment`}. The server assigns the - * resulting {@link Comment.id}. - * - * @category Comments - */ -export interface NewComment { - /** Comment body. See {@link Comment.text}. */ - text: StringOrMarkdown; - /** - * Server-defined opaque metadata, forwarded onto the resulting - * {@link Comment._meta}. - */ - _meta?: Record; -} diff --git a/types/channels-session/state.ts b/types/channels-session/state.ts index 0ee3516a..dd38b953 100644 --- a/types/channels-session/state.ts +++ b/types/channels-session/state.ts @@ -6,7 +6,7 @@ */ import type { Changeset } from '../channels-changeset/state.js'; -import type { CommentsSummary } from '../channels-comments/state.js'; +import type { AnnotationsSummary } from '../channels-annotations/state.js'; import type { ModelSelection } from '../channels-root/state.js'; import type { ConfigPropertySchema, @@ -234,12 +234,12 @@ export interface SessionSummary { */ changes?: ChangesSummary; /** - * Lightweight summary of this session's inline comments channel - * (`ahp-session://comments`). Surfaced so badge UI can render - * thread / comment counts without subscribing. Absent when the session - * does not expose a comments channel. + * Lightweight summary of this session's inline annotations channel + * (`ahp-session://annotations`). Surfaced so badge UI can render + * annotation / entry counts without subscribing. Absent when the session + * does not expose an annotations channel. */ - comments?: CommentsSummary; + annotations?: AnnotationsSummary; } /** @@ -593,8 +593,8 @@ export const enum MessageAttachmentKind { EmbeddedResource = 'embeddedResource', /** An attachment that references a resource by URI. */ Resource = 'resource', - /** An attachment that references comment threads on a comments channel. */ - Comments = 'comments', + /** An attachment that references annotations on an annotations channel. */ + Annotations = 'annotations', } /** @@ -785,28 +785,28 @@ export interface MessageResourceAttachment extends MessageAttachmentBase, Conten } /** - * An attachment that references comment threads on a session's comments - * channel (see {@link CommentsState}). + * An attachment that references annotations on a session's annotations + * channel (see {@link AnnotationsState}). * - * When {@link threadIds} is omitted the attachment references every thread - * on the channel; when present it references only the listed - * {@link CommentThread.id | thread ids}. + * When {@link annotationIds} is omitted the attachment references every + * annotation on the channel; when present it references only the listed + * {@link Annotation.id | annotation ids}. * * @category Turn Types */ -export interface MessageCommentsAttachment extends MessageAttachmentBase { +export interface MessageAnnotationsAttachment extends MessageAttachmentBase { /** Discriminant */ - type: MessageAttachmentKind.Comments; + type: MessageAttachmentKind.Annotations; /** - * The comments channel URI (typically `ahp-session://comments`). - * Matches {@link CommentsSummary.resource}. + * The annotations channel URI (typically `ahp-session://annotations`). + * Matches {@link AnnotationsSummary.resource}. */ resource: URI; /** - * Specific {@link CommentThread.id | thread ids} to reference. When - * omitted, the attachment references all threads on the channel. + * Specific {@link Annotation.id | annotation ids} to reference. When + * omitted, the attachment references all annotations on the channel. */ - threadIds?: string[]; + annotationIds?: string[]; } /** @@ -818,7 +818,7 @@ export type MessageAttachment = | SimpleMessageAttachment | MessageEmbeddedResourceAttachment | MessageResourceAttachment - | MessageCommentsAttachment; + | MessageAnnotationsAttachment; // ─── Response Parts ────────────────────────────────────────────────────────── diff --git a/types/commands.ts b/types/commands.ts index dc9281d0..bf82f656 100644 --- a/types/commands.ts +++ b/types/commands.ts @@ -12,5 +12,5 @@ export * from './channels-root/commands.js'; export * from './channels-session/commands.js'; export * from './channels-terminal/commands.js'; export * from './channels-changeset/commands.js'; -export * from './channels-comments/commands.js'; +export * from './channels-annotations/commands.js'; export * from './channels-resource-watch/commands.js'; diff --git a/types/common/actions.ts b/types/common/actions.ts index bae49916..934d38c0 100644 --- a/types/common/actions.ts +++ b/types/common/actions.ts @@ -69,12 +69,11 @@ import type { } from '../channels-changeset/actions.js'; import type { - CommentsThreadSetAction, - CommentsThreadRemovedAction, - CommentsCommentSetAction, - CommentsCommentRemovedAction, - CommentsClearedAction, -} from '../channels-comments/actions.js'; + AnnotationsSetAction, + AnnotationsRemovedAction, + AnnotationsEntrySetAction, + AnnotationsEntryRemovedAction, +} from '../channels-annotations/actions.js'; import type { TerminalDataAction, @@ -151,11 +150,10 @@ export const enum ActionType { ChangesetOperationsChanged = 'changeset/operationsChanged', ChangesetOperationStatusChanged = 'changeset/operationStatusChanged', ChangesetCleared = 'changeset/cleared', - CommentsThreadSet = 'comments/threadSet', - CommentsThreadRemoved = 'comments/threadRemoved', - CommentsCommentSet = 'comments/commentSet', - CommentsCommentRemoved = 'comments/commentRemoved', - CommentsCleared = 'comments/cleared', + AnnotationsSet = 'annotations/set', + AnnotationsRemoved = 'annotations/removed', + AnnotationsEntrySet = 'annotations/entrySet', + AnnotationsEntryRemoved = 'annotations/entryRemoved', RootTerminalsChanged = 'root/terminalsChanged', RootConfigChanged = 'root/configChanged', TerminalData = 'terminal/data', @@ -257,11 +255,10 @@ export type StateAction = | ChangesetOperationsChangedAction | ChangesetOperationStatusChangedAction | ChangesetClearedAction - | CommentsThreadSetAction - | CommentsThreadRemovedAction - | CommentsCommentSetAction - | CommentsCommentRemovedAction - | CommentsClearedAction + | AnnotationsSetAction + | AnnotationsRemovedAction + | AnnotationsEntrySetAction + | AnnotationsEntryRemovedAction | TerminalDataAction | TerminalInputAction | TerminalResizedAction diff --git a/types/common/messages.ts b/types/common/messages.ts index 9e5ffedc..d33d9cce 100644 --- a/types/common/messages.ts +++ b/types/common/messages.ts @@ -67,15 +67,15 @@ import type { InvokeChangesetOperationResult, } from '../channels-changeset/commands.js'; import type { - CreateCommentThreadParams, - CreateCommentThreadResult, - UpdateCommentThreadParams, - DeleteCommentThreadParams, - AddCommentParams, - AddCommentResult, - EditCommentParams, - DeleteCommentParams, -} from '../channels-comments/commands.js'; + CreateAnnotationParams, + CreateAnnotationResult, + UpdateAnnotationParams, + DeleteAnnotationParams, + AddAnnotationEntryParams, + AddAnnotationEntryResult, + EditAnnotationEntryParams, + DeleteAnnotationEntryParams, +} from '../channels-annotations/commands.js'; import type { ActionEnvelope } from './actions.js'; import type { @@ -177,12 +177,12 @@ export interface CommandMap { 'sessionConfigCompletions': { params: SessionConfigCompletionsParams; result: SessionConfigCompletionsResult }; 'completions': { params: CompletionsParams; result: CompletionsResult }; 'invokeChangesetOperation': { params: InvokeChangesetOperationParams; result: InvokeChangesetOperationResult }; - 'createCommentThread': { params: CreateCommentThreadParams; result: CreateCommentThreadResult }; - 'updateCommentThread': { params: UpdateCommentThreadParams; result: null }; - 'deleteCommentThread': { params: DeleteCommentThreadParams; result: null }; - 'addComment': { params: AddCommentParams; result: AddCommentResult }; - 'editComment': { params: EditCommentParams; result: null }; - 'deleteComment': { params: DeleteCommentParams; result: null }; + 'createAnnotation': { params: CreateAnnotationParams; result: CreateAnnotationResult }; + 'updateAnnotation': { params: UpdateAnnotationParams; result: null }; + 'deleteAnnotation': { params: DeleteAnnotationParams; result: null }; + 'addAnnotationEntry': { params: AddAnnotationEntryParams; result: AddAnnotationEntryResult }; + 'editAnnotationEntry': { params: EditAnnotationEntryParams; result: null }; + 'deleteAnnotationEntry': { params: DeleteAnnotationEntryParams; result: null }; } /** diff --git a/types/common/reducer-helpers.ts b/types/common/reducer-helpers.ts index 9fa3a484..7742f2af 100644 --- a/types/common/reducer-helpers.ts +++ b/types/common/reducer-helpers.ts @@ -14,8 +14,8 @@ import type { ClientTerminalAction, ChangesetAction, ClientChangesetAction, - CommentsAction, - ClientCommentsAction, + AnnotationsAction, + ClientAnnotationsAction, } from '../action-origin.generated.js'; import { IS_CLIENT_DISPATCHABLE } from '../action-origin.generated.js'; @@ -40,6 +40,6 @@ export function softAssertNever(value: never, log?: (msg: string) => void): void * Servers SHOULD call this to validate incoming `dispatchAction` requests * and reject any action the client is not allowed to originate. */ -export function isClientDispatchable(action: RootAction | SessionAction | TerminalAction | ChangesetAction | CommentsAction): action is ClientRootAction | ClientSessionAction | ClientTerminalAction | ClientChangesetAction | ClientCommentsAction { +export function isClientDispatchable(action: RootAction | SessionAction | TerminalAction | ChangesetAction | AnnotationsAction): action is ClientRootAction | ClientSessionAction | ClientTerminalAction | ClientChangesetAction | ClientAnnotationsAction { return IS_CLIENT_DISPATCHABLE[action.type]; } diff --git a/types/common/state.ts b/types/common/state.ts index 21cd9405..be87a53f 100644 --- a/types/common/state.ts +++ b/types/common/state.ts @@ -11,7 +11,7 @@ import type { RootState } from '../channels-root/state.js'; import type { SessionState } from '../channels-session/state.js'; import type { TerminalState } from '../channels-terminal/state.js'; import type { ChangesetState } from '../channels-changeset/state.js'; -import type { CommentsState } from '../channels-comments/state.js'; +import type { AnnotationsState } from '../channels-annotations/state.js'; // ─── Type Aliases ──────────────────────────────────────────────────────────── @@ -324,7 +324,7 @@ export interface Snapshot { /** The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`) */ resource: URI; /** The current state of the resource */ - state: RootState | SessionState | TerminalState | ChangesetState | CommentsState; + state: RootState | SessionState | TerminalState | ChangesetState | AnnotationsState; /** The `serverSeq` at which this snapshot was taken. Subsequent actions will have `serverSeq > fromSeq`. */ fromSeq: number; } diff --git a/types/index.ts b/types/index.ts index bd310a7b..5d03f192 100644 --- a/types/index.ts +++ b/types/index.ts @@ -34,7 +34,7 @@ export type { SimpleMessageAttachment, MessageEmbeddedResourceAttachment, MessageResourceAttachment, - MessageCommentsAttachment, + MessageAnnotationsAttachment, MarkdownResponsePart, ContentRef, ToolCallResponsePart, @@ -94,11 +94,11 @@ export type { ChangesetState, ChangesetFile, ChangesetOperation, - CommentsSummary, - CommentsState, - CommentThread, - Comment, - NewComment, + AnnotationsSummary, + AnnotationsState, + Annotation, + AnnotationEntry, + NewAnnotationEntry, TelemetryCapabilities, ResourceWatchState, ResourceChange, @@ -176,11 +176,10 @@ export type { ChangesetOperationsChangedAction, ChangesetOperationStatusChangedAction, ChangesetClearedAction, - CommentsThreadSetAction, - CommentsThreadRemovedAction, - CommentsCommentSetAction, - CommentsCommentRemovedAction, - CommentsClearedAction, + AnnotationsSetAction, + AnnotationsRemovedAction, + AnnotationsEntrySetAction, + AnnotationsEntryRemovedAction, StateAction, RootTerminalsChangedAction, RootConfigChangedAction, @@ -212,9 +211,9 @@ export type { ChangesetAction, ClientChangesetAction, ServerChangesetAction, - CommentsAction, - ClientCommentsAction, - ServerCommentsAction, + AnnotationsAction, + ClientAnnotationsAction, + ServerAnnotationsAction, ResourceWatchAction, ClientResourceWatchAction, ServerResourceWatchAction, @@ -228,7 +227,7 @@ export { sessionReducer, terminalReducer, changesetReducer, - commentsReducer, + annotationsReducer, resourceWatchReducer, isClientDispatchable, } from './reducers.js'; @@ -295,14 +294,14 @@ export type { InvokeChangesetOperationResult, ChangesetOperationTarget, ChangesetOperationFollowUp, - CreateCommentThreadParams, - CreateCommentThreadResult, - UpdateCommentThreadParams, - DeleteCommentThreadParams, - AddCommentParams, - AddCommentResult, - EditCommentParams, - DeleteCommentParams, + CreateAnnotationParams, + CreateAnnotationResult, + UpdateAnnotationParams, + DeleteAnnotationParams, + AddAnnotationEntryParams, + AddAnnotationEntryResult, + EditAnnotationEntryParams, + DeleteAnnotationEntryParams, } from './commands.js'; export { ReconnectResultType, ContentEncoding, CompletionItemKind, ResourceType, ResourceWriteMode } from './commands.js'; diff --git a/types/messages.test.ts b/types/messages.test.ts index 12af7dc0..264a1ecb 100644 --- a/types/messages.test.ts +++ b/types/messages.test.ts @@ -30,7 +30,7 @@ function readChannelSources(baseName: string): string { 'channels-session', 'channels-terminal', 'channels-changeset', - 'channels-comments', + 'channels-annotations', 'channels-resource-watch', ]; return dirs diff --git a/types/reducers.test.ts b/types/reducers.test.ts index 420af191..4a99a3dd 100644 --- a/types/reducers.test.ts +++ b/types/reducers.test.ts @@ -22,13 +22,13 @@ import { sessionReducer, terminalReducer, changesetReducer, - commentsReducer, + annotationsReducer, resourceWatchReducer, isClientDispatchable, } from './reducers.js'; import { IS_CLIENT_DISPATCHABLE } from './action-origin.generated.js'; import { ActionType } from './actions.js'; -import type { RootState, SessionState, TerminalState, ChangesetState, CommentsState, ResourceWatchState } from './state.js'; +import type { RootState, SessionState, TerminalState, ChangesetState, AnnotationsState, ResourceWatchState } from './state.js'; import { SessionLifecycle, SessionStatus, @@ -51,7 +51,7 @@ function readChannelSources(baseName: string): string { 'channels-session', 'channels-terminal', 'channels-changeset', - 'channels-comments', + 'channels-annotations', 'channels-resource-watch', ]; return dirs @@ -68,11 +68,11 @@ function readChannelSources(baseName: string): string { // ─── Fixture Loading ───────────────────────────────────────────────────────── -type FixtureState = RootState | SessionState | TerminalState | ChangesetState | CommentsState | ResourceWatchState; +type FixtureState = RootState | SessionState | TerminalState | ChangesetState | AnnotationsState | ResourceWatchState; interface Fixture { description: string; - reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'comments' | 'resourceWatch'; + reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'annotations' | 'resourceWatch'; initial: FixtureState; actions: unknown[]; expected: FixtureState; @@ -133,8 +133,8 @@ describe('reducer fixtures', () => { state = terminalReducer(state as TerminalState, action as any); } else if (fixture.reducer === 'changeset') { state = changesetReducer(state as ChangesetState, action as any); - } else if (fixture.reducer === 'comments') { - state = commentsReducer(state as CommentsState, action as any); + } else if (fixture.reducer === 'annotations') { + state = annotationsReducer(state as AnnotationsState, action as any); } else if (fixture.reducer === 'resourceWatch') { state = resourceWatchReducer(state as ResourceWatchState, action as any); } else { diff --git a/types/reducers.ts b/types/reducers.ts index 6a91272b..eb2d615a 100644 --- a/types/reducers.ts +++ b/types/reducers.ts @@ -9,6 +9,6 @@ export { rootReducer } from './channels-root/reducer.js'; export { sessionReducer } from './channels-session/reducer.js'; export { terminalReducer } from './channels-terminal/reducer.js'; export { changesetReducer } from './channels-changeset/reducer.js'; -export { commentsReducer } from './channels-comments/reducer.js'; +export { annotationsReducer } from './channels-annotations/reducer.js'; export { resourceWatchReducer } from './channels-resource-watch/reducer.js'; export { softAssertNever, isClientDispatchable } from './common/reducer-helpers.js'; diff --git a/types/state.ts b/types/state.ts index 0670e165..03de20b7 100644 --- a/types/state.ts +++ b/types/state.ts @@ -12,6 +12,6 @@ export * from './channels-root/state.js'; export * from './channels-session/state.js'; export * from './channels-terminal/state.js'; export * from './channels-changeset/state.js'; -export * from './channels-comments/state.js'; +export * from './channels-annotations/state.js'; export * from './channels-otlp/state.js'; export * from './channels-resource-watch/state.js'; diff --git a/types/test-cases/reducers/210-comments-threadset-appends-new-thread.json b/types/test-cases/reducers/210-annotations-set-appends-new-annotation.json similarity index 68% rename from types/test-cases/reducers/210-comments-threadset-appends-new-thread.json rename to types/test-cases/reducers/210-annotations-set-appends-new-annotation.json index 62330cf1..8fc9db8c 100644 --- a/types/test-cases/reducers/210-comments-threadset-appends-new-thread.json +++ b/types/test-cases/reducers/210-annotations-set-appends-new-annotation.json @@ -1,8 +1,8 @@ { - "description": "comments/threadSet appends a new thread when its id is unknown", - "reducer": "comments", + "description": "annotations/set appends a new annotation when its id is unknown", + "reducer": "annotations", "initial": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -12,10 +12,10 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", - "text": "first thread, first comment" + "text": "first annotation, first entry" } ] } @@ -23,23 +23,23 @@ }, "actions": [ { - "type": "comments/threadSet", - "thread": { + "type": "annotations/set", + "annotation": { "id": "t-2", "turnId": "turn-2", "resource": "file:///src/b.ts", "resolved": false, - "comments": [ + "entries": [ { "id": "c-2", - "text": "second thread, first comment" + "text": "second annotation, first entry" } ] } } ], "expected": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -49,10 +49,10 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", - "text": "first thread, first comment" + "text": "first annotation, first entry" } ] }, @@ -61,10 +61,10 @@ "turnId": "turn-2", "resource": "file:///src/b.ts", "resolved": false, - "comments": [ + "entries": [ { "id": "c-2", - "text": "second thread, first comment" + "text": "second annotation, first entry" } ] } diff --git a/types/test-cases/reducers/211-comments-threadset-replaces-existing-thread.json b/types/test-cases/reducers/211-annotations-set-replaces-existing-annotation.json similarity index 71% rename from types/test-cases/reducers/211-comments-threadset-replaces-existing-thread.json rename to types/test-cases/reducers/211-annotations-set-replaces-existing-annotation.json index c6e2d7c9..98c01a53 100644 --- a/types/test-cases/reducers/211-comments-threadset-replaces-existing-thread.json +++ b/types/test-cases/reducers/211-annotations-set-replaces-existing-annotation.json @@ -1,8 +1,8 @@ { - "description": "comments/threadSet replaces an existing thread when the id matches, dropping the range to anchor to the whole file and marking it resolved", - "reducer": "comments", + "description": "annotations/set replaces an existing annotation when the id matches, dropping the range to anchor to the whole file and marking it resolved", + "reducer": "annotations", "initial": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -12,7 +12,7 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", "text": "original" } ] } @@ -20,13 +20,13 @@ }, "actions": [ { - "type": "comments/threadSet", - "thread": { + "type": "annotations/set", + "annotation": { "id": "t-1", "turnId": "turn-2", "resource": "file:///src/a.ts", "resolved": true, - "comments": [ + "entries": [ { "id": "c-1", "text": "rewritten" }, { "id": "c-2", "text": "reply" } ] @@ -34,13 +34,13 @@ } ], "expected": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-2", "resource": "file:///src/a.ts", "resolved": true, - "comments": [ + "entries": [ { "id": "c-1", "text": "rewritten" }, { "id": "c-2", "text": "reply" } ] diff --git a/types/test-cases/reducers/212-comments-threadremoved-drops-matching-thread.json b/types/test-cases/reducers/212-annotations-removed-drops-matching-annotation.json similarity index 73% rename from types/test-cases/reducers/212-comments-threadremoved-drops-matching-thread.json rename to types/test-cases/reducers/212-annotations-removed-drops-matching-annotation.json index 05bb0700..8403f0c7 100644 --- a/types/test-cases/reducers/212-comments-threadremoved-drops-matching-thread.json +++ b/types/test-cases/reducers/212-annotations-removed-drops-matching-annotation.json @@ -1,8 +1,8 @@ { - "description": "comments/threadRemoved drops the matching thread and is a no-op for unknown ids", - "reducer": "comments", + "description": "annotations/removed drops the matching annotation and is a no-op for unknown ids", + "reducer": "annotations", "initial": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -12,7 +12,7 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", "text": "keep" } ] }, @@ -25,18 +25,18 @@ "end": { "line": 2, "character": 4 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-2", "text": "drop me" } ] } ] }, "actions": [ - { "type": "comments/threadRemoved", "threadId": "t-2" }, - { "type": "comments/threadRemoved", "threadId": "missing" } + { "type": "annotations/removed", "annotationId": "t-2" }, + { "type": "annotations/removed", "annotationId": "missing" } ], "expected": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -46,7 +46,7 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", "text": "keep" } ] } diff --git a/types/test-cases/reducers/213-comments-commentset-appends-and-replaces.json b/types/test-cases/reducers/213-annotations-entryset-appends-and-replaces.json similarity index 64% rename from types/test-cases/reducers/213-comments-commentset-appends-and-replaces.json rename to types/test-cases/reducers/213-annotations-entryset-appends-and-replaces.json index 757cd553..6f0ff55b 100644 --- a/types/test-cases/reducers/213-comments-commentset-appends-and-replaces.json +++ b/types/test-cases/reducers/213-annotations-entryset-appends-and-replaces.json @@ -1,8 +1,8 @@ { - "description": "comments/commentSet appends or replaces a comment within a thread", - "reducer": "comments", + "description": "annotations/entrySet appends or replaces an entry within an annotation", + "reducer": "annotations", "initial": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -12,7 +12,7 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", "text": "original" } ] } @@ -20,18 +20,18 @@ }, "actions": [ { - "type": "comments/commentSet", - "threadId": "t-1", - "comment": { "id": "c-2", "text": "second" } + "type": "annotations/entrySet", + "annotationId": "t-1", + "entry": { "id": "c-2", "text": "second" } }, { - "type": "comments/commentSet", - "threadId": "t-1", - "comment": { "id": "c-1", "text": "edited" } + "type": "annotations/entrySet", + "annotationId": "t-1", + "entry": { "id": "c-1", "text": "edited" } } ], "expected": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -41,7 +41,7 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", "text": "edited" }, { "id": "c-2", "text": "second" } ] diff --git a/types/test-cases/reducers/214-comments-commentset-unknown-thread-is-no-op.json b/types/test-cases/reducers/214-annotations-entryset-unknown-annotation-is-no-op.json similarity index 68% rename from types/test-cases/reducers/214-comments-commentset-unknown-thread-is-no-op.json rename to types/test-cases/reducers/214-annotations-entryset-unknown-annotation-is-no-op.json index 5bf3745b..d52f66bf 100644 --- a/types/test-cases/reducers/214-comments-commentset-unknown-thread-is-no-op.json +++ b/types/test-cases/reducers/214-annotations-entryset-unknown-annotation-is-no-op.json @@ -1,8 +1,8 @@ { - "description": "comments/commentSet is a no-op when the enclosing thread is unknown", - "reducer": "comments", + "description": "annotations/entrySet is a no-op when the enclosing annotation is unknown", + "reducer": "annotations", "initial": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -12,7 +12,7 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", "text": "only" } ] } @@ -20,13 +20,13 @@ }, "actions": [ { - "type": "comments/commentSet", - "threadId": "missing", - "comment": { "id": "c-x", "text": "lost" } + "type": "annotations/entrySet", + "annotationId": "missing", + "entry": { "id": "c-x", "text": "lost" } } ], "expected": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -36,7 +36,7 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", "text": "only" } ] } diff --git a/types/test-cases/reducers/215-comments-commentremoved-drops-matching-comment.json b/types/test-cases/reducers/215-annotations-entryremoved-drops-matching-entry.json similarity index 63% rename from types/test-cases/reducers/215-comments-commentremoved-drops-matching-comment.json rename to types/test-cases/reducers/215-annotations-entryremoved-drops-matching-entry.json index 69a8b08e..f782f222 100644 --- a/types/test-cases/reducers/215-comments-commentremoved-drops-matching-comment.json +++ b/types/test-cases/reducers/215-annotations-entryremoved-drops-matching-entry.json @@ -1,8 +1,8 @@ { - "description": "comments/commentRemoved drops the matching comment and leaves other comments untouched", - "reducer": "comments", + "description": "annotations/entryRemoved drops the matching entry and leaves other entries untouched", + "reducer": "annotations", "initial": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -12,7 +12,7 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", "text": "first" }, { "id": "c-2", "text": "second" }, { "id": "c-3", "text": "third" } @@ -21,12 +21,12 @@ ] }, "actions": [ - { "type": "comments/commentRemoved", "threadId": "t-1", "commentId": "c-2" }, - { "type": "comments/commentRemoved", "threadId": "t-1", "commentId": "missing" }, - { "type": "comments/commentRemoved", "threadId": "missing", "commentId": "c-1" } + { "type": "annotations/entryRemoved", "annotationId": "t-1", "entryId": "c-2" }, + { "type": "annotations/entryRemoved", "annotationId": "t-1", "entryId": "missing" }, + { "type": "annotations/entryRemoved", "annotationId": "missing", "entryId": "c-1" } ], "expected": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -36,7 +36,7 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", "text": "first" }, { "id": "c-3", "text": "third" } ] diff --git a/types/test-cases/reducers/216-comments-cleared-empties-threads.json b/types/test-cases/reducers/216-comments-cleared-empties-threads.json deleted file mode 100644 index c78d2118..00000000 --- a/types/test-cases/reducers/216-comments-cleared-empties-threads.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "description": "comments/cleared empties the thread list and is a no-op on an already-empty channel", - "reducer": "comments", - "initial": { - "threads": [ - { - "id": "t-1", - "turnId": "turn-1", - "resource": "file:///src/a.ts", - "range": { - "start": { "line": 0, "character": 0 }, - "end": { "line": 0, "character": 5 } - }, - "resolved": false, - "comments": [ - { "id": "c-1", "text": "first" } - ] - } - ] - }, - "actions": [ - { "type": "comments/cleared" }, - { "type": "comments/cleared" } - ], - "expected": { - "threads": [] - } -} diff --git a/types/test-cases/reducers/217-comments-unknown-action-type-is-no-op.json b/types/test-cases/reducers/217-annotations-unknown-action-type-is-no-op.json similarity index 75% rename from types/test-cases/reducers/217-comments-unknown-action-type-is-no-op.json rename to types/test-cases/reducers/217-annotations-unknown-action-type-is-no-op.json index e2f67e72..16a92663 100644 --- a/types/test-cases/reducers/217-comments-unknown-action-type-is-no-op.json +++ b/types/test-cases/reducers/217-annotations-unknown-action-type-is-no-op.json @@ -1,8 +1,8 @@ { - "description": "comments unknown action type is a no-op", - "reducer": "comments", + "description": "annotations unknown action type is a no-op", + "reducer": "annotations", "initial": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -12,17 +12,17 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", "text": "only" } ] } ] }, "actions": [ - { "type": "comments/unknownActionType" } + { "type": "annotations/unknownActionType" } ], "expected": { - "threads": [ + "annotations": [ { "id": "t-1", "turnId": "turn-1", @@ -32,7 +32,7 @@ "end": { "line": 0, "character": 5 } }, "resolved": false, - "comments": [ + "entries": [ { "id": "c-1", "text": "only" } ] } diff --git a/types/version/message-checks.ts b/types/version/message-checks.ts index a9ab12bc..831b570d 100644 --- a/types/version/message-checks.ts +++ b/types/version/message-checks.ts @@ -75,12 +75,12 @@ type _ExpectedCommands = | 'sessionConfigCompletions' | 'completions' | 'invokeChangesetOperation' - | 'createCommentThread' - | 'updateCommentThread' - | 'deleteCommentThread' - | 'addComment' - | 'editComment' - | 'deleteComment'; + | 'createAnnotation' + | 'updateAnnotation' + | 'deleteAnnotation' + | 'addAnnotationEntry' + | 'editAnnotationEntry' + | 'deleteAnnotationEntry'; /** All methods annotated `@messageType Notification` (client → server). */ type _ExpectedClientNotifications = diff --git a/types/version/registry.ts b/types/version/registry.ts index 174b8ebf..bad97aa1 100644 --- a/types/version/registry.ts +++ b/types/version/registry.ts @@ -124,11 +124,10 @@ export const ACTION_INTRODUCED_IN: { readonly [K in StateAction['type']]: string [ActionType.ChangesetOperationsChanged]: '0.2.0', [ActionType.ChangesetOperationStatusChanged]: '0.3.0', [ActionType.ChangesetCleared]: '0.2.0', - [ActionType.CommentsThreadSet]: '0.3.0', - [ActionType.CommentsThreadRemoved]: '0.3.0', - [ActionType.CommentsCommentSet]: '0.3.0', - [ActionType.CommentsCommentRemoved]: '0.3.0', - [ActionType.CommentsCleared]: '0.3.0', + [ActionType.AnnotationsSet]: '0.3.0', + [ActionType.AnnotationsRemoved]: '0.3.0', + [ActionType.AnnotationsEntrySet]: '0.3.0', + [ActionType.AnnotationsEntryRemoved]: '0.3.0', [ActionType.RootTerminalsChanged]: '0.1.0', [ActionType.RootConfigChanged]: '0.1.0', [ActionType.TerminalData]: '0.1.0', From ee46f071a8bced2c22873516d6e77a50e144dd92 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 8 Jun 2026 16:39:55 +0200 Subject: [PATCH 10/11] fix tests --- clients/go/ahptypes/actions.generated.go | 334 +++++++++--------- clients/go/ahptypes/commands.generated.go | 58 +-- clients/go/ahptypes/errors.generated.go | 20 +- .../go/ahptypes/notifications.generated.go | 28 +- clients/go/ahptypes/state.generated.go | 322 ++++++++--------- .../generated/State.generated.kt | 2 +- .../Generated/State.generated.swift | 2 +- 7 files changed, 383 insertions(+), 383 deletions(-) diff --git a/clients/go/ahptypes/actions.generated.go b/clients/go/ahptypes/actions.generated.go index c3a81ab9..0e14d528 100644 --- a/clients/go/ahptypes/actions.generated.go +++ b/clients/go/ahptypes/actions.generated.go @@ -19,81 +19,81 @@ var _ = json.RawMessage(nil) type ActionType string const ( - ActionTypeRootAgentsChanged ActionType = "root/agentsChanged" - ActionTypeRootActiveSessionsChanged ActionType = "root/activeSessionsChanged" - ActionTypeSessionReady ActionType = "session/ready" - ActionTypeSessionCreationFailed ActionType = "session/creationFailed" - ActionTypeSessionTurnStarted ActionType = "session/turnStarted" - ActionTypeSessionDelta ActionType = "session/delta" - ActionTypeSessionResponsePart ActionType = "session/responsePart" - ActionTypeSessionToolCallStart ActionType = "session/toolCallStart" - ActionTypeSessionToolCallDelta ActionType = "session/toolCallDelta" - ActionTypeSessionToolCallReady ActionType = "session/toolCallReady" - ActionTypeSessionToolCallConfirmed ActionType = "session/toolCallConfirmed" - ActionTypeSessionToolCallComplete ActionType = "session/toolCallComplete" - ActionTypeSessionToolCallResultConfirmed ActionType = "session/toolCallResultConfirmed" - ActionTypeSessionToolCallContentChanged ActionType = "session/toolCallContentChanged" - ActionTypeSessionTurnComplete ActionType = "session/turnComplete" - ActionTypeSessionTurnCancelled ActionType = "session/turnCancelled" - ActionTypeSessionError ActionType = "session/error" - ActionTypeSessionTitleChanged ActionType = "session/titleChanged" - ActionTypeSessionUsage ActionType = "session/usage" - ActionTypeSessionReasoning ActionType = "session/reasoning" - ActionTypeSessionModelChanged ActionType = "session/modelChanged" - ActionTypeSessionAgentChanged ActionType = "session/agentChanged" - ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged" - ActionTypeSessionActiveClientChanged ActionType = "session/activeClientChanged" - ActionTypeSessionActiveClientToolsChanged ActionType = "session/activeClientToolsChanged" - ActionTypeSessionPendingMessageSet ActionType = "session/pendingMessageSet" - ActionTypeSessionPendingMessageRemoved ActionType = "session/pendingMessageRemoved" - ActionTypeSessionQueuedMessagesReordered ActionType = "session/queuedMessagesReordered" - ActionTypeSessionInputRequested ActionType = "session/inputRequested" - ActionTypeSessionInputAnswerChanged ActionType = "session/inputAnswerChanged" - ActionTypeSessionInputCompleted ActionType = "session/inputCompleted" - ActionTypeSessionCustomizationsChanged ActionType = "session/customizationsChanged" - ActionTypeSessionCustomizationToggled ActionType = "session/customizationToggled" - ActionTypeSessionCustomizationUpdated ActionType = "session/customizationUpdated" - ActionTypeSessionCustomizationRemoved ActionType = "session/customizationRemoved" - ActionTypeSessionMcpServerStateChanged ActionType = "session/mcpServerStateChanged" - ActionTypeSessionTruncated ActionType = "session/truncated" - 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" - ActionTypeChangesetFileSet ActionType = "changeset/fileSet" - ActionTypeChangesetFileRemoved ActionType = "changeset/fileRemoved" - ActionTypeChangesetOperationsChanged ActionType = "changeset/operationsChanged" - ActionTypeChangesetOperationStatusChanged ActionType = "changeset/operationStatusChanged" - ActionTypeChangesetCleared ActionType = "changeset/cleared" - ActionTypeAnnotationsSet ActionType = "annotations/set" - ActionTypeAnnotationsRemoved ActionType = "annotations/removed" - ActionTypeAnnotationsEntrySet ActionType = "annotations/entrySet" - ActionTypeAnnotationsEntryRemoved ActionType = "annotations/entryRemoved" - ActionTypeRootTerminalsChanged ActionType = "root/terminalsChanged" - ActionTypeRootConfigChanged ActionType = "root/configChanged" - ActionTypeTerminalData ActionType = "terminal/data" - ActionTypeTerminalInput ActionType = "terminal/input" - ActionTypeTerminalResized ActionType = "terminal/resized" - ActionTypeTerminalClaimed ActionType = "terminal/claimed" - ActionTypeTerminalTitleChanged ActionType = "terminal/titleChanged" - ActionTypeTerminalCwdChanged ActionType = "terminal/cwdChanged" - ActionTypeTerminalExited ActionType = "terminal/exited" - ActionTypeTerminalCleared ActionType = "terminal/cleared" + ActionTypeRootAgentsChanged ActionType = "root/agentsChanged" + ActionTypeRootActiveSessionsChanged ActionType = "root/activeSessionsChanged" + ActionTypeSessionReady ActionType = "session/ready" + ActionTypeSessionCreationFailed ActionType = "session/creationFailed" + ActionTypeSessionTurnStarted ActionType = "session/turnStarted" + ActionTypeSessionDelta ActionType = "session/delta" + ActionTypeSessionResponsePart ActionType = "session/responsePart" + ActionTypeSessionToolCallStart ActionType = "session/toolCallStart" + ActionTypeSessionToolCallDelta ActionType = "session/toolCallDelta" + ActionTypeSessionToolCallReady ActionType = "session/toolCallReady" + ActionTypeSessionToolCallConfirmed ActionType = "session/toolCallConfirmed" + ActionTypeSessionToolCallComplete ActionType = "session/toolCallComplete" + ActionTypeSessionToolCallResultConfirmed ActionType = "session/toolCallResultConfirmed" + ActionTypeSessionToolCallContentChanged ActionType = "session/toolCallContentChanged" + ActionTypeSessionTurnComplete ActionType = "session/turnComplete" + ActionTypeSessionTurnCancelled ActionType = "session/turnCancelled" + ActionTypeSessionError ActionType = "session/error" + ActionTypeSessionTitleChanged ActionType = "session/titleChanged" + ActionTypeSessionUsage ActionType = "session/usage" + ActionTypeSessionReasoning ActionType = "session/reasoning" + ActionTypeSessionModelChanged ActionType = "session/modelChanged" + ActionTypeSessionAgentChanged ActionType = "session/agentChanged" + ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged" + ActionTypeSessionActiveClientChanged ActionType = "session/activeClientChanged" + ActionTypeSessionActiveClientToolsChanged ActionType = "session/activeClientToolsChanged" + ActionTypeSessionPendingMessageSet ActionType = "session/pendingMessageSet" + ActionTypeSessionPendingMessageRemoved ActionType = "session/pendingMessageRemoved" + ActionTypeSessionQueuedMessagesReordered ActionType = "session/queuedMessagesReordered" + ActionTypeSessionInputRequested ActionType = "session/inputRequested" + ActionTypeSessionInputAnswerChanged ActionType = "session/inputAnswerChanged" + ActionTypeSessionInputCompleted ActionType = "session/inputCompleted" + ActionTypeSessionCustomizationsChanged ActionType = "session/customizationsChanged" + ActionTypeSessionCustomizationToggled ActionType = "session/customizationToggled" + ActionTypeSessionCustomizationUpdated ActionType = "session/customizationUpdated" + ActionTypeSessionCustomizationRemoved ActionType = "session/customizationRemoved" + ActionTypeSessionMcpServerStateChanged ActionType = "session/mcpServerStateChanged" + ActionTypeSessionTruncated ActionType = "session/truncated" + 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" + ActionTypeChangesetFileSet ActionType = "changeset/fileSet" + ActionTypeChangesetFileRemoved ActionType = "changeset/fileRemoved" + ActionTypeChangesetOperationsChanged ActionType = "changeset/operationsChanged" + ActionTypeChangesetOperationStatusChanged ActionType = "changeset/operationStatusChanged" + ActionTypeChangesetCleared ActionType = "changeset/cleared" + ActionTypeAnnotationsSet ActionType = "annotations/set" + ActionTypeAnnotationsRemoved ActionType = "annotations/removed" + ActionTypeAnnotationsEntrySet ActionType = "annotations/entrySet" + ActionTypeAnnotationsEntryRemoved ActionType = "annotations/entryRemoved" + ActionTypeRootTerminalsChanged ActionType = "root/terminalsChanged" + ActionTypeRootConfigChanged ActionType = "root/configChanged" + ActionTypeTerminalData ActionType = "terminal/data" + ActionTypeTerminalInput ActionType = "terminal/input" + ActionTypeTerminalResized ActionType = "terminal/resized" + ActionTypeTerminalClaimed ActionType = "terminal/claimed" + ActionTypeTerminalTitleChanged ActionType = "terminal/titleChanged" + ActionTypeTerminalCwdChanged ActionType = "terminal/cwdChanged" + ActionTypeTerminalExited ActionType = "terminal/exited" + ActionTypeTerminalCleared ActionType = "terminal/cleared" ActionTypeTerminalCommandDetectionAvailable ActionType = "terminal/commandDetectionAvailable" - ActionTypeTerminalCommandExecuted ActionType = "terminal/commandExecuted" - ActionTypeTerminalCommandFinished ActionType = "terminal/commandFinished" - ActionTypeResourceWatchChanged ActionType = "resourceWatch/changed" + ActionTypeTerminalCommandExecuted ActionType = "terminal/commandExecuted" + ActionTypeTerminalCommandFinished ActionType = "terminal/commandFinished" + ActionTypeResourceWatchChanged ActionType = "resourceWatch/changed" ) // ─── Action Envelope ───────────────────────────────────────────────── // Identifies the client that originally dispatched an action. type ActionOrigin struct { - ClientId string `json:"clientId"` - ClientSeq int64 `json:"clientSeq"` + ClientId string `json:"clientId"` + ClientSeq int64 `json:"clientSeq"` } // ActionEnvelope wraps every action with the channel URI it @@ -202,7 +202,7 @@ type SessionToolCallStartAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Internal tool name (for debugging/logging) ToolName string `json:"toolName"` // Human-readable tool name @@ -225,7 +225,7 @@ type SessionToolCallDeltaAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Partial parameter content to append Content string `json:"content"` // Updated progress message @@ -257,7 +257,7 @@ type SessionToolCallReadyAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Message describing what the tool will do or what confirmation is needed InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input @@ -280,17 +280,17 @@ type SessionToolCallReadyAction struct { // SessionToolCallConfirmedAction is the client approves or denies a // pending tool call (merged approved + denied variants on the wire). type SessionToolCallConfirmedAction struct { - Type ActionType `json:"type"` - TurnId string `json:"turnId"` - ToolCallId string `json:"toolCallId"` - Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Approved bool `json:"approved"` - Confirmed *ToolCallConfirmationReason `json:"confirmed,omitempty"` - Reason *ToolCallCancellationReason `json:"reason,omitempty"` - EditedToolInput *string `json:"editedToolInput,omitempty"` - UserSuggestion *Message `json:"userSuggestion,omitempty"` - ReasonMessage *StringOrMarkdown `json:"reasonMessage,omitempty"` - SelectedOptionId *string `json:"selectedOptionId,omitempty"` + Type ActionType `json:"type"` + TurnId string `json:"turnId"` + ToolCallId string `json:"toolCallId"` + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Approved bool `json:"approved"` + Confirmed *ToolCallConfirmationReason `json:"confirmed,omitempty"` + Reason *ToolCallCancellationReason `json:"reason,omitempty"` + EditedToolInput *string `json:"editedToolInput,omitempty"` + UserSuggestion *Message `json:"userSuggestion,omitempty"` + ReasonMessage *StringOrMarkdown `json:"reasonMessage,omitempty"` + SelectedOptionId *string `json:"selectedOptionId,omitempty"` } // Tool execution finished. Transitions to `completed` or `pending-result-confirmation` @@ -315,7 +315,7 @@ type SessionToolCallCompleteAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Execution result Result ToolCallResult `json:"result"` // If true, the result requires client approval before finalizing @@ -337,7 +337,7 @@ type SessionToolCallResultConfirmedAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // Whether the result was approved Approved bool `json:"approved"` } @@ -607,10 +607,10 @@ type SessionCustomizationToggledAction struct { // // The reducer locates the existing entry by `customization.id`: // -// - If found, the entry is replaced entirely with `customization`, -// including its `children` array. To preserve existing children, the -// host must include them on the payload. -// - If not found, the entry is appended. +// - If found, the entry is replaced entirely with `customization`, +// including its `children` array. To preserve existing children, the +// host must include them on the payload. +// - If not found, the entry is appended. type SessionCustomizationUpdatedAction struct { Type ActionType `json:"type"` // The customization to upsert (matched by `customization.id`). @@ -719,7 +719,7 @@ type SessionToolCallContentChangedAction struct { // indicates the tool operated on a terminal (both `input` and `output` may // contain escape sequences). Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` + Type ActionType `json:"type"` // The current partial content for the running tool call Content []ToolResultContent `json:"content"` } @@ -785,12 +785,12 @@ type ChangesetOperationStatusChangedAction struct { // Drop every file from the changeset. // // Two cases use this: -// 1. The underlying source moved (branch switched, fork point invalidated, -// …) and the server is recomputing from scratch — subsequent -// {@link ChangesetFileSetAction} entries will repopulate it. -// 2. The owning session has ended and the URI is becoming -// un-subscribable — the server will unsubscribe all clients shortly -// after dispatching this action. +// 1. The underlying source moved (branch switched, fork point invalidated, +// …) and the server is recomputing from scratch — subsequent +// {@link ChangesetFileSetAction} entries will repopulate it. +// 2. The owning session has ended and the URI is becoming +// un-subscribable — the server will unsubscribe all clients shortly +// after dispatching this action. // // Clients SHOULD release any references on receipt and SHOULD NOT // distinguish the two cases from the action alone — instead, react to @@ -815,12 +815,12 @@ type AnnotationsSetAction struct { // Remove an {@link Annotation} from the channel by its id. // // The server emits this in two cases: -// 1. The client explicitly invoked -// {@link DeleteAnnotationParams | `deleteAnnotation`}. -// 2. The client invoked {@link DeleteAnnotationEntryParams | -// `deleteAnnotationEntry`} on the last remaining entry in the -// annotation — the protocol collapses the annotation rather than -// leaving an empty one behind. +// 1. The client explicitly invoked +// {@link DeleteAnnotationParams | `deleteAnnotation`}. +// 2. The client invoked {@link DeleteAnnotationEntryParams | +// `deleteAnnotationEntry`} on the last remaining entry in the +// annotation — the protocol collapses the annotation rather than +// leaving an empty one behind. type AnnotationsRemovedAction struct { Type ActionType `json:"type"` // The {@link Annotation.id} of the annotation to remove. @@ -1009,73 +1009,73 @@ type StateAction struct { // concrete variant of StateAction. type isStateAction interface{ isStateAction() } -func (*RootAgentsChangedAction) isStateAction() {} -func (*RootActiveSessionsChangedAction) isStateAction() {} -func (*RootConfigChangedAction) isStateAction() {} -func (*SessionReadyAction) isStateAction() {} -func (*SessionCreationFailedAction) isStateAction() {} -func (*SessionTurnStartedAction) isStateAction() {} -func (*SessionDeltaAction) isStateAction() {} -func (*SessionResponsePartAction) isStateAction() {} -func (*SessionToolCallStartAction) isStateAction() {} -func (*SessionToolCallDeltaAction) isStateAction() {} -func (*SessionToolCallReadyAction) isStateAction() {} -func (*SessionToolCallConfirmedAction) isStateAction() {} -func (*SessionToolCallCompleteAction) isStateAction() {} -func (*SessionToolCallResultConfirmedAction) isStateAction() {} -func (*SessionTurnCompleteAction) isStateAction() {} -func (*SessionTurnCancelledAction) isStateAction() {} -func (*SessionErrorAction) isStateAction() {} -func (*SessionTitleChangedAction) isStateAction() {} -func (*SessionUsageAction) isStateAction() {} -func (*SessionReasoningAction) isStateAction() {} -func (*SessionModelChangedAction) isStateAction() {} -func (*SessionAgentChangedAction) isStateAction() {} -func (*SessionIsReadChangedAction) isStateAction() {} -func (*SessionIsArchivedChangedAction) isStateAction() {} -func (*SessionActivityChangedAction) isStateAction() {} -func (*SessionChangesetsChangedAction) isStateAction() {} -func (*SessionServerToolsChangedAction) isStateAction() {} -func (*SessionActiveClientChangedAction) isStateAction() {} -func (*SessionActiveClientToolsChangedAction) isStateAction() {} -func (*SessionPendingMessageSetAction) isStateAction() {} -func (*SessionPendingMessageRemovedAction) isStateAction() {} -func (*SessionQueuedMessagesReorderedAction) isStateAction() {} -func (*SessionInputRequestedAction) isStateAction() {} -func (*SessionInputAnswerChangedAction) isStateAction() {} -func (*SessionInputCompletedAction) isStateAction() {} -func (*SessionCustomizationsChangedAction) isStateAction() {} -func (*SessionCustomizationToggledAction) isStateAction() {} -func (*SessionCustomizationUpdatedAction) isStateAction() {} -func (*SessionCustomizationRemovedAction) isStateAction() {} -func (*SessionMcpServerStateChangedAction) isStateAction() {} -func (*SessionTruncatedAction) isStateAction() {} -func (*SessionConfigChangedAction) isStateAction() {} -func (*SessionMetaChangedAction) isStateAction() {} -func (*SessionToolCallContentChangedAction) isStateAction() {} -func (*ChangesetStatusChangedAction) isStateAction() {} -func (*ChangesetFileSetAction) isStateAction() {} -func (*ChangesetFileRemovedAction) isStateAction() {} -func (*ChangesetOperationsChangedAction) isStateAction() {} -func (*ChangesetOperationStatusChangedAction) isStateAction() {} -func (*ChangesetClearedAction) isStateAction() {} -func (*AnnotationsSetAction) isStateAction() {} -func (*AnnotationsRemovedAction) isStateAction() {} -func (*AnnotationsEntrySetAction) isStateAction() {} -func (*AnnotationsEntryRemovedAction) isStateAction() {} -func (*RootTerminalsChangedAction) isStateAction() {} -func (*TerminalDataAction) isStateAction() {} -func (*TerminalInputAction) isStateAction() {} -func (*TerminalResizedAction) isStateAction() {} -func (*TerminalClaimedAction) isStateAction() {} -func (*TerminalTitleChangedAction) isStateAction() {} -func (*TerminalCwdChangedAction) isStateAction() {} -func (*TerminalExitedAction) isStateAction() {} -func (*TerminalClearedAction) isStateAction() {} +func (*RootAgentsChangedAction) isStateAction() {} +func (*RootActiveSessionsChangedAction) isStateAction() {} +func (*RootConfigChangedAction) isStateAction() {} +func (*SessionReadyAction) isStateAction() {} +func (*SessionCreationFailedAction) isStateAction() {} +func (*SessionTurnStartedAction) isStateAction() {} +func (*SessionDeltaAction) isStateAction() {} +func (*SessionResponsePartAction) isStateAction() {} +func (*SessionToolCallStartAction) isStateAction() {} +func (*SessionToolCallDeltaAction) isStateAction() {} +func (*SessionToolCallReadyAction) isStateAction() {} +func (*SessionToolCallConfirmedAction) isStateAction() {} +func (*SessionToolCallCompleteAction) isStateAction() {} +func (*SessionToolCallResultConfirmedAction) isStateAction() {} +func (*SessionTurnCompleteAction) isStateAction() {} +func (*SessionTurnCancelledAction) isStateAction() {} +func (*SessionErrorAction) isStateAction() {} +func (*SessionTitleChangedAction) isStateAction() {} +func (*SessionUsageAction) isStateAction() {} +func (*SessionReasoningAction) isStateAction() {} +func (*SessionModelChangedAction) isStateAction() {} +func (*SessionAgentChangedAction) isStateAction() {} +func (*SessionIsReadChangedAction) isStateAction() {} +func (*SessionIsArchivedChangedAction) isStateAction() {} +func (*SessionActivityChangedAction) isStateAction() {} +func (*SessionChangesetsChangedAction) isStateAction() {} +func (*SessionServerToolsChangedAction) isStateAction() {} +func (*SessionActiveClientChangedAction) isStateAction() {} +func (*SessionActiveClientToolsChangedAction) isStateAction() {} +func (*SessionPendingMessageSetAction) isStateAction() {} +func (*SessionPendingMessageRemovedAction) isStateAction() {} +func (*SessionQueuedMessagesReorderedAction) isStateAction() {} +func (*SessionInputRequestedAction) isStateAction() {} +func (*SessionInputAnswerChangedAction) isStateAction() {} +func (*SessionInputCompletedAction) isStateAction() {} +func (*SessionCustomizationsChangedAction) isStateAction() {} +func (*SessionCustomizationToggledAction) isStateAction() {} +func (*SessionCustomizationUpdatedAction) isStateAction() {} +func (*SessionCustomizationRemovedAction) isStateAction() {} +func (*SessionMcpServerStateChangedAction) isStateAction() {} +func (*SessionTruncatedAction) isStateAction() {} +func (*SessionConfigChangedAction) isStateAction() {} +func (*SessionMetaChangedAction) isStateAction() {} +func (*SessionToolCallContentChangedAction) isStateAction() {} +func (*ChangesetStatusChangedAction) isStateAction() {} +func (*ChangesetFileSetAction) isStateAction() {} +func (*ChangesetFileRemovedAction) isStateAction() {} +func (*ChangesetOperationsChangedAction) isStateAction() {} +func (*ChangesetOperationStatusChangedAction) isStateAction() {} +func (*ChangesetClearedAction) isStateAction() {} +func (*AnnotationsSetAction) isStateAction() {} +func (*AnnotationsRemovedAction) isStateAction() {} +func (*AnnotationsEntrySetAction) isStateAction() {} +func (*AnnotationsEntryRemovedAction) isStateAction() {} +func (*RootTerminalsChangedAction) isStateAction() {} +func (*TerminalDataAction) isStateAction() {} +func (*TerminalInputAction) isStateAction() {} +func (*TerminalResizedAction) isStateAction() {} +func (*TerminalClaimedAction) isStateAction() {} +func (*TerminalTitleChangedAction) isStateAction() {} +func (*TerminalCwdChangedAction) isStateAction() {} +func (*TerminalExitedAction) isStateAction() {} +func (*TerminalClearedAction) isStateAction() {} func (*TerminalCommandDetectionAvailableAction) isStateAction() {} -func (*TerminalCommandExecutedAction) isStateAction() {} -func (*TerminalCommandFinishedAction) isStateAction() {} -func (*ResourceWatchChangedAction) isStateAction() {} +func (*TerminalCommandExecutedAction) isStateAction() {} +func (*TerminalCommandFinishedAction) isStateAction() {} +func (*ResourceWatchChangedAction) isStateAction() {} // StateActionUnknown carries an unrecognized StateAction variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type StateActionUnknown struct { diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go index 01487d82..902e9ea2 100644 --- a/clients/go/ahptypes/commands.generated.go +++ b/clients/go/ahptypes/commands.generated.go @@ -19,7 +19,7 @@ var _ = json.RawMessage(nil) type ReconnectResultType string const ( - ReconnectResultTypeReplay ReconnectResultType = "replay" + ReconnectResultTypeReplay ReconnectResultType = "replay" ReconnectResultTypeSnapshot ReconnectResultType = "snapshot" ) @@ -28,7 +28,7 @@ type ContentEncoding string const ( ContentEncodingBase64 ContentEncoding = "base64" - ContentEncodingUtf8 ContentEncoding = "utf-8" + ContentEncodingUtf8 ContentEncoding = "utf-8" ) // The kind of completion items being requested. @@ -45,36 +45,36 @@ const ( type ResourceType string const ( - ResourceTypeFile ResourceType = "file" + ResourceTypeFile ResourceType = "file" ResourceTypeDirectory ResourceType = "directory" - ResourceTypeSymlink ResourceType = "symlink" + ResourceTypeSymlink ResourceType = "symlink" ) // How {@link ResourceWriteParams.data} is placed within the target file. // // Each mode interprets {@link ResourceWriteParams.position} differently: // -// - `truncate` (default): rooted at the **start** of the file. The file is -// truncated at `position` (0 by default) and `data` is written from that -// offset, so the resulting file is `existing[0..position] + data`. With -// `position` omitted this is a full overwrite. -// - `append`: rooted at the **end** of the file. `position` counts bytes -// backwards from EOF, so `position: 0` (the default) writes at EOF — -// POSIX append — and `position: 5` inserts `data` 5 bytes before the -// current EOF, shifting those trailing 5 bytes after the inserted region. -// The server MUST evaluate the effective EOF and write atomically with -// respect to other appenders so concurrent `append` writes do not -// clobber each other. -// - `insert`: rooted at the **start** of the file. `position` (0 by default) -// is the byte offset at which `data` is spliced in; bytes at or after -// `position` are shifted right by `data.length`. `insert` always grows -// the file — use `truncate` to overwrite bytes in place. +// - `truncate` (default): rooted at the **start** of the file. The file is +// truncated at `position` (0 by default) and `data` is written from that +// offset, so the resulting file is `existing[0..position] + data`. With +// `position` omitted this is a full overwrite. +// - `append`: rooted at the **end** of the file. `position` counts bytes +// backwards from EOF, so `position: 0` (the default) writes at EOF — +// POSIX append — and `position: 5` inserts `data` 5 bytes before the +// current EOF, shifting those trailing 5 bytes after the inserted region. +// The server MUST evaluate the effective EOF and write atomically with +// respect to other appenders so concurrent `append` writes do not +// clobber each other. +// - `insert`: rooted at the **start** of the file. `position` (0 by default) +// is the byte offset at which `data` is spliced in; bytes at or after +// `position` are shifted right by `data.length`. `insert` always grows +// the file — use `truncate` to overwrite bytes in place. type ResourceWriteMode string const ( ResourceWriteModeTruncate ResourceWriteMode = "truncate" - ResourceWriteModeAppend ResourceWriteMode = "append" - ResourceWriteModeInsert ResourceWriteMode = "insert" + ResourceWriteModeAppend ResourceWriteMode = "append" + ResourceWriteModeInsert ResourceWriteMode = "insert" ) // ─── Command Payloads ───────────────────────────────────────────────── @@ -801,9 +801,9 @@ type CompletionsParams struct { // A single completion item returned by the `completions` command. // // When the user accepts an item, the client SHOULD: -// 1. Replace the range `[rangeStart, rangeEnd)` in the input with `insertText` -// (or insert `insertText` at the cursor when the range is omitted). -// 2. Associate the item's `attachment` with the resulting {@link Message}. +// 1. Replace the range `[rangeStart, rangeEnd)` in the input with `insertText` +// (or insert `insertText` at the cursor when the range is omitted). +// 2. Associate the item's `attachment` with the resulting {@link Message}. type CompletionItem struct { // The text inserted into the input when this item is accepted. InsertText string `json:"insertText"` @@ -1008,7 +1008,7 @@ type ReconnectResult struct { // concrete variant of ReconnectResult. type isReconnectResult interface{ isReconnectResult() } -func (*ReconnectReplayResult) isReconnectResult() {} +func (*ReconnectReplayResult) isReconnectResult() {} func (*ReconnectSnapshotResult) isReconnectResult() {} // UnmarshalJSON decodes the variant indicated by the "type" discriminator. @@ -1066,10 +1066,10 @@ func (*ChangesetOperationResourceTarget) isChangesetOperationTarget() {} // ChangesetOperationRangeTarget targets a range within a resource. type ChangesetOperationRangeTarget struct { - Kind string `json:"kind"` - Resource URI `json:"resource"` - Side *string `json:"side,omitempty"` - Range ChangesetOperationTargetRange `json:"range"` + Kind string `json:"kind"` + Resource URI `json:"resource"` + Side *string `json:"side,omitempty"` + Range ChangesetOperationTargetRange `json:"range"` } func (*ChangesetOperationRangeTarget) isChangesetOperationTarget() {} diff --git a/clients/go/ahptypes/errors.generated.go b/clients/go/ahptypes/errors.generated.go index b5a22f4b..3b3e8f82 100644 --- a/clients/go/ahptypes/errors.generated.go +++ b/clients/go/ahptypes/errors.generated.go @@ -26,16 +26,16 @@ const ( // AHP application-specific error codes (above the JSON-RPC reserved // range). const ( - ErrorCodeSessionNotFound int32 = -32001 - ErrorCodeProviderNotFound int32 = -32002 - ErrorCodeSessionAlreadyExists int32 = -32003 - ErrorCodeTurnInProgress int32 = -32004 - ErrorCodeUnsupportedProtocolVersion int32 = -32005 - ErrorCodeContentNotFound int32 = -32006 - ErrorCodeAuthRequired int32 = -32007 - ErrorCodeNotFound int32 = -32008 - ErrorCodePermissionDenied int32 = -32009 - ErrorCodeAlreadyExists int32 = -32010 + ErrorCodeSessionNotFound int32 = -32001 + ErrorCodeProviderNotFound int32 = -32002 + ErrorCodeSessionAlreadyExists int32 = -32003 + ErrorCodeTurnInProgress int32 = -32004 + ErrorCodeUnsupportedProtocolVersion int32 = -32005 + ErrorCodeContentNotFound int32 = -32006 + ErrorCodeAuthRequired int32 = -32007 + ErrorCodeNotFound int32 = -32008 + ErrorCodePermissionDenied int32 = -32009 + ErrorCodeAlreadyExists int32 = -32010 ) // AhpErrorCode is the type alias used by AHP application error codes. diff --git a/clients/go/ahptypes/notifications.generated.go b/clients/go/ahptypes/notifications.generated.go index 6384f763..f74c8b2f 100644 --- a/clients/go/ahptypes/notifications.generated.go +++ b/clients/go/ahptypes/notifications.generated.go @@ -59,20 +59,20 @@ type SessionRemovedParams struct { // // Semantics: // -// - Only fields present in `changes` have new values; omitted fields are -// unchanged on the client's cached summary. -// - Identity fields (`resource`, `provider`, `createdAt`) never change and -// are not carried. -// - Like all protocol notifications, this is ephemeral: it is **not** -// replayed on reconnect. On reconnect, clients should re-fetch the full -// catalog via `listSessions()` as usual. -// - The server SHOULD emit this notification whenever any mutable field on -// {@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) at their discretion. -// - Clients that have no cached entry for `session` MAY ignore the -// notification; it is not a substitute for `root/sessionAdded`. +// - Only fields present in `changes` have new values; omitted fields are +// unchanged on the client's cached summary. +// - Identity fields (`resource`, `provider`, `createdAt`) never change and +// are not carried. +// - Like all protocol notifications, this is ephemeral: it is **not** +// replayed on reconnect. On reconnect, clients should re-fetch the full +// catalog via `listSessions()` as usual. +// - The server SHOULD emit this notification whenever any mutable field on +// {@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) 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 { // Channel URI this notification belongs to (the root channel) Channel URI `json:"channel"` diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index b7ce33a8..eeec0edf 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -19,8 +19,8 @@ var _ = json.RawMessage(nil) type PolicyState string const ( - PolicyStateEnabled PolicyState = "enabled" - PolicyStateDisabled PolicyState = "disabled" + PolicyStateEnabled PolicyState = "enabled" + PolicyStateDisabled PolicyState = "disabled" PolicyStateUnconfigured PolicyState = "unconfigured" ) @@ -38,8 +38,8 @@ const ( type SessionLifecycle string const ( - SessionLifecycleCreating SessionLifecycle = "creating" - SessionLifecycleReady SessionLifecycle = "ready" + SessionLifecycleCreating SessionLifecycle = "creating" + SessionLifecycleReady SessionLifecycle = "ready" SessionLifecycleCreationFailed SessionLifecycle = "creationFailed" ) @@ -75,19 +75,19 @@ func (s SessionStatus) Or(other SessionStatus) SessionStatus { return s | other type SessionInputAnswerState string const ( - SessionInputAnswerStateDraft SessionInputAnswerState = "draft" + SessionInputAnswerStateDraft SessionInputAnswerState = "draft" SessionInputAnswerStateSubmitted SessionInputAnswerState = "submitted" - SessionInputAnswerStateSkipped SessionInputAnswerState = "skipped" + SessionInputAnswerStateSkipped SessionInputAnswerState = "skipped" ) // Answer value kind. type SessionInputAnswerValueKind string const ( - SessionInputAnswerValueKindText SessionInputAnswerValueKind = "text" - SessionInputAnswerValueKindNumber SessionInputAnswerValueKind = "number" - SessionInputAnswerValueKindBoolean SessionInputAnswerValueKind = "boolean" - SessionInputAnswerValueKindSelected SessionInputAnswerValueKind = "selected" + SessionInputAnswerValueKindText SessionInputAnswerValueKind = "text" + SessionInputAnswerValueKindNumber SessionInputAnswerValueKind = "number" + SessionInputAnswerValueKindBoolean SessionInputAnswerValueKind = "boolean" + SessionInputAnswerValueKindSelected SessionInputAnswerValueKind = "selected" SessionInputAnswerValueKindSelectedMany SessionInputAnswerValueKind = "selected-many" ) @@ -95,30 +95,30 @@ const ( type SessionInputQuestionKind string const ( - SessionInputQuestionKindText SessionInputQuestionKind = "text" - SessionInputQuestionKindNumber SessionInputQuestionKind = "number" - SessionInputQuestionKindInteger SessionInputQuestionKind = "integer" - SessionInputQuestionKindBoolean SessionInputQuestionKind = "boolean" + SessionInputQuestionKindText SessionInputQuestionKind = "text" + SessionInputQuestionKindNumber SessionInputQuestionKind = "number" + SessionInputQuestionKindInteger SessionInputQuestionKind = "integer" + SessionInputQuestionKindBoolean SessionInputQuestionKind = "boolean" SessionInputQuestionKindSingleSelect SessionInputQuestionKind = "single-select" - SessionInputQuestionKindMultiSelect SessionInputQuestionKind = "multi-select" + SessionInputQuestionKindMultiSelect SessionInputQuestionKind = "multi-select" ) // How a client completed an input request. type SessionInputResponseKind string const ( - SessionInputResponseKindAccept SessionInputResponseKind = "accept" + SessionInputResponseKindAccept SessionInputResponseKind = "accept" SessionInputResponseKindDecline SessionInputResponseKind = "decline" - SessionInputResponseKindCancel SessionInputResponseKind = "cancel" + SessionInputResponseKindCancel SessionInputResponseKind = "cancel" ) // How a turn ended. type TurnState string const ( - TurnStateComplete TurnState = "complete" + TurnStateComplete TurnState = "complete" TurnStateCancelled TurnState = "cancelled" - TurnStateError TurnState = "error" + TurnStateError TurnState = "error" ) // Discriminant for {@link MessageAttachment} variants. @@ -139,10 +139,10 @@ const ( type ResponsePartKind string const ( - ResponsePartKindMarkdown ResponsePartKind = "markdown" - ResponsePartKindContentRef ResponsePartKind = "contentRef" - ResponsePartKindToolCall ResponsePartKind = "toolCall" - ResponsePartKindReasoning ResponsePartKind = "reasoning" + ResponsePartKindMarkdown ResponsePartKind = "markdown" + ResponsePartKindContentRef ResponsePartKind = "contentRef" + ResponsePartKindToolCall ResponsePartKind = "toolCall" + ResponsePartKindReasoning ResponsePartKind = "reasoning" ResponsePartKindSystemNotification ResponsePartKind = "systemNotification" ) @@ -150,12 +150,12 @@ const ( type ToolCallStatus string const ( - ToolCallStatusStreaming ToolCallStatus = "streaming" - ToolCallStatusPendingConfirmation ToolCallStatus = "pending-confirmation" - ToolCallStatusRunning ToolCallStatus = "running" + ToolCallStatusStreaming ToolCallStatus = "streaming" + ToolCallStatusPendingConfirmation ToolCallStatus = "pending-confirmation" + ToolCallStatusRunning ToolCallStatus = "running" ToolCallStatusPendingResultConfirmation ToolCallStatus = "pending-result-confirmation" - ToolCallStatusCompleted ToolCallStatus = "completed" - ToolCallStatusCancelled ToolCallStatus = "cancelled" + ToolCallStatusCompleted ToolCallStatus = "completed" + ToolCallStatusCancelled ToolCallStatus = "cancelled" ) // How a tool call was confirmed for execution. @@ -166,17 +166,17 @@ const ( type ToolCallConfirmationReason string const ( - ToolCallConfirmationReasonNotNeeded ToolCallConfirmationReason = "not-needed" + ToolCallConfirmationReasonNotNeeded ToolCallConfirmationReason = "not-needed" ToolCallConfirmationReasonUserAction ToolCallConfirmationReason = "user-action" - ToolCallConfirmationReasonSetting ToolCallConfirmationReason = "setting" + ToolCallConfirmationReasonSetting ToolCallConfirmationReason = "setting" ) // Why a tool call was cancelled. type ToolCallCancellationReason string const ( - ToolCallCancellationReasonDenied ToolCallCancellationReason = "denied" - ToolCallCancellationReasonSkipped ToolCallCancellationReason = "skipped" + ToolCallCancellationReasonDenied ToolCallCancellationReason = "denied" + ToolCallCancellationReasonSkipped ToolCallCancellationReason = "skipped" ToolCallCancellationReasonResultDenied ToolCallCancellationReason = "result-denied" ) @@ -185,26 +185,26 @@ type ConfirmationOptionKind string const ( ConfirmationOptionKindApprove ConfirmationOptionKind = "approve" - ConfirmationOptionKindDeny ConfirmationOptionKind = "deny" + ConfirmationOptionKindDeny ConfirmationOptionKind = "deny" ) type ToolCallContributorKind string const ( ToolCallContributorKindClient ToolCallContributorKind = "client" - ToolCallContributorKindMCP ToolCallContributorKind = "mcp" + ToolCallContributorKindMCP ToolCallContributorKind = "mcp" ) // Discriminant for tool result content types. type ToolResultContentType string const ( - ToolResultContentTypeText ToolResultContentType = "text" + ToolResultContentTypeText ToolResultContentType = "text" ToolResultContentTypeEmbeddedResource ToolResultContentType = "embeddedResource" - ToolResultContentTypeResource ToolResultContentType = "resource" - ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" - ToolResultContentTypeTerminal ToolResultContentType = "terminal" - ToolResultContentTypeSubagent ToolResultContentType = "subagent" + ToolResultContentTypeResource ToolResultContentType = "resource" + ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" + ToolResultContentTypeTerminal ToolResultContentType = "terminal" + ToolResultContentTypeSubagent ToolResultContentType = "subagent" ) // Discriminant for the kind of customization. @@ -219,13 +219,13 @@ const ( type CustomizationType string const ( - CustomizationTypePlugin CustomizationType = "plugin" + CustomizationTypePlugin CustomizationType = "plugin" CustomizationTypeDirectory CustomizationType = "directory" - CustomizationTypeAgent CustomizationType = "agent" - CustomizationTypeSkill CustomizationType = "skill" - CustomizationTypePrompt CustomizationType = "prompt" - CustomizationTypeRule CustomizationType = "rule" - CustomizationTypeHook CustomizationType = "hook" + CustomizationTypeAgent CustomizationType = "agent" + CustomizationTypeSkill CustomizationType = "skill" + CustomizationTypePrompt CustomizationType = "prompt" + CustomizationTypeRule CustomizationType = "rule" + CustomizationTypeHook CustomizationType = "hook" CustomizationTypeMcpServer CustomizationType = "mcpServer" ) @@ -233,17 +233,17 @@ const ( type CustomizationLoadStatus string const ( - CustomizationLoadStatusLoading CustomizationLoadStatus = "loading" - CustomizationLoadStatusLoaded CustomizationLoadStatus = "loaded" + CustomizationLoadStatusLoading CustomizationLoadStatus = "loading" + CustomizationLoadStatusLoaded CustomizationLoadStatus = "loaded" CustomizationLoadStatusDegraded CustomizationLoadStatus = "degraded" - CustomizationLoadStatusError CustomizationLoadStatus = "error" + CustomizationLoadStatusError CustomizationLoadStatus = "error" ) // Discriminant for terminal claim kinds. type TerminalClaimKind string const ( - TerminalClaimKindClient TerminalClaimKind = "client" + TerminalClaimKindClient TerminalClaimKind = "client" TerminalClaimKindSession TerminalClaimKind = "session" ) @@ -344,7 +344,7 @@ const ( type ResourceChangeType string const ( - ResourceChangeTypeAdded ResourceChangeType = "added" + ResourceChangeTypeAdded ResourceChangeType = "added" ResourceChangeTypeUpdated ResourceChangeType = "updated" ResourceChangeTypeDeleted ResourceChangeType = "deleted" ) @@ -848,30 +848,30 @@ type SessionInputOption struct { // Value captured for one answer. type SessionInputTextAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` } type SessionInputNumberAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value float64 `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value float64 `json:"value"` } type SessionInputBooleanAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value bool `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value bool `json:"value"` } type SessionInputSelectedAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` // Free-form text entered instead of selecting an option FreeformValues []string `json:"freeformValues,omitempty"` } type SessionInputSelectedManyAnswerValue struct { - Kind SessionInputAnswerValueKind `json:"kind"` - Value []string `json:"value"` + Kind SessionInputAnswerValueKind `json:"kind"` + Value []string `json:"value"` // Free-form text entered in addition to selected options FreeformValues []string `json:"freeformValues,omitempty"` } @@ -899,8 +899,8 @@ type SessionInputTextQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Format hint for text questions, such as `email`, `uri`, `date`, or `date-time` Format *string `json:"format,omitempty"` // Minimum string length @@ -920,8 +920,8 @@ type SessionInputNumberQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Minimum value Min *float64 `json:"min,omitempty"` // Maximum value @@ -939,8 +939,8 @@ type SessionInputBooleanQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Default boolean value DefaultValue *bool `json:"defaultValue,omitempty"` } @@ -954,8 +954,8 @@ type SessionInputSingleSelectQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Options the user may select from Options []SessionInputOption `json:"options"` // Whether the user may enter text instead of selecting an option @@ -971,8 +971,8 @@ type SessionInputMultiSelectQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind SessionInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Options the user may select from Options []SessionInputOption `json:"options"` // Whether the user may enter text in addition to selecting options @@ -1305,8 +1305,8 @@ type ToolCallStreamingState struct { // This MAY include a `ui` field corresponding to the MCP Apps (SEP-1865) // `McpUiToolMeta` found in MCP tool calls, which may be used in combination // with the {@link contributor} to serve MCP Apps. - Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Status ToolCallStatus `json:"status"` + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Status ToolCallStatus `json:"status"` // Partial parameters accumulated so far PartialInput *string `json:"partialInput,omitempty"` // Progress message shown while parameters are streaming @@ -1333,8 +1333,8 @@ type ToolCallPendingConfirmationState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // Short title for the confirmation prompt (e.g. `"Run in terminal"`, `"Write file"`) ConfirmationTitle *StringOrMarkdown `json:"confirmationTitle,omitempty"` // File edits that this tool call will perform, for preview before confirmation @@ -1367,8 +1367,8 @@ type ToolCallRunningState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1413,8 +1413,8 @@ type ToolCallPendingResultConfirmationState struct { // This mirrors the `structuredContent` field of MCP `CallToolResult`. StructuredContent map[string]json.RawMessage `json:"structuredContent,omitempty"` // Error details if the tool failed - Error *json.RawMessage `json:"error,omitempty"` - Status ToolCallStatus `json:"status"` + Error *json.RawMessage `json:"error,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1454,8 +1454,8 @@ type ToolCallCompletedState struct { // This mirrors the `structuredContent` field of MCP `CallToolResult`. StructuredContent map[string]json.RawMessage `json:"structuredContent,omitempty"` // Error details if the tool failed - Error *json.RawMessage `json:"error,omitempty"` - Status ToolCallStatus `json:"status"` + Error *json.RawMessage `json:"error,omitempty"` + Status ToolCallStatus `json:"status"` // How the tool was confirmed for execution Confirmed ToolCallConfirmationReason `json:"confirmed"` // The confirmation option the user selected, if confirmation options were provided @@ -1481,8 +1481,8 @@ type ToolCallCancelledState struct { // Message describing what the tool will do InvocationMessage StringOrMarkdown `json:"invocationMessage"` // Raw tool input - ToolInput *string `json:"toolInput,omitempty"` - Status ToolCallStatus `json:"status"` + ToolInput *string `json:"toolInput,omitempty"` + Status ToolCallStatus `json:"status"` // Why the tool was cancelled Reason ToolCallCancellationReason `json:"reason"` // Optional message explaining the cancellation @@ -1564,8 +1564,8 @@ type ToolResultResourceContent struct { // Approximate size in bytes SizeHint *int64 `json:"sizeHint,omitempty"` // Content MIME type - ContentType *string `json:"contentType,omitempty"` - Type ToolResultContentType `json:"type"` + ContentType *string `json:"contentType,omitempty"` + Type ToolResultContentType `json:"type"` } // Describes a file modification performed by a tool. @@ -1575,7 +1575,7 @@ type ToolResultFileEditContent struct { // The file state after the edit. Absent for file deletions. After *json.RawMessage `json:"after,omitempty"` // Optional diff display metadata - Diff *json.RawMessage `json:"diff,omitempty"` + Diff *json.RawMessage `json:"diff,omitempty"` Type ToolResultContentType `json:"type"` } @@ -1668,7 +1668,7 @@ type PluginCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` } // A {@link PluginCustomization} as published by a client. Extends the @@ -1716,7 +1716,7 @@ type ClientPluginCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` // Opaque version token used by the host to detect changes. Nonce *string `json:"nonce,omitempty"` } @@ -1766,7 +1766,7 @@ type DirectoryCustomization struct { // array means the host parsed the container and it contributes // nothing. Children []ChildCustomization `json:"children,omitempty"` - Type CustomizationType `json:"type"` + Type CustomizationType `json:"type"` // Which child customization type this directory holds. Contents CustomizationType `json:"contents"` // Whether clients may write into this directory. @@ -1799,8 +1799,8 @@ type AgentCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description of what the agent specializes in and when to // invoke it. Sourced from the agent file's frontmatter `description`. Description *string `json:"description,omitempty"` @@ -1837,8 +1837,8 @@ type SkillCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description used for help text and auto-invocation matching. // Sourced from the skill's frontmatter `description`. Description *string `json:"description,omitempty"` @@ -1870,8 +1870,8 @@ type PromptCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Short description of what the prompt does. Description *string `json:"description,omitempty"` } @@ -1906,8 +1906,8 @@ type RuleCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Description of what the rule enforces. Description *string `json:"description,omitempty"` // When `true`, the rule is always active (subject to `globs` if any). @@ -1941,8 +1941,8 @@ type HookCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` } // An MCP server contributed by a plugin or directory. @@ -1974,8 +1974,8 @@ type McpServerCustomization struct { // customization is a subset of a larger file (for example, one entry // in an inline `mcpServers` block of a `plugins.json` manifest). // Absent when the customization covers the whole resource. - Range *TextRange `json:"range,omitempty"` - Type CustomizationType `json:"type"` + Range *TextRange `json:"range,omitempty"` + Type CustomizationType `json:"type"` // Whether this MCP server is currently enabled. Enabled bool `json:"enabled"` // Current lifecycle state of the MCP server. @@ -2032,20 +2032,20 @@ type McpServerCustomizationApps struct { // An agent host MUST only advertise a capability when it actually accepts the // corresponding methods/notifications on the `mcp://` channel: // -// - {@link serverTools}: host proxies `tools/list` and `tools/call` to -// the MCP server. When `listChanged` is `true`, the host also forwards -// `notifications/tools/list_changed`. -// - {@link serverResources}: host proxies `resources/read`, -// `resources/list`, and `resources/templates/list` to the MCP server. -// When `listChanged` is `true`, the host also forwards -// `notifications/resources/list_changed`. -// - {@link logging}: host accepts `notifications/message` log entries -// from the App and forwards them via `mcpNotification` (and forwards -// `logging/setLevel` calls to the server). -// - {@link sampling}: host serves `sampling/createMessage` via -// `mcpMethodCall`. When `sampling.tools` is present, the host also -// accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks -// inside `CreateMessageRequest`. +// - {@link serverTools}: host proxies `tools/list` and `tools/call` to +// the MCP server. When `listChanged` is `true`, the host also forwards +// `notifications/tools/list_changed`. +// - {@link serverResources}: host proxies `resources/read`, +// `resources/list`, and `resources/templates/list` to the MCP server. +// When `listChanged` is `true`, the host also forwards +// `notifications/resources/list_changed`. +// - {@link logging}: host accepts `notifications/message` log entries +// from the App and forwards them via `mcpNotification` (and forwards +// `logging/setLevel` calls to the server). +// - {@link sampling}: host serves `sampling/createMessage` via +// `mcpMethodCall`. When `sampling.tools` is present, the host also +// accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks +// inside `CreateMessageRequest`. type AhpMcpUiHostCapabilities struct { // Producer proxies the MCP `tools/*` methods to the upstream server. ServerTools *json.RawMessage `json:"serverTools,omitempty"` @@ -2574,10 +2574,10 @@ type ResponsePart struct { // concrete variant of ResponsePart. type isResponsePart interface{ isResponsePart() } -func (*MarkdownResponsePart) isResponsePart() {} -func (*ResourceResponsePart) isResponsePart() {} -func (*ToolCallResponsePart) isResponsePart() {} -func (*ReasoningResponsePart) isResponsePart() {} +func (*MarkdownResponsePart) isResponsePart() {} +func (*ResourceResponsePart) isResponsePart() {} +func (*ToolCallResponsePart) isResponsePart() {} +func (*ReasoningResponsePart) isResponsePart() {} func (*SystemNotificationResponsePart) isResponsePart() {} // ResponsePartUnknown carries an unrecognized ResponsePart variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2655,12 +2655,12 @@ type ToolCallState struct { // concrete variant of ToolCallState. type isToolCallState interface{ isToolCallState() } -func (*ToolCallStreamingState) isToolCallState() {} -func (*ToolCallPendingConfirmationState) isToolCallState() {} -func (*ToolCallRunningState) isToolCallState() {} +func (*ToolCallStreamingState) isToolCallState() {} +func (*ToolCallPendingConfirmationState) isToolCallState() {} +func (*ToolCallRunningState) isToolCallState() {} func (*ToolCallPendingResultConfirmationState) isToolCallState() {} -func (*ToolCallCompletedState) isToolCallState() {} -func (*ToolCallCancelledState) isToolCallState() {} +func (*ToolCallCompletedState) isToolCallState() {} +func (*ToolCallCancelledState) isToolCallState() {} // ToolCallStateUnknown carries an unrecognized ToolCallState variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type ToolCallStateUnknown struct { @@ -2743,7 +2743,7 @@ type TerminalClaim struct { // concrete variant of TerminalClaim. type isTerminalClaim interface{ isTerminalClaim() } -func (*TerminalClientClaim) isTerminalClaim() {} +func (*TerminalClientClaim) isTerminalClaim() {} func (*TerminalSessionClaim) isTerminalClaim() {} // TerminalClaimUnknown carries an unrecognized TerminalClaim variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -2804,7 +2804,7 @@ type TerminalContentPart struct { type isTerminalContentPart interface{ isTerminalContentPart() } func (*TerminalUnclassifiedPart) isTerminalContentPart() {} -func (*TerminalCommandPart) isTerminalContentPart() {} +func (*TerminalCommandPart) isTerminalContentPart() {} // TerminalContentPartUnknown carries an unrecognized TerminalContentPart variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type TerminalContentPartUnknown struct { @@ -2863,11 +2863,11 @@ type SessionInputQuestion struct { // concrete variant of SessionInputQuestion. type isSessionInputQuestion interface{ isSessionInputQuestion() } -func (*SessionInputTextQuestion) isSessionInputQuestion() {} -func (*SessionInputNumberQuestion) isSessionInputQuestion() {} -func (*SessionInputBooleanQuestion) isSessionInputQuestion() {} +func (*SessionInputTextQuestion) isSessionInputQuestion() {} +func (*SessionInputNumberQuestion) isSessionInputQuestion() {} +func (*SessionInputBooleanQuestion) isSessionInputQuestion() {} func (*SessionInputSingleSelectQuestion) isSessionInputQuestion() {} -func (*SessionInputMultiSelectQuestion) isSessionInputQuestion() {} +func (*SessionInputMultiSelectQuestion) isSessionInputQuestion() {} // SessionInputQuestionUnknown carries an unrecognized SessionInputQuestion variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type SessionInputQuestionUnknown struct { @@ -2950,10 +2950,10 @@ type SessionInputAnswerValue struct { // concrete variant of SessionInputAnswerValue. type isSessionInputAnswerValue interface{ isSessionInputAnswerValue() } -func (*SessionInputTextAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputNumberAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputBooleanAnswerValue) isSessionInputAnswerValue() {} -func (*SessionInputSelectedAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputTextAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputNumberAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputBooleanAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputSelectedAnswerValue) isSessionInputAnswerValue() {} func (*SessionInputSelectedManyAnswerValue) isSessionInputAnswerValue() {} // SessionInputAnswerValueUnknown carries an unrecognized SessionInputAnswerValue variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -3032,7 +3032,7 @@ type SessionInputAnswer struct { type isSessionInputAnswer interface{ isSessionInputAnswer() } func (*SessionInputAnswered) isSessionInputAnswer() {} -func (*SessionInputSkipped) isSessionInputAnswer() {} +func (*SessionInputSkipped) isSessionInputAnswer() {} // SessionInputAnswerUnknown carries an unrecognized SessionInputAnswer variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type SessionInputAnswerUnknown struct { @@ -3097,12 +3097,12 @@ type ToolResultContent struct { // concrete variant of ToolResultContent. type isToolResultContent interface{ isToolResultContent() } -func (*ToolResultTextContent) isToolResultContent() {} +func (*ToolResultTextContent) isToolResultContent() {} func (*ToolResultEmbeddedResourceContent) isToolResultContent() {} -func (*ToolResultResourceContent) isToolResultContent() {} -func (*ToolResultFileEditContent) isToolResultContent() {} -func (*ToolResultTerminalContent) isToolResultContent() {} -func (*ToolResultSubagentContent) isToolResultContent() {} +func (*ToolResultResourceContent) isToolResultContent() {} +func (*ToolResultFileEditContent) isToolResultContent() {} +func (*ToolResultTerminalContent) isToolResultContent() {} +func (*ToolResultSubagentContent) isToolResultContent() {} // ToolResultContentUnknown carries an unrecognized ToolResultContent variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type ToolResultContentUnknown struct { @@ -3185,10 +3185,10 @@ type MessageAttachment struct { // concrete variant of MessageAttachment. type isMessageAttachment interface{ isMessageAttachment() } -func (*SimpleMessageAttachment) isMessageAttachment() {} +func (*SimpleMessageAttachment) isMessageAttachment() {} func (*MessageEmbeddedResourceAttachment) isMessageAttachment() {} -func (*MessageResourceAttachment) isMessageAttachment() {} -func (*MessageAnnotationsAttachment) isMessageAttachment() {} +func (*MessageResourceAttachment) isMessageAttachment() {} +func (*MessageAnnotationsAttachment) isMessageAttachment() {} // MessageAttachmentUnknown carries an unrecognized MessageAttachment variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type MessageAttachmentUnknown struct { @@ -3259,7 +3259,7 @@ type Customization struct { // concrete variant of Customization. type isCustomization interface{ isCustomization() } -func (*PluginCustomization) isCustomization() {} +func (*PluginCustomization) isCustomization() {} func (*DirectoryCustomization) isCustomization() {} func (*McpServerCustomization) isCustomization() {} @@ -3326,11 +3326,11 @@ type ChildCustomization struct { // concrete variant of ChildCustomization. type isChildCustomization interface{ isChildCustomization() } -func (*AgentCustomization) isChildCustomization() {} -func (*SkillCustomization) isChildCustomization() {} -func (*PromptCustomization) isChildCustomization() {} -func (*RuleCustomization) isChildCustomization() {} -func (*HookCustomization) isChildCustomization() {} +func (*AgentCustomization) isChildCustomization() {} +func (*SkillCustomization) isChildCustomization() {} +func (*PromptCustomization) isChildCustomization() {} +func (*RuleCustomization) isChildCustomization() {} +func (*HookCustomization) isChildCustomization() {} func (*McpServerCustomization) isChildCustomization() {} // ChildCustomizationUnknown carries an unrecognized ChildCustomization variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -3414,10 +3414,10 @@ type CustomizationLoadState struct { // concrete variant of CustomizationLoadState. type isCustomizationLoadState interface{ isCustomizationLoadState() } -func (*CustomizationLoadingState) isCustomizationLoadState() {} -func (*CustomizationLoadedState) isCustomizationLoadState() {} +func (*CustomizationLoadingState) isCustomizationLoadState() {} +func (*CustomizationLoadedState) isCustomizationLoadState() {} func (*CustomizationDegradedState) isCustomizationLoadState() {} -func (*CustomizationErrorState) isCustomizationLoadState() {} +func (*CustomizationErrorState) isCustomizationLoadState() {} // CustomizationLoadStateUnknown carries an unrecognized CustomizationLoadState variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type CustomizationLoadStateUnknown struct { @@ -3488,11 +3488,11 @@ type McpServerState struct { // concrete variant of McpServerState. type isMcpServerState interface{ isMcpServerState() } -func (*McpServerStartingState) isMcpServerState() {} -func (*McpServerReadyState) isMcpServerState() {} +func (*McpServerStartingState) isMcpServerState() {} +func (*McpServerReadyState) isMcpServerState() {} func (*McpServerAuthRequiredState) isMcpServerState() {} -func (*McpServerErrorState) isMcpServerState() {} -func (*McpServerStoppedState) isMcpServerState() {} +func (*McpServerErrorState) isMcpServerState() {} +func (*McpServerStoppedState) isMcpServerState() {} // McpServerStateUnknown carries an unrecognized McpServerState variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type McpServerStateUnknown struct { @@ -3570,7 +3570,7 @@ type ToolCallContributor struct { type isToolCallContributor interface{ isToolCallContributor() } func (*ToolCallClientContributor) isToolCallContributor() {} -func (*ToolCallMcpContributor) isToolCallContributor() {} +func (*ToolCallMcpContributor) isToolCallContributor() {} // ToolCallContributorUnknown carries an unrecognized ToolCallContributor variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. type ToolCallContributorUnknown struct { @@ -3625,11 +3625,11 @@ func (u ToolCallContributor) MarshalJSON() ([]byte, error) { // pointer field is non-nil; UnmarshalJSON probes for required fields in // the canonical order (session → terminal → changeset → annotations → root). type SnapshotState struct { - Root *RootState `json:"-"` - Session *SessionState `json:"-"` - Terminal *TerminalState `json:"-"` - Changeset *ChangesetState `json:"-"` - Annotations *AnnotationsState `json:"-"` + Root *RootState `json:"-"` + Session *SessionState `json:"-"` + Terminal *TerminalState `json:"-"` + Changeset *ChangesetState `json:"-"` + Annotations *AnnotationsState `json:"-"` } // MarshalJSON encodes whichever variant is currently populated. 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 82e83108..d1742414 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 @@ -737,7 +737,7 @@ data class RootState( val config: RootConfigState? = null, /** * Additional implementation-defined metadata about the agent host itself. - * + * * Clients MAY look for well-known keys here to provide enhanced UI. */ @SerialName("_meta") diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 274f8e8a..117df9f3 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -453,7 +453,7 @@ public struct RootState: Codable, Sendable { /// Agent host configuration schema and current values public var config: RootConfigState? /// Additional implementation-defined metadata about the agent host itself. - /// + /// /// Clients MAY look for well-known keys here to provide enhanced UI. public var meta: [String: AnyCodable]? From 2a97fa5ad153da9ade3595fddd3a98ce6da56d94 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Mon, 8 Jun 2026 23:00:13 +0200 Subject: [PATCH 11/11] remove commands --- CHANGELOG.md | 17 +- clients/go/CHANGELOG.md | 11 +- clients/go/ahptypes/actions.generated.go | 37 +-- clients/go/ahptypes/commands.generated.go | 116 --------- clients/go/ahptypes/state.generated.go | 36 ++- clients/kotlin/CHANGELOG.md | 11 +- .../generated/Commands.generated.kt | 137 ----------- .../generated/Messages.generated.kt | 18 -- .../generated/State.generated.kt | 28 +-- clients/rust/CHANGELOG.md | 10 +- clients/rust/crates/ahp-types/src/actions.rs | 37 +-- clients/rust/crates/ahp-types/src/commands.rs | 143 +---------- clients/rust/crates/ahp-types/src/state.rs | 39 ++- .../Generated/Commands.generated.swift | 161 ------------- .../Generated/State.generated.swift | 35 +-- clients/swift/CHANGELOG.md | 12 +- clients/typescript/CHANGELOG.md | 13 +- docs/guide/actions.md | 13 + schema/actions.schema.json | 38 +-- schema/commands.schema.json | 227 +----------------- schema/errors.schema.json | 219 +---------------- schema/notifications.schema.json | 30 +-- schema/state.schema.json | 30 +-- scripts/generate-go.ts | 5 - scripts/generate-kotlin.ts | 26 +- scripts/generate-markdown.ts | 2 +- scripts/generate-rust.ts | 9 +- scripts/generate-swift.ts | 8 +- types/action-origin.generated.ts | 18 +- types/channels-annotations/actions.ts | 54 +++-- types/channels-annotations/commands.ts | 203 ---------------- types/channels-annotations/reducer.ts | 9 +- types/channels-annotations/state.ts | 48 ++-- types/commands.ts | 1 - types/common/messages.ts | 16 -- types/index.ts | 9 - types/version/message-checks.ts | 8 +- 37 files changed, 245 insertions(+), 1589 deletions(-) delete mode 100644 types/channels-annotations/commands.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 53f2a1c6..26f75c3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,15 +89,14 @@ Spec version: `0.3.0` - Added a new annotations channel exposed on `ahp-session://annotations`. Annotations anchor to a `(turnId, resource)` pair with an optional `range` (omitted to anchor to the entire file), carry a `resolved` flag (newly - created annotations start unresolved; clients flip it via - `updateAnnotation`), and always carry at least one entry; new - `createAnnotation`, `updateAnnotation`, - `deleteAnnotation`, `addAnnotationEntry`, `editAnnotationEntry`, `deleteAnnotationEntry` - commands drive mutations and echo as `annotations/set`, - `annotations/removed`, `annotations/entrySet`, and `annotations/entryRemoved` - actions. `SessionSummary.annotations` advertises the - per-session `AnnotationsSummary` (`{ resource, annotationCount, entryCount }`) - for badge UI. + created annotations start unresolved), and always carry at least one entry. + Clients drive every mutation by dispatching the client-dispatchable + `annotations/set`, `annotations/removed`, `annotations/entrySet`, and + `annotations/entryRemoved` state actions directly — assigning the + `Annotation.id` / `AnnotationEntry.id` themselves — rather than through RPC + commands, so annotations inherit write-ahead replay and conflict resolution. + `SessionSummary.annotations` advertises the per-session `AnnotationsSummary` + (`{ resource, annotationCount, entryCount }`) for badge UI. - Added an `annotations` `MessageAttachment` variant (`MessageAnnotationsAttachment`) that references annotations on a session's annotations channel by its `resource` URI, optionally narrowed to diff --git a/clients/go/CHANGELOG.md b/clients/go/CHANGELOG.md index c1124452..9f4d4f39 100644 --- a/clients/go/CHANGELOG.md +++ b/clients/go/CHANGELOG.md @@ -51,13 +51,12 @@ Implements AHP 0.3.0. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. - New annotations channel wire types (`ahp-session://annotations`): - `AnnotationsState`, `Annotation`, `AnnotationEntry`, `NewAnnotationEntry`, - `AnnotationsSummary`; the `AnnotationsSetAction`, + `AnnotationsState`, `Annotation`, `AnnotationEntry`, + `AnnotationsSummary`; the client-dispatchable `AnnotationsSetAction`, `AnnotationsRemovedAction`, `AnnotationsEntrySetAction`, - `AnnotationsEntryRemovedAction` variants; - `CreateAnnotationParams/Result`, `UpdateAnnotationParams`, - `DeleteAnnotationParams`, `AddAnnotationEntryParams/Result`, - `EditAnnotationEntryParams`, `DeleteAnnotationEntryParams` command structs; + `AnnotationsEntryRemovedAction` variants — clients drive every annotation + mutation by dispatching these directly, assigning the `Annotation.Id` / + `AnnotationEntry.Id` themselves; `ApplyActionToAnnotations` (stub mirroring `ApplyActionToChangeset`); and `SnapshotState.Annotations`. - `MessageAnnotationsAttachment` (`annotations` `MessageAttachment` variant) diff --git a/clients/go/ahptypes/actions.generated.go b/clients/go/ahptypes/actions.generated.go index 0e14d528..50949583 100644 --- a/clients/go/ahptypes/actions.generated.go +++ b/clients/go/ahptypes/actions.generated.go @@ -802,10 +802,15 @@ type ChangesetClearedAction struct { // Upsert an {@link Annotation} in the annotations channel — adds a new // annotation, or replaces an existing one identified by -// {@link Annotation.id}. When replacing, the full annotation payload -// (including its {@link Annotation.entries | entries} list) is -// substituted; producers SHOULD prefer {@link AnnotationsEntrySetAction} -// for per-entry edits to keep wire updates small. +// {@link Annotation.id}. +// +// Dispatched by a client to create an annotation (together with its +// mandatory first entry) or to re-anchor / resolve an existing one; the +// dispatching client assigns the {@link Annotation.id} and the id of any +// new entry. When replacing, the full annotation payload (including its +// {@link Annotation.entries | entries} list) is substituted; producers +// SHOULD prefer {@link AnnotationsEntrySetAction} for per-entry edits to +// keep wire updates small. type AnnotationsSetAction struct { Type ActionType `json:"type"` // The new or replacement annotation. MUST contain at least one entry. @@ -814,13 +819,10 @@ type AnnotationsSetAction struct { // Remove an {@link Annotation} from the channel by its id. // -// The server emits this in two cases: -// 1. The client explicitly invoked -// {@link DeleteAnnotationParams | `deleteAnnotation`}. -// 2. The client invoked {@link DeleteAnnotationEntryParams | -// `deleteAnnotationEntry`} on the last remaining entry in the -// annotation — the protocol collapses the annotation rather than -// leaving an empty one behind. +// Dispatched to delete an entire annotation and every entry it contains. +// Because the protocol forbids empty annotations, a client that wants to +// remove the last remaining entry dispatches this action — collapsing the +// annotation — rather than {@link AnnotationsEntryRemovedAction}. type AnnotationsRemovedAction struct { Type ActionType `json:"type"` // The {@link Annotation.id} of the annotation to remove. @@ -828,9 +830,10 @@ type AnnotationsRemovedAction struct { } // Upsert an {@link AnnotationEntry} within an existing annotation — adds a -// new entry, or replaces one identified by {@link AnnotationEntry.id}. If -// {@link annotationId} does not match any current annotation the action is -// a no-op. +// new entry, or replaces one identified by {@link AnnotationEntry.id}. The +// dispatching client assigns the {@link AnnotationEntry.id} of a new entry. +// If {@link annotationId} does not match any current annotation the action +// is a no-op. type AnnotationsEntrySetAction struct { Type ActionType `json:"type"` // The {@link Annotation.id} the entry belongs to. @@ -840,9 +843,9 @@ type AnnotationsEntrySetAction struct { } // Remove a single {@link AnnotationEntry} from an annotation without -// collapsing the annotation itself. Used when more than one entry remains -// — the server MUST dispatch {@link AnnotationsRemovedAction} instead when -// removing the last entry would otherwise leave the annotation empty. +// collapsing the annotation itself. Used when more than one entry remains — +// to remove the last entry a client dispatches {@link AnnotationsRemovedAction} +// instead, since the protocol forbids empty annotations. // // If either {@link annotationId} or {@link entryId} does not match the // current state the action is a no-op. diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go index 902e9ea2..a48150c5 100644 --- a/clients/go/ahptypes/commands.generated.go +++ b/clients/go/ahptypes/commands.generated.go @@ -881,122 +881,6 @@ type ChangesetOperationFollowUp struct { External *bool `json:"external,omitempty"` } -// Create a new {@link Annotation} anchored to a file from a specific -// turn, optionally narrowed to a range within that file. -// -// The initial entry is required — the protocol forbids empty annotations, -// so annotation creation and first-entry creation are fused into one -// command. The created annotation always starts unresolved -// ({@link Annotation.resolved} is `false`). The server assigns both -// {@link CreateAnnotationResult.annotationId} and -// {@link CreateAnnotationResult.entryId}, then broadcasts an -// {@link AnnotationsSetAction} on the channel. -type CreateAnnotationParams struct { - // Channel URI this command targets. - Channel URI `json:"channel"` - // Turn whose file versions {@link resource} + {@link range} address. - TurnId string `json:"turnId"` - // Anchored file URI. - Resource URI `json:"resource"` - // Anchored range within {@link resource}. When omitted the annotation is - // anchored to the entire file. - Range *TextRange `json:"range,omitempty"` - // First entry in the annotation. The server assigns its {@link AnnotationEntry.id}. - Entry NewAnnotationEntry `json:"entry"` -} - -// Result of {@link CreateAnnotationParams | `createAnnotation`}. -type CreateAnnotationResult struct { - // Server-assigned {@link Annotation.id}. - AnnotationId string `json:"annotationId"` - // Server-assigned {@link AnnotationEntry.id} of the initial entry. - EntryId string `json:"entryId"` -} - -// Re-anchor or resolve an existing {@link Annotation} — typically used -// to re-pin an annotation to a different range or a newer turn after an -// edit, or to mark the annotation {@link Annotation.resolved | resolved} -// (or re-open it). Entries themselves are not modified by this command; -// use {@link AddAnnotationEntryParams | `addAnnotationEntry`}, -// {@link EditAnnotationEntryParams | `editAnnotationEntry`}, or -// {@link DeleteAnnotationEntryParams | `deleteAnnotationEntry`} for that. -// -// Omitted optional fields preserve their current value. The server -// echoes the resulting annotation state as an {@link AnnotationsSetAction}. -type UpdateAnnotationParams struct { - // Channel URI this command targets. - Channel URI `json:"channel"` - // The {@link Annotation.id} to update. - AnnotationId string `json:"annotationId"` - // New {@link Annotation.turnId}, if changing. - TurnId *string `json:"turnId,omitempty"` - // New anchored file URI, if changing. - Resource *URI `json:"resource,omitempty"` - // New anchored range, if changing. - Range *TextRange `json:"range,omitempty"` - // New {@link Annotation.resolved} state, if changing. - Resolved *bool `json:"resolved,omitempty"` -} - -// Delete an entire annotation (and every entry it contains). The -// server echoes an {@link AnnotationsRemovedAction} on the channel. -type DeleteAnnotationParams struct { - // Channel URI this command targets. - Channel URI `json:"channel"` - // The {@link Annotation.id} to delete. - AnnotationId string `json:"annotationId"` -} - -// Append a new {@link AnnotationEntry} to an existing annotation. The -// server assigns the resulting {@link AnnotationEntry.id} and echoes an -// {@link AnnotationsEntrySetAction}. -type AddAnnotationEntryParams struct { - // Channel URI this command targets. - Channel URI `json:"channel"` - // Annotation that receives the new entry. - AnnotationId string `json:"annotationId"` - // Entry payload — the server assigns the id. - Entry NewAnnotationEntry `json:"entry"` -} - -// Result of {@link AddAnnotationEntryParams | `addAnnotationEntry`}. -type AddAnnotationEntryResult struct { - // Server-assigned {@link AnnotationEntry.id} of the new entry. - EntryId string `json:"entryId"` -} - -// Edit the body of an existing entry in place. The server echoes an -// {@link AnnotationsEntrySetAction} carrying the updated entry. -// -// Only the body is mutable through this command; to change -// {@link AnnotationEntry._meta} delete and re-create the entry. -type EditAnnotationEntryParams struct { - // Channel URI this command targets. - Channel URI `json:"channel"` - // Enclosing annotation. - AnnotationId string `json:"annotationId"` - // {@link AnnotationEntry.id} to edit. - EntryId string `json:"entryId"` - // New entry body. See {@link AnnotationEntry.text}. - Text StringOrMarkdown `json:"text"` -} - -// Remove a single entry from an annotation. -// -// If the removal would leave the annotation empty (i.e. the targeted entry -// is the only one remaining), the server collapses the annotation instead -// — it dispatches an {@link AnnotationsRemovedAction} and the annotation -// disappears from {@link AnnotationsState.annotations}. Otherwise the server -// echoes an {@link AnnotationsEntryRemovedAction}. -type DeleteAnnotationEntryParams struct { - // Channel URI this command targets. - Channel URI `json:"channel"` - // Enclosing annotation. - AnnotationId string `json:"annotationId"` - // {@link AnnotationEntry.id} to remove. - EntryId string `json:"entryId"` -} - // ─── ReconnectResult Union ──────────────────────────────────────────── // ReconnectResult is the result of the `reconnect` command. diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index eeec0edf..bad8d352 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -2430,14 +2430,14 @@ type AnnotationsState struct { // and {@link range} against the turn's changeset. When {@link range} is // omitted the annotation is anchored to the entire file. // -// Every annotation MUST contain at least one {@link AnnotationEntry}. The -// server enforces this invariant: {@link CreateAnnotationParams | -// `createAnnotation`} requires an initial entry, and deleting the -// last remaining entry collapses the annotation into a -// {@link AnnotationsRemovedAction} rather than leaving an empty annotation -// behind. +// Every annotation MUST contain at least one {@link AnnotationEntry}. An +// {@link AnnotationsSetAction} that creates an annotation therefore carries +// its mandatory first entry, and removing the last remaining entry collapses +// the annotation via {@link AnnotationsRemovedAction} rather than leaving an +// empty annotation behind. type Annotation struct { - // Stable identifier within the annotations channel. Server-assigned. + // Stable identifier within the annotations channel. Assigned by the client + // that dispatches the creating {@link AnnotationsSetAction}. Id string `json:"id"` // Turn that produced the file versions this annotation is anchored to. // Matches a {@link Turn.id} on the owning session. @@ -2449,40 +2449,32 @@ type Annotation struct { Range *TextRange `json:"range,omitempty"` // Whether the annotation has been resolved. Newly created annotations are // always unresolved (`false`); a client marks an annotation resolved (or - // re-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}. + // re-opens it) by dispatching an {@link AnnotationsSetAction} carrying the + // updated flag. Resolved bool `json:"resolved"` // Entries in this annotation, in dispatch order (oldest first). MUST // contain at least one entry. Entries []AnnotationEntry `json:"entries"` - // Server-defined opaque metadata, surfaced to tooling but not + // Producer-defined opaque metadata, surfaced to tooling but not // interpreted by the protocol. Meta map[string]json.RawMessage `json:"_meta,omitempty"` } // A single entry within an {@link Annotation}. type AnnotationEntry struct { - // Stable identifier within the enclosing annotation. Server-assigned. + // Stable identifier within the enclosing annotation. Assigned by the client + // that dispatches the {@link AnnotationsEntrySetAction} (or the enclosing + // {@link AnnotationsSetAction}) introducing the entry. Id string `json:"id"` // Entry body. A bare `string` is rendered as plain text; pass // `{ markdown: "…" }` to opt into Markdown rendering. See // {@link StringOrMarkdown}. Text StringOrMarkdown `json:"text"` - // Server-defined opaque metadata, surfaced to tooling but not + // Producer-defined opaque metadata, surfaced to tooling but not // interpreted by the protocol. Meta map[string]json.RawMessage `json:"_meta,omitempty"` } -// Input shape passed to {@link CreateAnnotationParams | `createAnnotation`} -// and {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server -// assigns the resulting {@link AnnotationEntry.id}. -type NewAnnotationEntry struct { - // Entry body. See {@link AnnotationEntry.text}. - Text StringOrMarkdown `json:"text"` - // Server-defined opaque metadata, forwarded onto the resulting - // {@link AnnotationEntry._meta}. - Meta map[string]json.RawMessage `json:"_meta,omitempty"` -} - // OTLP telemetry channels the agent host emits. // // Each field, when present, is either a literal channel URI or an diff --git a/clients/kotlin/CHANGELOG.md b/clients/kotlin/CHANGELOG.md index c2178f50..3b9837e8 100644 --- a/clients/kotlin/CHANGELOG.md +++ b/clients/kotlin/CHANGELOG.md @@ -50,14 +50,13 @@ Implements AHP 0.3.0. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. - New annotations channel (`ahp-session://annotations`): `AnnotationsState`, - `Annotation`, `AnnotationEntry`, `NewAnnotationEntry`, + `Annotation`, `AnnotationEntry`, `AnnotationsSummary`; the `annotationsReducer` top-level function and - `AnnotationsReducer` object; the `annotations/set`, + `AnnotationsReducer` object; and the client-dispatchable `annotations/set`, `annotations/removed`, `annotations/entrySet`, and `annotations/entryRemoved` - action variants; `createAnnotation`, - `updateAnnotation`, `deleteAnnotation`, `addAnnotationEntry`, - `editAnnotationEntry`, and `deleteAnnotationEntry` request factories on - `AhpClientRequests`; and `SnapshotState.Annotations`. + action variants — clients drive every annotation mutation by dispatching + these directly (assigning the `Annotation.id` / `AnnotationEntry.id` + themselves); and `SnapshotState.Annotations`. `SessionSummary.annotations` surfaces the per-session `AnnotationsSummary`. - `MessageAnnotationsAttachment` (`annotations` `MessageAttachment` variant) referencing annotations on a session's annotations channel by `resource` diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt index 1139305d..e532cba7 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt @@ -1027,143 +1027,6 @@ data class InvokeChangesetOperationResult( val followUp: ChangesetOperationFollowUp? = null ) -@Serializable -data class CreateAnnotationParams( - /** - * Channel URI this command targets. - */ - val channel: String, - /** - * Turn whose file versions {@link resource} + {@link range} address. - */ - val turnId: String, - /** - * Anchored file URI. - */ - val resource: String, - /** - * Anchored range within {@link resource}. When omitted the annotation is - * anchored to the entire file. - */ - val range: TextRange? = null, - /** - * First entry in the annotation. The server assigns its {@link AnnotationEntry.id}. - */ - val entry: NewAnnotationEntry -) - -@Serializable -data class CreateAnnotationResult( - /** - * Server-assigned {@link Annotation.id}. - */ - val annotationId: String, - /** - * Server-assigned {@link AnnotationEntry.id} of the initial entry. - */ - val entryId: String -) - -@Serializable -data class UpdateAnnotationParams( - /** - * Channel URI this command targets. - */ - val channel: String, - /** - * The {@link Annotation.id} to update. - */ - val annotationId: String, - /** - * New {@link Annotation.turnId}, if changing. - */ - val turnId: String? = null, - /** - * New anchored file URI, if changing. - */ - val resource: String? = null, - /** - * New anchored range, if changing. - */ - val range: TextRange? = null, - /** - * New {@link Annotation.resolved} state, if changing. - */ - val resolved: Boolean? = null -) - -@Serializable -data class DeleteAnnotationParams( - /** - * Channel URI this command targets. - */ - val channel: String, - /** - * The {@link Annotation.id} to delete. - */ - val annotationId: String -) - -@Serializable -data class AddAnnotationEntryParams( - /** - * Channel URI this command targets. - */ - val channel: String, - /** - * Annotation that receives the new entry. - */ - val annotationId: String, - /** - * Entry payload — the server assigns the id. - */ - val entry: NewAnnotationEntry -) - -@Serializable -data class AddAnnotationEntryResult( - /** - * Server-assigned {@link AnnotationEntry.id} of the new entry. - */ - val entryId: String -) - -@Serializable -data class EditAnnotationEntryParams( - /** - * Channel URI this command targets. - */ - val channel: String, - /** - * Enclosing annotation. - */ - val annotationId: String, - /** - * {@link AnnotationEntry.id} to edit. - */ - val entryId: String, - /** - * New entry body. See {@link AnnotationEntry.text}. - */ - val text: StringOrMarkdown -) - -@Serializable -data class DeleteAnnotationEntryParams( - /** - * Channel URI this command targets. - */ - val channel: String, - /** - * Enclosing annotation. - */ - val annotationId: String, - /** - * {@link AnnotationEntry.id} to remove. - */ - val entryId: String -) - @Serializable data class ChangesetOperationFollowUp( val content: ContentRef, diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt index dc056593..07ed4755 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Messages.generated.kt @@ -145,24 +145,6 @@ object AhpCommands { fun invokeChangesetOperation(id: Long, params: InvokeChangesetOperationParams): JsonRpcRequest = JsonRpcRequest(id = id, method = "invokeChangesetOperation", params = params) - - fun createAnnotation(id: Long, params: CreateAnnotationParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "createAnnotation", params = params) - - fun updateAnnotation(id: Long, params: UpdateAnnotationParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "updateAnnotation", params = params) - - fun deleteAnnotation(id: Long, params: DeleteAnnotationParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "deleteAnnotation", params = params) - - fun addAnnotationEntry(id: Long, params: AddAnnotationEntryParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "addAnnotationEntry", params = params) - - fun editAnnotationEntry(id: Long, params: EditAnnotationEntryParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "editAnnotationEntry", params = params) - - fun deleteAnnotationEntry(id: Long, params: DeleteAnnotationEntryParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "deleteAnnotationEntry", params = params) } /** 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 d1742414..9af47c60 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 @@ -3383,7 +3383,8 @@ data class AnnotationsState( @Serializable data class Annotation( /** - * Stable identifier within the annotations channel. Server-assigned. + * Stable identifier within the annotations channel. Assigned by the client + * that dispatches the creating {@link AnnotationsSetAction}. */ val id: String, /** @@ -3403,7 +3404,8 @@ data class Annotation( /** * Whether the annotation has been resolved. Newly created annotations are * always unresolved (`false`); a client marks an annotation resolved (or - * re-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}. + * re-opens it) by dispatching an {@link AnnotationsSetAction} carrying the + * updated flag. */ val resolved: Boolean, /** @@ -3412,7 +3414,7 @@ data class Annotation( */ val entries: List, /** - * Server-defined opaque metadata, surfaced to tooling but not + * Producer-defined opaque metadata, surfaced to tooling but not * interpreted by the protocol. */ @SerialName("_meta") @@ -3422,7 +3424,9 @@ data class Annotation( @Serializable data class AnnotationEntry( /** - * Stable identifier within the enclosing annotation. Server-assigned. + * Stable identifier within the enclosing annotation. Assigned by the client + * that dispatches the {@link AnnotationsEntrySetAction} (or the enclosing + * {@link AnnotationsSetAction}) introducing the entry. */ val id: String, /** @@ -3432,27 +3436,13 @@ data class AnnotationEntry( */ val text: StringOrMarkdown, /** - * Server-defined opaque metadata, surfaced to tooling but not + * Producer-defined opaque metadata, surfaced to tooling but not * interpreted by the protocol. */ @SerialName("_meta") val meta: Map? = null ) -@Serializable -data class NewAnnotationEntry( - /** - * Entry body. See {@link AnnotationEntry.text}. - */ - val text: StringOrMarkdown, - /** - * Server-defined opaque metadata, forwarded onto the resulting - * {@link AnnotationEntry._meta}. - */ - @SerialName("_meta") - val meta: Map? = null -) - @Serializable data class TelemetryCapabilities( /** diff --git a/clients/rust/CHANGELOG.md b/clients/rust/CHANGELOG.md index e141dd66..545446a9 100644 --- a/clients/rust/CHANGELOG.md +++ b/clients/rust/CHANGELOG.md @@ -51,12 +51,12 @@ Implements AHP 0.3.0. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. - New annotations channel wire types (`ahp-session://annotations`): - `AnnotationsState`, `Annotation`, `AnnotationEntry`, `NewAnnotationEntry`, - `AnnotationsSummary`; the + `AnnotationsState`, `Annotation`, `AnnotationEntry`, + `AnnotationsSummary`; the client-dispatchable `annotations/set` / `annotations/removed` / `annotations/entrySet` - / `annotations/entryRemoved` action variants; - `createAnnotation`, `updateAnnotation`, `deleteAnnotation`, - `addAnnotationEntry`, `editAnnotationEntry`, and `deleteAnnotationEntry` command structs; + / `annotations/entryRemoved` action variants — clients drive every annotation + mutation by dispatching these directly, assigning the `Annotation.id` / + `AnnotationEntry.id` themselves; `MultiHostStateMirror.annotations()` and `SnapshotState::Annotations`. Reducer logic is deferred (matches the changeset stub). - `MessageAnnotationsAttachment` (`annotations` `MessageAttachment` variant) diff --git a/clients/rust/crates/ahp-types/src/actions.rs b/clients/rust/crates/ahp-types/src/actions.rs index 5d1b4325..627b6d68 100644 --- a/clients/rust/crates/ahp-types/src/actions.rs +++ b/clients/rust/crates/ahp-types/src/actions.rs @@ -978,10 +978,15 @@ pub struct ChangesetClearedAction {} /// Upsert an {@link Annotation} in the annotations channel — adds a new /// annotation, or replaces an existing one identified by -/// {@link Annotation.id}. When replacing, the full annotation payload -/// (including its {@link Annotation.entries | entries} list) is -/// substituted; producers SHOULD prefer {@link AnnotationsEntrySetAction} -/// for per-entry edits to keep wire updates small. +/// {@link Annotation.id}. +/// +/// Dispatched by a client to create an annotation (together with its +/// mandatory first entry) or to re-anchor / resolve an existing one; the +/// dispatching client assigns the {@link Annotation.id} and the id of any +/// new entry. When replacing, the full annotation payload (including its +/// {@link Annotation.entries | entries} list) is substituted; producers +/// SHOULD prefer {@link AnnotationsEntrySetAction} for per-entry edits to +/// keep wire updates small. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AnnotationsSetAction { @@ -991,13 +996,10 @@ pub struct AnnotationsSetAction { /// Remove an {@link Annotation} from the channel by its id. /// -/// The server emits this in two cases: -/// 1. The client explicitly invoked -/// {@link DeleteAnnotationParams | `deleteAnnotation`}. -/// 2. The client invoked {@link DeleteAnnotationEntryParams | -/// `deleteAnnotationEntry`} on the last remaining entry in the -/// annotation — the protocol collapses the annotation rather than -/// leaving an empty one behind. +/// Dispatched to delete an entire annotation and every entry it contains. +/// Because the protocol forbids empty annotations, a client that wants to +/// remove the last remaining entry dispatches this action — collapsing the +/// annotation — rather than {@link AnnotationsEntryRemovedAction}. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AnnotationsRemovedAction { @@ -1006,9 +1008,10 @@ pub struct AnnotationsRemovedAction { } /// Upsert an {@link AnnotationEntry} within an existing annotation — adds a -/// new entry, or replaces one identified by {@link AnnotationEntry.id}. If -/// {@link annotationId} does not match any current annotation the action is -/// a no-op. +/// new entry, or replaces one identified by {@link AnnotationEntry.id}. The +/// dispatching client assigns the {@link AnnotationEntry.id} of a new entry. +/// If {@link annotationId} does not match any current annotation the action +/// is a no-op. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AnnotationsEntrySetAction { @@ -1019,9 +1022,9 @@ pub struct AnnotationsEntrySetAction { } /// Remove a single {@link AnnotationEntry} from an annotation without -/// collapsing the annotation itself. Used when more than one entry remains -/// — the server MUST dispatch {@link AnnotationsRemovedAction} instead when -/// removing the last entry would otherwise leave the annotation empty. +/// collapsing the annotation itself. Used when more than one entry remains — +/// to remove the last entry a client dispatches {@link AnnotationsRemovedAction} +/// instead, since the protocol forbids empty annotations. /// /// If either {@link annotationId} or {@link entryId} does not match the /// current state the action is a no-op. diff --git a/clients/rust/crates/ahp-types/src/commands.rs b/clients/rust/crates/ahp-types/src/commands.rs index 3abc9c07..a87e1626 100644 --- a/clients/rust/crates/ahp-types/src/commands.rs +++ b/clients/rust/crates/ahp-types/src/commands.rs @@ -15,9 +15,9 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::actions::{ActionEnvelope, StateAction}; #[allow(unused_imports)] use crate::state::{ - AgentSelection, ContentRef, MessageAttachment, ModelSelection, NewAnnotationEntry, - SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, - TelemetryCapabilities, TerminalClaim, TextRange, Turn, + AgentSelection, ContentRef, MessageAttachment, ModelSelection, SessionActiveClient, + SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, + TerminalClaim, TextRange, Turn, }; // ─── Enums ──────────────────────────────────────────────────────────── @@ -1050,143 +1050,6 @@ pub struct ChangesetOperationFollowUp { pub external: Option, } -/// Create a new {@link Annotation} anchored to a file from a specific -/// turn, optionally narrowed to a range within that file. -/// -/// The initial entry is required — the protocol forbids empty annotations, -/// so annotation creation and first-entry creation are fused into one -/// command. The created annotation always starts unresolved -/// ({@link Annotation.resolved} is `false`). The server assigns both -/// {@link CreateAnnotationResult.annotationId} and -/// {@link CreateAnnotationResult.entryId}, then broadcasts an -/// {@link AnnotationsSetAction} on the channel. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CreateAnnotationParams { - /// Channel URI this command targets. - pub channel: Uri, - /// Turn whose file versions {@link resource} + {@link range} address. - pub turn_id: String, - /// Anchored file URI. - pub resource: Uri, - /// Anchored range within {@link resource}. When omitted the annotation is - /// anchored to the entire file. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub range: Option, - /// First entry in the annotation. The server assigns its {@link AnnotationEntry.id}. - pub entry: NewAnnotationEntry, -} - -/// Result of {@link CreateAnnotationParams | `createAnnotation`}. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CreateAnnotationResult { - /// Server-assigned {@link Annotation.id}. - pub annotation_id: String, - /// Server-assigned {@link AnnotationEntry.id} of the initial entry. - pub entry_id: String, -} - -/// Re-anchor or resolve an existing {@link Annotation} — typically used -/// to re-pin an annotation to a different range or a newer turn after an -/// edit, or to mark the annotation {@link Annotation.resolved | resolved} -/// (or re-open it). Entries themselves are not modified by this command; -/// use {@link AddAnnotationEntryParams | `addAnnotationEntry`}, -/// {@link EditAnnotationEntryParams | `editAnnotationEntry`}, or -/// {@link DeleteAnnotationEntryParams | `deleteAnnotationEntry`} for that. -/// -/// Omitted optional fields preserve their current value. The server -/// echoes the resulting annotation state as an {@link AnnotationsSetAction}. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UpdateAnnotationParams { - /// Channel URI this command targets. - pub channel: Uri, - /// The {@link Annotation.id} to update. - pub annotation_id: String, - /// New {@link Annotation.turnId}, if changing. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub turn_id: Option, - /// New anchored file URI, if changing. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub resource: Option, - /// New anchored range, if changing. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub range: Option, - /// New {@link Annotation.resolved} state, if changing. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub resolved: Option, -} - -/// Delete an entire annotation (and every entry it contains). The -/// server echoes an {@link AnnotationsRemovedAction} on the channel. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DeleteAnnotationParams { - /// Channel URI this command targets. - pub channel: Uri, - /// The {@link Annotation.id} to delete. - pub annotation_id: String, -} - -/// Append a new {@link AnnotationEntry} to an existing annotation. The -/// server assigns the resulting {@link AnnotationEntry.id} and echoes an -/// {@link AnnotationsEntrySetAction}. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AddAnnotationEntryParams { - /// Channel URI this command targets. - pub channel: Uri, - /// Annotation that receives the new entry. - pub annotation_id: String, - /// Entry payload — the server assigns the id. - pub entry: NewAnnotationEntry, -} - -/// Result of {@link AddAnnotationEntryParams | `addAnnotationEntry`}. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AddAnnotationEntryResult { - /// Server-assigned {@link AnnotationEntry.id} of the new entry. - pub entry_id: String, -} - -/// Edit the body of an existing entry in place. The server echoes an -/// {@link AnnotationsEntrySetAction} carrying the updated entry. -/// -/// Only the body is mutable through this command; to change -/// {@link AnnotationEntry._meta} delete and re-create the entry. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EditAnnotationEntryParams { - /// Channel URI this command targets. - pub channel: Uri, - /// Enclosing annotation. - pub annotation_id: String, - /// {@link AnnotationEntry.id} to edit. - pub entry_id: String, - /// New entry body. See {@link AnnotationEntry.text}. - pub text: StringOrMarkdown, -} - -/// Remove a single entry from an annotation. -/// -/// If the removal would leave the annotation empty (i.e. the targeted entry -/// is the only one remaining), the server collapses the annotation instead -/// — it dispatches an {@link AnnotationsRemovedAction} and the annotation -/// disappears from {@link AnnotationsState.annotations}. Otherwise the server -/// echoes an {@link AnnotationsEntryRemovedAction}. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DeleteAnnotationEntryParams { - /// Channel URI this command targets. - pub channel: Uri, - /// Enclosing annotation. - pub annotation_id: String, - /// {@link AnnotationEntry.id} to remove. - pub entry_id: String, -} - // ─── ReconnectResult Union ──────────────────────────────────────────── /// Result of the `reconnect` command. diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 411285cf..8dffb43b 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -2920,16 +2920,16 @@ pub struct AnnotationsState { /// and {@link range} against the turn's changeset. When {@link range} is /// omitted the annotation is anchored to the entire file. /// -/// Every annotation MUST contain at least one {@link AnnotationEntry}. The -/// server enforces this invariant: {@link CreateAnnotationParams | -/// `createAnnotation`} requires an initial entry, and deleting the -/// last remaining entry collapses the annotation into a -/// {@link AnnotationsRemovedAction} rather than leaving an empty annotation -/// behind. +/// Every annotation MUST contain at least one {@link AnnotationEntry}. An +/// {@link AnnotationsSetAction} that creates an annotation therefore carries +/// its mandatory first entry, and removing the last remaining entry collapses +/// the annotation via {@link AnnotationsRemovedAction} rather than leaving an +/// empty annotation behind. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Annotation { - /// Stable identifier within the annotations channel. Server-assigned. + /// Stable identifier within the annotations channel. Assigned by the client + /// that dispatches the creating {@link AnnotationsSetAction}. pub id: String, /// Turn that produced the file versions this annotation is anchored to. /// Matches a {@link Turn.id} on the owning session. @@ -2942,12 +2942,13 @@ pub struct Annotation { pub range: Option, /// Whether the annotation has been resolved. Newly created annotations are /// always unresolved (`false`); a client marks an annotation resolved (or - /// re-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}. + /// re-opens it) by dispatching an {@link AnnotationsSetAction} carrying the + /// updated flag. pub resolved: bool, /// Entries in this annotation, in dispatch order (oldest first). MUST /// contain at least one entry. pub entries: Vec, - /// Server-defined opaque metadata, surfaced to tooling but not + /// Producer-defined opaque metadata, surfaced to tooling but not /// interpreted by the protocol. #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, @@ -2957,32 +2958,20 @@ pub struct Annotation { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AnnotationEntry { - /// Stable identifier within the enclosing annotation. Server-assigned. + /// Stable identifier within the enclosing annotation. Assigned by the client + /// that dispatches the {@link AnnotationsEntrySetAction} (or the enclosing + /// {@link AnnotationsSetAction}) introducing the entry. pub id: String, /// Entry body. A bare `string` is rendered as plain text; pass /// `{ markdown: "…" }` to opt into Markdown rendering. See /// {@link StringOrMarkdown}. pub text: StringOrMarkdown, - /// Server-defined opaque metadata, surfaced to tooling but not + /// Producer-defined opaque metadata, surfaced to tooling but not /// interpreted by the protocol. #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, } -/// Input shape passed to {@link CreateAnnotationParams | `createAnnotation`} -/// and {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server -/// assigns the resulting {@link AnnotationEntry.id}. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct NewAnnotationEntry { - /// Entry body. See {@link AnnotationEntry.text}. - pub text: StringOrMarkdown, - /// Server-defined opaque metadata, forwarded onto the resulting - /// {@link AnnotationEntry._meta}. - #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] - pub meta: Option, -} - /// OTLP telemetry channels the agent host emits. /// /// Each field, when present, is either a literal channel URI or an diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift index 73f037f8..922d1e6f 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift @@ -1174,167 +1174,6 @@ public struct InvokeChangesetOperationResult: Codable, Sendable { } } -public struct CreateAnnotationParams: Codable, Sendable { - /// Channel URI this command targets. - public var channel: String - /// Turn whose file versions {@link resource} + {@link range} address. - public var turnId: String - /// Anchored file URI. - public var resource: String - /// Anchored range within {@link resource}. When omitted the annotation is - /// anchored to the entire file. - public var range: TextRange? - /// First entry in the annotation. The server assigns its {@link AnnotationEntry.id}. - public var entry: NewAnnotationEntry - - public init( - channel: String, - turnId: String, - resource: String, - range: TextRange? = nil, - entry: NewAnnotationEntry - ) { - self.channel = channel - self.turnId = turnId - self.resource = resource - self.range = range - self.entry = entry - } -} - -public struct CreateAnnotationResult: Codable, Sendable { - /// Server-assigned {@link Annotation.id}. - public var annotationId: String - /// Server-assigned {@link AnnotationEntry.id} of the initial entry. - public var entryId: String - - public init( - annotationId: String, - entryId: String - ) { - self.annotationId = annotationId - self.entryId = entryId - } -} - -public struct UpdateAnnotationParams: Codable, Sendable { - /// Channel URI this command targets. - public var channel: String - /// The {@link Annotation.id} to update. - public var annotationId: String - /// New {@link Annotation.turnId}, if changing. - public var turnId: String? - /// New anchored file URI, if changing. - public var resource: String? - /// New anchored range, if changing. - public var range: TextRange? - /// New {@link Annotation.resolved} state, if changing. - public var resolved: Bool? - - public init( - channel: String, - annotationId: String, - turnId: String? = nil, - resource: String? = nil, - range: TextRange? = nil, - resolved: Bool? = nil - ) { - self.channel = channel - self.annotationId = annotationId - self.turnId = turnId - self.resource = resource - self.range = range - self.resolved = resolved - } -} - -public struct DeleteAnnotationParams: Codable, Sendable { - /// Channel URI this command targets. - public var channel: String - /// The {@link Annotation.id} to delete. - public var annotationId: String - - public init( - channel: String, - annotationId: String - ) { - self.channel = channel - self.annotationId = annotationId - } -} - -public struct AddAnnotationEntryParams: Codable, Sendable { - /// Channel URI this command targets. - public var channel: String - /// Annotation that receives the new entry. - public var annotationId: String - /// Entry payload — the server assigns the id. - public var entry: NewAnnotationEntry - - public init( - channel: String, - annotationId: String, - entry: NewAnnotationEntry - ) { - self.channel = channel - self.annotationId = annotationId - self.entry = entry - } -} - -public struct AddAnnotationEntryResult: Codable, Sendable { - /// Server-assigned {@link AnnotationEntry.id} of the new entry. - public var entryId: String - - public init( - entryId: String - ) { - self.entryId = entryId - } -} - -public struct EditAnnotationEntryParams: Codable, Sendable { - /// Channel URI this command targets. - public var channel: String - /// Enclosing annotation. - public var annotationId: String - /// {@link AnnotationEntry.id} to edit. - public var entryId: String - /// New entry body. See {@link AnnotationEntry.text}. - public var text: StringOrMarkdown - - public init( - channel: String, - annotationId: String, - entryId: String, - text: StringOrMarkdown - ) { - self.channel = channel - self.annotationId = annotationId - self.entryId = entryId - self.text = text - } -} - -public struct DeleteAnnotationEntryParams: Codable, Sendable { - /// Channel URI this command targets. - public var channel: String - /// Enclosing annotation. - public var annotationId: String - /// {@link AnnotationEntry.id} to remove. - public var entryId: String - - public init( - channel: String, - annotationId: String, - entryId: String - ) { - self.channel = channel - self.annotationId = annotationId - self.entryId = entryId - } -} - public struct ChangesetOperationFollowUp: Codable, Sendable { public var content: ContentRef /// When `true`, open in an external handler rather than inline. diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 117df9f3..e0c74179 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -3709,7 +3709,8 @@ public struct AnnotationsState: Codable, Sendable { } public struct Annotation: Codable, Sendable { - /// Stable identifier within the annotations channel. Server-assigned. + /// Stable identifier within the annotations channel. Assigned by the client + /// that dispatches the creating {@link AnnotationsSetAction}. public var id: String /// Turn that produced the file versions this annotation is anchored to. /// Matches a {@link Turn.id} on the owning session. @@ -3721,12 +3722,13 @@ public struct Annotation: Codable, Sendable { public var range: TextRange? /// Whether the annotation has been resolved. Newly created annotations are /// always unresolved (`false`); a client marks an annotation resolved (or - /// re-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}. + /// re-opens it) by dispatching an {@link AnnotationsSetAction} carrying the + /// updated flag. public var resolved: Bool /// Entries in this annotation, in dispatch order (oldest first). MUST /// contain at least one entry. public var entries: [AnnotationEntry] - /// Server-defined opaque metadata, surfaced to tooling but not + /// Producer-defined opaque metadata, surfaced to tooling but not /// interpreted by the protocol. public var meta: [String: AnyCodable]? @@ -3760,13 +3762,15 @@ public struct Annotation: Codable, Sendable { } public struct AnnotationEntry: Codable, Sendable { - /// Stable identifier within the enclosing annotation. Server-assigned. + /// Stable identifier within the enclosing annotation. Assigned by the client + /// that dispatches the {@link AnnotationsEntrySetAction} (or the enclosing + /// {@link AnnotationsSetAction}) introducing the entry. public var id: String /// Entry body. A bare `string` is rendered as plain text; pass /// `{ markdown: "…" }` to opt into Markdown rendering. See /// {@link StringOrMarkdown}. public var text: StringOrMarkdown - /// Server-defined opaque metadata, surfaced to tooling but not + /// Producer-defined opaque metadata, surfaced to tooling but not /// interpreted by the protocol. public var meta: [String: AnyCodable]? @@ -3787,27 +3791,6 @@ public struct AnnotationEntry: Codable, Sendable { } } -public struct NewAnnotationEntry: Codable, Sendable { - /// Entry body. See {@link AnnotationEntry.text}. - public var text: StringOrMarkdown - /// Server-defined opaque metadata, forwarded onto the resulting - /// {@link AnnotationEntry._meta}. - public var meta: [String: AnyCodable]? - - enum CodingKeys: String, CodingKey { - case text - case meta = "_meta" - } - - public init( - text: StringOrMarkdown, - meta: [String: AnyCodable]? = nil - ) { - self.text = text - self.meta = meta - } -} - public struct TelemetryCapabilities: Codable, Sendable { /// Channel URI (or RFC 6570 URI template) for OTLP log records /// (`otlp/exportLogs` notifications). diff --git a/clients/swift/CHANGELOG.md b/clients/swift/CHANGELOG.md index 41797805..5d680c6b 100644 --- a/clients/swift/CHANGELOG.md +++ b/clients/swift/CHANGELOG.md @@ -53,13 +53,13 @@ Implements AHP 0.3.0. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. - New annotations channel wire types (`ahp-session://annotations`): - `AnnotationsState`, `Annotation`, `AnnotationEntry`, `NewAnnotationEntry`, - `AnnotationsSummary`; the + `AnnotationsState`, `Annotation`, `AnnotationEntry`, + `AnnotationsSummary`; and the client-dispatchable `annotations/set` / `annotations/removed` / `annotations/entrySet` - / `annotations/entryRemoved` cases on `StateAction`; - `CreateAnnotationParams/Result`, `UpdateAnnotationParams`, - `DeleteAnnotationParams`, `AddAnnotationEntryParams/Result`, - `EditAnnotationEntryParams`, `DeleteAnnotationEntryParams`; and `SnapshotState.annotations`. + / `annotations/entryRemoved` cases on `StateAction` — clients drive every + annotation mutation by dispatching these directly, assigning the + `Annotation.id` / `AnnotationEntry.id` themselves; and + `SnapshotState.annotations`. Reducer logic is deferred (matches the changeset/resource-watch parity). - `MessageAnnotationsAttachment` (`annotations` `MessageAttachment` variant) referencing annotations on a session's annotations channel by `resource` diff --git a/clients/typescript/CHANGELOG.md b/clients/typescript/CHANGELOG.md index aa37ccc4..aec639e5 100644 --- a/clients/typescript/CHANGELOG.md +++ b/clients/typescript/CHANGELOG.md @@ -56,12 +56,13 @@ Implements AHP 0.3.0. - `AgentCustomization._meta` provider metadata field. - Optional `changes` field on `SessionSummary` (`ChangesSummary` with optional `additions`, `deletions`, and `files` counts) summarising a session's file-change footprint. - New annotations channel (`ahp-session://annotations`): `AnnotationsState`, - `Annotation`, `AnnotationEntry`, `NewAnnotationEntry`, `AnnotationsSummary`, - the `annotationsReducer`, the `annotations/set`, `annotations/removed`, - `annotations/entrySet`, and `annotations/entryRemoved` actions, - and the `createAnnotation`, `updateAnnotation`, `deleteAnnotation`, - `addAnnotationEntry`, `editAnnotationEntry`, `deleteAnnotationEntry` commands. `SessionSummary.annotations` - surfaces the per-session `AnnotationsSummary` for badge UI. + `Annotation`, `AnnotationEntry`, `AnnotationsSummary`, + the `annotationsReducer`, and the client-dispatchable `annotations/set`, + `annotations/removed`, `annotations/entrySet`, and `annotations/entryRemoved` + actions — clients drive every annotation mutation by dispatching these + directly, assigning the `Annotation.id` / `AnnotationEntry.id` themselves. + `SessionSummary.annotations` surfaces the per-session `AnnotationsSummary` + for badge UI. - `MessageAnnotationsAttachment` (`annotations` `MessageAttachment` variant) referencing annotations on a session's annotations channel by `resource` URI, optionally narrowed to an `annotationIds` array. diff --git a/docs/guide/actions.md b/docs/guide/actions.md index 9a715e72..6aa2a0f6 100644 --- a/docs/guide/actions.md +++ b/docs/guide/actions.md @@ -149,6 +149,19 @@ Terminal actions travel on the relevant [Terminal Channel](/specification/termin See the [Terminals guide](/guide/terminals) for usage flows. +## Annotations Actions + +Annotations actions travel on a session's annotations channel (`ahp-session://annotations`). Every annotations action is client-dispatchable — clients create, re-anchor, resolve, and delete annotations and their entries by dispatching these directly (assigning the `Annotation.id` / `AnnotationEntry.id` themselves and applying them optimistically), and the agent host MAY also originate them. + +| Type | Client-dispatchable? | When | +|---|---|---| +| `annotations/set` | **Yes** | Upsert an annotation — create one with its mandatory first entry, or re-anchor / resolve an existing one | +| `annotations/removed` | **Yes** | Remove an entire annotation (and every entry it contains) | +| `annotations/entrySet` | **Yes** | Upsert a single entry within an annotation (add or edit) | +| `annotations/entryRemoved` | **Yes** | Remove a single entry; dispatch `annotations/removed` instead to drop the last remaining entry | + +See the [Annotations Channel reference](/reference/annotations) for the full state shape. + ## Client-Dispatched Actions Clients interact with the server by dispatching actions as fire-and-forget notifications: diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 39485abe..a151b7b8 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -1502,7 +1502,7 @@ }, "AnnotationsSetAction": { "type": "object", - "description": "Upsert an {@link Annotation} in the annotations channel — adds a new\nannotation, or replaces an existing one identified by\n{@link Annotation.id}. When replacing, the full annotation payload\n(including its {@link Annotation.entries | entries} list) is\nsubstituted; producers SHOULD prefer {@link AnnotationsEntrySetAction}\nfor per-entry edits to keep wire updates small.", + "description": "Upsert an {@link Annotation} in the annotations channel — adds a new\nannotation, or replaces an existing one identified by\n{@link Annotation.id}.\n\nDispatched by a client to create an annotation (together with its\nmandatory first entry) or to re-anchor / resolve an existing one; the\ndispatching client assigns the {@link Annotation.id} and the id of any\nnew entry. When replacing, the full annotation payload (including its\n{@link Annotation.entries | entries} list) is substituted; producers\nSHOULD prefer {@link AnnotationsEntrySetAction} for per-entry edits to\nkeep wire updates small.", "properties": { "type": { "$ref": "#/$defs/ActionType.AnnotationsSet" @@ -1519,7 +1519,7 @@ }, "AnnotationsRemovedAction": { "type": "object", - "description": "Remove an {@link Annotation} from the channel by its id.\n\nThe server emits this in two cases:\n1. The client explicitly invoked\n {@link DeleteAnnotationParams | `deleteAnnotation`}.\n2. The client invoked {@link DeleteAnnotationEntryParams |\n `deleteAnnotationEntry`} on the last remaining entry in the\n annotation — the protocol collapses the annotation rather than\n leaving an empty one behind.", + "description": "Remove an {@link Annotation} from the channel by its id.\n\nDispatched to delete an entire annotation and every entry it contains.\nBecause the protocol forbids empty annotations, a client that wants to\nremove the last remaining entry dispatches this action — collapsing the\nannotation — rather than {@link AnnotationsEntryRemovedAction}.", "properties": { "type": { "$ref": "#/$defs/ActionType.AnnotationsRemoved" @@ -1536,7 +1536,7 @@ }, "AnnotationsEntrySetAction": { "type": "object", - "description": "Upsert an {@link AnnotationEntry} within an existing annotation — adds a\nnew entry, or replaces one identified by {@link AnnotationEntry.id}. If\n{@link annotationId} does not match any current annotation the action is\na no-op.", + "description": "Upsert an {@link AnnotationEntry} within an existing annotation — adds a\nnew entry, or replaces one identified by {@link AnnotationEntry.id}. The\ndispatching client assigns the {@link AnnotationEntry.id} of a new entry.\nIf {@link annotationId} does not match any current annotation the action\nis a no-op.", "properties": { "type": { "$ref": "#/$defs/ActionType.AnnotationsEntrySet" @@ -1558,7 +1558,7 @@ }, "AnnotationsEntryRemovedAction": { "type": "object", - "description": "Remove a single {@link AnnotationEntry} from an annotation without\ncollapsing the annotation itself. Used when more than one entry remains\n— the server MUST dispatch {@link AnnotationsRemovedAction} instead when\nremoving the last entry would otherwise leave the annotation empty.\n\nIf either {@link annotationId} or {@link entryId} does not match the\ncurrent state the action is a no-op.", + "description": "Remove a single {@link AnnotationEntry} from an annotation without\ncollapsing the annotation itself. Used when more than one entry remains —\nto remove the last entry a client dispatches {@link AnnotationsRemovedAction}\ninstead, since the protocol forbids empty annotations.\n\nIf either {@link annotationId} or {@link entryId} does not match the\ncurrent state the action is a no-op.", "properties": { "type": { "$ref": "#/$defs/ActionType.AnnotationsEntryRemoved" @@ -5385,11 +5385,11 @@ }, "Annotation": { "type": "object", - "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. The\nserver enforces this invariant: {@link CreateAnnotationParams |\n`createAnnotation`} requires an initial entry, and deleting the\nlast remaining entry collapses the annotation into a\n{@link AnnotationsRemovedAction} rather than leaving an empty annotation\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. An\n{@link AnnotationsSetAction} that creates an annotation therefore carries\nits mandatory first entry, and removing the last remaining entry collapses\nthe annotation via {@link AnnotationsRemovedAction} rather than leaving an\nempty annotation behind.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the annotations channel. Server-assigned." + "description": "Stable identifier within the annotations channel. Assigned by the client\nthat dispatches the creating {@link AnnotationsSetAction}." }, "turnId": { "type": "string", @@ -5405,7 +5405,7 @@ }, "resolved": { "type": "boolean", - "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}." + "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) by dispatching an {@link AnnotationsSetAction} carrying the\nupdated flag." }, "entries": { "type": "array", @@ -5417,7 +5417,7 @@ "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." } }, "required": [ @@ -5434,7 +5434,7 @@ "properties": { "id": { "type": "string", - "description": "Stable identifier within the enclosing annotation. Server-assigned." + "description": "Stable identifier within the enclosing annotation. Assigned by the client\nthat dispatches the {@link AnnotationsEntrySetAction} (or the enclosing\n{@link AnnotationsSetAction}) introducing the entry." }, "text": { "$ref": "#/$defs/StringOrMarkdown", @@ -5443,7 +5443,7 @@ "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." } }, "required": [ @@ -5451,24 +5451,6 @@ "text" ] }, - "NewAnnotationEntry": { - "type": "object", - "description": "Input shape passed to {@link CreateAnnotationParams | `createAnnotation`}\nand {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server\nassigns the resulting {@link AnnotationEntry.id}.", - "properties": { - "text": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Entry body. See {@link AnnotationEntry.text}." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link AnnotationEntry._meta}." - } - }, - "required": [ - "text" - ] - }, "TelemetryCapabilities": { "type": "object", "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 30426690..8070e89f 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -1116,195 +1116,6 @@ } } }, - "CreateAnnotationParams": { - "type": "object", - "description": "Create a new {@link Annotation} anchored to a file from a specific\nturn, optionally narrowed to a range within that file.\n\nThe initial entry is required — the protocol forbids empty annotations,\nso annotation creation and first-entry creation are fused into one\ncommand. The created annotation always starts unresolved\n({@link Annotation.resolved} is `false`). The server assigns both\n{@link CreateAnnotationResult.annotationId} and\n{@link CreateAnnotationResult.entryId}, then broadcasts an\n{@link AnnotationsSetAction} on the channel.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI, e.g. `ahp-session://annotations`." - }, - "turnId": { - "type": "string", - "description": "Turn whose file versions {@link resource} + {@link range} address." - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "Anchored file URI." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Anchored range within {@link resource}. When omitted the annotation is\nanchored to the entire file." - }, - "entry": { - "$ref": "#/$defs/NewAnnotationEntry", - "description": "First entry in the annotation. The server assigns its {@link AnnotationEntry.id}." - } - }, - "required": [ - "channel", - "turnId", - "resource", - "entry" - ] - }, - "CreateAnnotationResult": { - "type": "object", - "description": "Result of {@link CreateAnnotationParams | `createAnnotation`}.", - "properties": { - "annotationId": { - "type": "string", - "description": "Server-assigned {@link Annotation.id}." - }, - "entryId": { - "type": "string", - "description": "Server-assigned {@link AnnotationEntry.id} of the initial entry." - } - }, - "required": [ - "annotationId", - "entryId" - ] - }, - "UpdateAnnotationParams": { - "type": "object", - "description": "Re-anchor or resolve an existing {@link Annotation} — typically used\nto re-pin an annotation to a different range or a newer turn after an\nedit, or to mark the annotation {@link Annotation.resolved | resolved}\n(or re-open it). Entries themselves are not modified by this command;\nuse {@link AddAnnotationEntryParams | `addAnnotationEntry`},\n{@link EditAnnotationEntryParams | `editAnnotationEntry`}, or\n{@link DeleteAnnotationEntryParams | `deleteAnnotationEntry`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting annotation state as an {@link AnnotationsSetAction}.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI." - }, - "annotationId": { - "type": "string", - "description": "The {@link Annotation.id} to update." - }, - "turnId": { - "type": "string", - "description": "New {@link Annotation.turnId}, if changing." - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "New anchored file URI, if changing." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "New anchored range, if changing." - }, - "resolved": { - "type": "boolean", - "description": "New {@link Annotation.resolved} state, if changing." - } - }, - "required": [ - "channel", - "annotationId" - ] - }, - "DeleteAnnotationParams": { - "type": "object", - "description": "Delete an entire annotation (and every entry it contains). The\nserver echoes an {@link AnnotationsRemovedAction} on the channel.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI." - }, - "annotationId": { - "type": "string", - "description": "The {@link Annotation.id} to delete." - } - }, - "required": [ - "channel", - "annotationId" - ] - }, - "AddAnnotationEntryParams": { - "type": "object", - "description": "Append a new {@link AnnotationEntry} to an existing annotation. The\nserver assigns the resulting {@link AnnotationEntry.id} and echoes an\n{@link AnnotationsEntrySetAction}.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI." - }, - "annotationId": { - "type": "string", - "description": "Annotation that receives the new entry." - }, - "entry": { - "$ref": "#/$defs/NewAnnotationEntry", - "description": "Entry payload — the server assigns the id." - } - }, - "required": [ - "channel", - "annotationId", - "entry" - ] - }, - "AddAnnotationEntryResult": { - "type": "object", - "description": "Result of {@link AddAnnotationEntryParams | `addAnnotationEntry`}.", - "properties": { - "entryId": { - "type": "string", - "description": "Server-assigned {@link AnnotationEntry.id} of the new entry." - } - }, - "required": [ - "entryId" - ] - }, - "EditAnnotationEntryParams": { - "type": "object", - "description": "Edit the body of an existing entry in place. The server echoes an\n{@link AnnotationsEntrySetAction} carrying the updated entry.\n\nOnly the body is mutable through this command; to change\n{@link AnnotationEntry._meta} delete and re-create the entry.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI." - }, - "annotationId": { - "type": "string", - "description": "Enclosing annotation." - }, - "entryId": { - "type": "string", - "description": "{@link AnnotationEntry.id} to edit." - }, - "text": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "New entry body. See {@link AnnotationEntry.text}." - } - }, - "required": [ - "channel", - "annotationId", - "entryId", - "text" - ] - }, - "DeleteAnnotationEntryParams": { - "type": "object", - "description": "Remove a single entry from an annotation.\n\nIf the removal would leave the annotation empty (i.e. the targeted entry\nis the only one remaining), the server collapses the annotation instead\n— it dispatches an {@link AnnotationsRemovedAction} and the annotation\ndisappears from {@link AnnotationsState.annotations}. Otherwise the server\nechoes an {@link AnnotationsEntryRemovedAction}.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI." - }, - "annotationId": { - "type": "string", - "description": "Enclosing annotation." - }, - "entryId": { - "type": "string", - "description": "{@link AnnotationEntry.id} to remove." - } - }, - "required": [ - "channel", - "annotationId", - "entryId" - ] - }, "CreateResourceWatchParams": { "type": "object", "description": "Creates a resource watcher on the receiver's filesystem.\n\nThe receiver allocates an `ahp-resource-watch:/` channel URI and\nreturns it on {@link CreateResourceWatchResult.channel}. The caller then\n[`subscribe`](./subscriptions)s to that channel to receive\n`resourceWatch/changed` actions over the standard action envelope.\n\nThe watch lifecycle is tied to subscription: when every subscriber has\nunsubscribed (or the underlying connection drops), the receiver MUST\nrelease the watcher. There is no explicit dispose command — `unsubscribe`\nis the only handle the caller needs.\n\nLike the rest of the `resource*` family, `createResourceWatch` is\nsymmetrical and MAY be sent in either direction. Access is gated through\nthe same permission flow as `resourceRead`/`resourceWrite`.", @@ -5136,11 +4947,11 @@ }, "Annotation": { "type": "object", - "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. The\nserver enforces this invariant: {@link CreateAnnotationParams |\n`createAnnotation`} requires an initial entry, and deleting the\nlast remaining entry collapses the annotation into a\n{@link AnnotationsRemovedAction} rather than leaving an empty annotation\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. An\n{@link AnnotationsSetAction} that creates an annotation therefore carries\nits mandatory first entry, and removing the last remaining entry collapses\nthe annotation via {@link AnnotationsRemovedAction} rather than leaving an\nempty annotation behind.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the annotations channel. Server-assigned." + "description": "Stable identifier within the annotations channel. Assigned by the client\nthat dispatches the creating {@link AnnotationsSetAction}." }, "turnId": { "type": "string", @@ -5156,7 +4967,7 @@ }, "resolved": { "type": "boolean", - "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}." + "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) by dispatching an {@link AnnotationsSetAction} carrying the\nupdated flag." }, "entries": { "type": "array", @@ -5168,7 +4979,7 @@ "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." } }, "required": [ @@ -5185,7 +4996,7 @@ "properties": { "id": { "type": "string", - "description": "Stable identifier within the enclosing annotation. Server-assigned." + "description": "Stable identifier within the enclosing annotation. Assigned by the client\nthat dispatches the {@link AnnotationsEntrySetAction} (or the enclosing\n{@link AnnotationsSetAction}) introducing the entry." }, "text": { "$ref": "#/$defs/StringOrMarkdown", @@ -5194,7 +5005,7 @@ "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." } }, "required": [ @@ -5202,24 +5013,6 @@ "text" ] }, - "NewAnnotationEntry": { - "type": "object", - "description": "Input shape passed to {@link CreateAnnotationParams | `createAnnotation`}\nand {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server\nassigns the resulting {@link AnnotationEntry.id}.", - "properties": { - "text": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Entry body. See {@link AnnotationEntry.text}." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link AnnotationEntry._meta}." - } - }, - "required": [ - "text" - ] - }, "TelemetryCapabilities": { "type": "object", "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", @@ -6795,7 +6588,7 @@ }, "AnnotationsSetAction": { "type": "object", - "description": "Upsert an {@link Annotation} in the annotations channel — adds a new\nannotation, or replaces an existing one identified by\n{@link Annotation.id}. When replacing, the full annotation payload\n(including its {@link Annotation.entries | entries} list) is\nsubstituted; producers SHOULD prefer {@link AnnotationsEntrySetAction}\nfor per-entry edits to keep wire updates small.", + "description": "Upsert an {@link Annotation} in the annotations channel — adds a new\nannotation, or replaces an existing one identified by\n{@link Annotation.id}.\n\nDispatched by a client to create an annotation (together with its\nmandatory first entry) or to re-anchor / resolve an existing one; the\ndispatching client assigns the {@link Annotation.id} and the id of any\nnew entry. When replacing, the full annotation payload (including its\n{@link Annotation.entries | entries} list) is substituted; producers\nSHOULD prefer {@link AnnotationsEntrySetAction} for per-entry edits to\nkeep wire updates small.", "properties": { "type": { "$ref": "#/$defs/ActionType.AnnotationsSet" @@ -6812,7 +6605,7 @@ }, "AnnotationsRemovedAction": { "type": "object", - "description": "Remove an {@link Annotation} from the channel by its id.\n\nThe server emits this in two cases:\n1. The client explicitly invoked\n {@link DeleteAnnotationParams | `deleteAnnotation`}.\n2. The client invoked {@link DeleteAnnotationEntryParams |\n `deleteAnnotationEntry`} on the last remaining entry in the\n annotation — the protocol collapses the annotation rather than\n leaving an empty one behind.", + "description": "Remove an {@link Annotation} from the channel by its id.\n\nDispatched to delete an entire annotation and every entry it contains.\nBecause the protocol forbids empty annotations, a client that wants to\nremove the last remaining entry dispatches this action — collapsing the\nannotation — rather than {@link AnnotationsEntryRemovedAction}.", "properties": { "type": { "$ref": "#/$defs/ActionType.AnnotationsRemoved" @@ -6829,7 +6622,7 @@ }, "AnnotationsEntrySetAction": { "type": "object", - "description": "Upsert an {@link AnnotationEntry} within an existing annotation — adds a\nnew entry, or replaces one identified by {@link AnnotationEntry.id}. If\n{@link annotationId} does not match any current annotation the action is\na no-op.", + "description": "Upsert an {@link AnnotationEntry} within an existing annotation — adds a\nnew entry, or replaces one identified by {@link AnnotationEntry.id}. The\ndispatching client assigns the {@link AnnotationEntry.id} of a new entry.\nIf {@link annotationId} does not match any current annotation the action\nis a no-op.", "properties": { "type": { "$ref": "#/$defs/ActionType.AnnotationsEntrySet" @@ -6851,7 +6644,7 @@ }, "AnnotationsEntryRemovedAction": { "type": "object", - "description": "Remove a single {@link AnnotationEntry} from an annotation without\ncollapsing the annotation itself. Used when more than one entry remains\n— the server MUST dispatch {@link AnnotationsRemovedAction} instead when\nremoving the last entry would otherwise leave the annotation empty.\n\nIf either {@link annotationId} or {@link entryId} does not match the\ncurrent state the action is a no-op.", + "description": "Remove a single {@link AnnotationEntry} from an annotation without\ncollapsing the annotation itself. Used when more than one entry remains —\nto remove the last entry a client dispatches {@link AnnotationsRemovedAction}\ninstead, since the protocol forbids empty annotations.\n\nIf either {@link annotationId} or {@link entryId} does not match the\ncurrent state the action is a no-op.", "properties": { "type": { "$ref": "#/$defs/ActionType.AnnotationsEntryRemoved" diff --git a/schema/errors.schema.json b/schema/errors.schema.json index eec8361e..d3329a2b 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -3864,11 +3864,11 @@ }, "Annotation": { "type": "object", - "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. The\nserver enforces this invariant: {@link CreateAnnotationParams |\n`createAnnotation`} requires an initial entry, and deleting the\nlast remaining entry collapses the annotation into a\n{@link AnnotationsRemovedAction} rather than leaving an empty annotation\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. An\n{@link AnnotationsSetAction} that creates an annotation therefore carries\nits mandatory first entry, and removing the last remaining entry collapses\nthe annotation via {@link AnnotationsRemovedAction} rather than leaving an\nempty annotation behind.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the annotations channel. Server-assigned." + "description": "Stable identifier within the annotations channel. Assigned by the client\nthat dispatches the creating {@link AnnotationsSetAction}." }, "turnId": { "type": "string", @@ -3884,7 +3884,7 @@ }, "resolved": { "type": "boolean", - "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}." + "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) by dispatching an {@link AnnotationsSetAction} carrying the\nupdated flag." }, "entries": { "type": "array", @@ -3896,7 +3896,7 @@ "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." } }, "required": [ @@ -3913,7 +3913,7 @@ "properties": { "id": { "type": "string", - "description": "Stable identifier within the enclosing annotation. Server-assigned." + "description": "Stable identifier within the enclosing annotation. Assigned by the client\nthat dispatches the {@link AnnotationsEntrySetAction} (or the enclosing\n{@link AnnotationsSetAction}) introducing the entry." }, "text": { "$ref": "#/$defs/StringOrMarkdown", @@ -3922,7 +3922,7 @@ "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." } }, "required": [ @@ -3930,24 +3930,6 @@ "text" ] }, - "NewAnnotationEntry": { - "type": "object", - "description": "Input shape passed to {@link CreateAnnotationParams | `createAnnotation`}\nand {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server\nassigns the resulting {@link AnnotationEntry.id}.", - "properties": { - "text": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Entry body. See {@link AnnotationEntry.text}." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link AnnotationEntry._meta}." - } - }, - "required": [ - "text" - ] - }, "TelemetryCapabilities": { "type": "object", "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", @@ -5137,195 +5119,6 @@ } } }, - "CreateAnnotationParams": { - "type": "object", - "description": "Create a new {@link Annotation} anchored to a file from a specific\nturn, optionally narrowed to a range within that file.\n\nThe initial entry is required — the protocol forbids empty annotations,\nso annotation creation and first-entry creation are fused into one\ncommand. The created annotation always starts unresolved\n({@link Annotation.resolved} is `false`). The server assigns both\n{@link CreateAnnotationResult.annotationId} and\n{@link CreateAnnotationResult.entryId}, then broadcasts an\n{@link AnnotationsSetAction} on the channel.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI, e.g. `ahp-session://annotations`." - }, - "turnId": { - "type": "string", - "description": "Turn whose file versions {@link resource} + {@link range} address." - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "Anchored file URI." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Anchored range within {@link resource}. When omitted the annotation is\nanchored to the entire file." - }, - "entry": { - "$ref": "#/$defs/NewAnnotationEntry", - "description": "First entry in the annotation. The server assigns its {@link AnnotationEntry.id}." - } - }, - "required": [ - "channel", - "turnId", - "resource", - "entry" - ] - }, - "CreateAnnotationResult": { - "type": "object", - "description": "Result of {@link CreateAnnotationParams | `createAnnotation`}.", - "properties": { - "annotationId": { - "type": "string", - "description": "Server-assigned {@link Annotation.id}." - }, - "entryId": { - "type": "string", - "description": "Server-assigned {@link AnnotationEntry.id} of the initial entry." - } - }, - "required": [ - "annotationId", - "entryId" - ] - }, - "UpdateAnnotationParams": { - "type": "object", - "description": "Re-anchor or resolve an existing {@link Annotation} — typically used\nto re-pin an annotation to a different range or a newer turn after an\nedit, or to mark the annotation {@link Annotation.resolved | resolved}\n(or re-open it). Entries themselves are not modified by this command;\nuse {@link AddAnnotationEntryParams | `addAnnotationEntry`},\n{@link EditAnnotationEntryParams | `editAnnotationEntry`}, or\n{@link DeleteAnnotationEntryParams | `deleteAnnotationEntry`} for that.\n\nOmitted optional fields preserve their current value. The server\nechoes the resulting annotation state as an {@link AnnotationsSetAction}.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI." - }, - "annotationId": { - "type": "string", - "description": "The {@link Annotation.id} to update." - }, - "turnId": { - "type": "string", - "description": "New {@link Annotation.turnId}, if changing." - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "New anchored file URI, if changing." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "New anchored range, if changing." - }, - "resolved": { - "type": "boolean", - "description": "New {@link Annotation.resolved} state, if changing." - } - }, - "required": [ - "channel", - "annotationId" - ] - }, - "DeleteAnnotationParams": { - "type": "object", - "description": "Delete an entire annotation (and every entry it contains). The\nserver echoes an {@link AnnotationsRemovedAction} on the channel.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI." - }, - "annotationId": { - "type": "string", - "description": "The {@link Annotation.id} to delete." - } - }, - "required": [ - "channel", - "annotationId" - ] - }, - "AddAnnotationEntryParams": { - "type": "object", - "description": "Append a new {@link AnnotationEntry} to an existing annotation. The\nserver assigns the resulting {@link AnnotationEntry.id} and echoes an\n{@link AnnotationsEntrySetAction}.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI." - }, - "annotationId": { - "type": "string", - "description": "Annotation that receives the new entry." - }, - "entry": { - "$ref": "#/$defs/NewAnnotationEntry", - "description": "Entry payload — the server assigns the id." - } - }, - "required": [ - "channel", - "annotationId", - "entry" - ] - }, - "AddAnnotationEntryResult": { - "type": "object", - "description": "Result of {@link AddAnnotationEntryParams | `addAnnotationEntry`}.", - "properties": { - "entryId": { - "type": "string", - "description": "Server-assigned {@link AnnotationEntry.id} of the new entry." - } - }, - "required": [ - "entryId" - ] - }, - "EditAnnotationEntryParams": { - "type": "object", - "description": "Edit the body of an existing entry in place. The server echoes an\n{@link AnnotationsEntrySetAction} carrying the updated entry.\n\nOnly the body is mutable through this command; to change\n{@link AnnotationEntry._meta} delete and re-create the entry.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI." - }, - "annotationId": { - "type": "string", - "description": "Enclosing annotation." - }, - "entryId": { - "type": "string", - "description": "{@link AnnotationEntry.id} to edit." - }, - "text": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "New entry body. See {@link AnnotationEntry.text}." - } - }, - "required": [ - "channel", - "annotationId", - "entryId", - "text" - ] - }, - "DeleteAnnotationEntryParams": { - "type": "object", - "description": "Remove a single entry from an annotation.\n\nIf the removal would leave the annotation empty (i.e. the targeted entry\nis the only one remaining), the server collapses the annotation instead\n— it dispatches an {@link AnnotationsRemovedAction} and the annotation\ndisappears from {@link AnnotationsState.annotations}. Otherwise the server\nechoes an {@link AnnotationsEntryRemovedAction}.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI." - }, - "annotationId": { - "type": "string", - "description": "Enclosing annotation." - }, - "entryId": { - "type": "string", - "description": "{@link AnnotationEntry.id} to remove." - } - }, - "required": [ - "channel", - "annotationId", - "entryId" - ] - }, "CreateResourceWatchParams": { "type": "object", "description": "Creates a resource watcher on the receiver's filesystem.\n\nThe receiver allocates an `ahp-resource-watch:/` channel URI and\nreturns it on {@link CreateResourceWatchResult.channel}. The caller then\n[`subscribe`](./subscriptions)s to that channel to receive\n`resourceWatch/changed` actions over the standard action envelope.\n\nThe watch lifecycle is tied to subscription: when every subscriber has\nunsubscribed (or the underlying connection drops), the receiver MUST\nrelease the watcher. There is no explicit dispose command — `unsubscribe`\nis the only handle the caller needs.\n\nLike the rest of the `resource*` family, `createResourceWatch` is\nsymmetrical and MAY be sent in either direction. Access is gated through\nthe same permission flow as `resourceRead`/`resourceWrite`.", diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index ea2b104c..56e16fc9 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -3993,11 +3993,11 @@ }, "Annotation": { "type": "object", - "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. The\nserver enforces this invariant: {@link CreateAnnotationParams |\n`createAnnotation`} requires an initial entry, and deleting the\nlast remaining entry collapses the annotation into a\n{@link AnnotationsRemovedAction} rather than leaving an empty annotation\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. An\n{@link AnnotationsSetAction} that creates an annotation therefore carries\nits mandatory first entry, and removing the last remaining entry collapses\nthe annotation via {@link AnnotationsRemovedAction} rather than leaving an\nempty annotation behind.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the annotations channel. Server-assigned." + "description": "Stable identifier within the annotations channel. Assigned by the client\nthat dispatches the creating {@link AnnotationsSetAction}." }, "turnId": { "type": "string", @@ -4013,7 +4013,7 @@ }, "resolved": { "type": "boolean", - "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}." + "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) by dispatching an {@link AnnotationsSetAction} carrying the\nupdated flag." }, "entries": { "type": "array", @@ -4025,7 +4025,7 @@ "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." } }, "required": [ @@ -4042,7 +4042,7 @@ "properties": { "id": { "type": "string", - "description": "Stable identifier within the enclosing annotation. Server-assigned." + "description": "Stable identifier within the enclosing annotation. Assigned by the client\nthat dispatches the {@link AnnotationsEntrySetAction} (or the enclosing\n{@link AnnotationsSetAction}) introducing the entry." }, "text": { "$ref": "#/$defs/StringOrMarkdown", @@ -4051,7 +4051,7 @@ "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." } }, "required": [ @@ -4059,24 +4059,6 @@ "text" ] }, - "NewAnnotationEntry": { - "type": "object", - "description": "Input shape passed to {@link CreateAnnotationParams | `createAnnotation`}\nand {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server\nassigns the resulting {@link AnnotationEntry.id}.", - "properties": { - "text": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Entry body. See {@link AnnotationEntry.text}." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link AnnotationEntry._meta}." - } - }, - "required": [ - "text" - ] - }, "TelemetryCapabilities": { "type": "object", "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", diff --git a/schema/state.schema.json b/schema/state.schema.json index 98877c61..52b6e5f4 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -3775,11 +3775,11 @@ }, "Annotation": { "type": "object", - "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. The\nserver enforces this invariant: {@link CreateAnnotationParams |\n`createAnnotation`} requires an initial entry, and deleting the\nlast remaining entry collapses the annotation into a\n{@link AnnotationsRemovedAction} rather than leaving an empty annotation\nbehind.", + "description": "A conversation anchored to a specific file produced by a specific turn,\noptionally narrowed to a range within that file.\n\n{@link turnId} anchors the annotation to the file versions that turn\nproduced, so a later turn that rewrites the same file does not silently\ninvalidate the annotation's anchor — clients can resolve {@link resource}\nand {@link range} against the turn's changeset. When {@link range} is\nomitted the annotation is anchored to the entire file.\n\nEvery annotation MUST contain at least one {@link AnnotationEntry}. An\n{@link AnnotationsSetAction} that creates an annotation therefore carries\nits mandatory first entry, and removing the last remaining entry collapses\nthe annotation via {@link AnnotationsRemovedAction} rather than leaving an\nempty annotation behind.", "properties": { "id": { "type": "string", - "description": "Stable identifier within the annotations channel. Server-assigned." + "description": "Stable identifier within the annotations channel. Assigned by the client\nthat dispatches the creating {@link AnnotationsSetAction}." }, "turnId": { "type": "string", @@ -3795,7 +3795,7 @@ }, "resolved": { "type": "boolean", - "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}." + "description": "Whether the annotation has been resolved. Newly created annotations are\nalways unresolved (`false`); a client marks an annotation resolved (or\nre-opens it) by dispatching an {@link AnnotationsSetAction} carrying the\nupdated flag." }, "entries": { "type": "array", @@ -3807,7 +3807,7 @@ "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." } }, "required": [ @@ -3824,7 +3824,7 @@ "properties": { "id": { "type": "string", - "description": "Stable identifier within the enclosing annotation. Server-assigned." + "description": "Stable identifier within the enclosing annotation. Assigned by the client\nthat dispatches the {@link AnnotationsEntrySetAction} (or the enclosing\n{@link AnnotationsSetAction}) introducing the entry." }, "text": { "$ref": "#/$defs/StringOrMarkdown", @@ -3833,7 +3833,7 @@ "_meta": { "type": "object", "additionalProperties": {}, - "description": "Server-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." } }, "required": [ @@ -3841,24 +3841,6 @@ "text" ] }, - "NewAnnotationEntry": { - "type": "object", - "description": "Input shape passed to {@link CreateAnnotationParams | `createAnnotation`}\nand {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server\nassigns the resulting {@link AnnotationEntry.id}.", - "properties": { - "text": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Entry body. See {@link AnnotationEntry.text}." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Server-defined opaque metadata, forwarded onto the resulting\n{@link AnnotationEntry._meta}." - } - }, - "required": [ - "text" - ] - }, "TelemetryCapabilities": { "type": "object", "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index aad3fe3e..0945e6cd 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -743,7 +743,6 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'AnnotationsState' }, { name: 'Annotation' }, { name: 'AnnotationEntry' }, - { name: 'NewAnnotationEntry' }, { name: 'TelemetryCapabilities' }, { name: 'ResourceWatchState' }, { name: 'ResourceChange' }, @@ -1286,10 +1285,6 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: str { name: 'CompletionsParams' }, { name: 'CompletionItem' }, { name: 'CompletionsResult' }, { name: 'InvokeChangesetOperationParams' }, { name: 'InvokeChangesetOperationResult' }, { name: 'ChangesetOperationFollowUp' }, - { name: 'CreateAnnotationParams' }, { name: 'CreateAnnotationResult' }, - { name: 'UpdateAnnotationParams' }, { name: 'DeleteAnnotationParams' }, - { name: 'AddAnnotationEntryParams' }, { name: 'AddAnnotationEntryResult' }, - { name: 'EditAnnotationEntryParams' }, { name: 'DeleteAnnotationEntryParams' }, ]; const RECONNECT_RESULT_UNION: UnionConfig = { diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 6ef01194..09df368a 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -804,7 +804,7 @@ const STATE_STRUCTS = [ 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', 'Changeset', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', - 'AnnotationsSummary', 'AnnotationsState', 'Annotation', 'AnnotationEntry', 'NewAnnotationEntry', + 'AnnotationsSummary', 'AnnotationsState', 'Annotation', 'AnnotationEntry', 'TelemetryCapabilities', 'ResourceWatchState', 'ResourceChange', ]; @@ -1273,12 +1273,6 @@ const COMMAND_STRUCTS = [ 'SessionConfigValueItem', 'CompletionsParams', 'CompletionItem', 'CompletionsResult', 'InvokeChangesetOperationParams', 'InvokeChangesetOperationResult', - 'CreateAnnotationParams', 'CreateAnnotationResult', - 'UpdateAnnotationParams', - 'DeleteAnnotationParams', - 'AddAnnotationEntryParams', 'AddAnnotationEntryResult', - 'EditAnnotationEntryParams', - 'DeleteAnnotationEntryParams', 'ChangesetOperationFollowUp', ]; @@ -1669,24 +1663,6 @@ object AhpCommands { fun invokeChangesetOperation(id: Long, params: InvokeChangesetOperationParams): JsonRpcRequest = JsonRpcRequest(id = id, method = "invokeChangesetOperation", params = params) - - fun createAnnotation(id: Long, params: CreateAnnotationParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "createAnnotation", params = params) - - fun updateAnnotation(id: Long, params: UpdateAnnotationParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "updateAnnotation", params = params) - - fun deleteAnnotation(id: Long, params: DeleteAnnotationParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "deleteAnnotation", params = params) - - fun addAnnotationEntry(id: Long, params: AddAnnotationEntryParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "addAnnotationEntry", params = params) - - fun editAnnotationEntry(id: Long, params: EditAnnotationEntryParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "editAnnotationEntry", params = params) - - fun deleteAnnotationEntry(id: Long, params: DeleteAnnotationEntryParams): JsonRpcRequest = - JsonRpcRequest(id = id, method = "deleteAnnotationEntry", params = params) } /** diff --git a/scripts/generate-markdown.ts b/scripts/generate-markdown.ts index da05e715..5e83baef 100644 --- a/scripts/generate-markdown.ts +++ b/scripts/generate-markdown.ts @@ -1012,7 +1012,7 @@ function generateAnnotationsChannelPage(project: Project): string { const lines: string[] = [GENERATED_HEADER]; lines.push('# Annotations Channel\n'); - lines.push('Reference for the `ahp-session://annotations` channel — server-owned annotations anchored to file ranges within a session turn. Clients mutate annotations through commands; servers echo state changes as annotations actions.\n'); + lines.push('Reference for the `ahp-session://annotations` channel — per-session annotations anchored to file ranges within a session turn. Clients (and the agent host) mutate annotations by dispatching the client-dispatchable `annotations/*` state actions, which the write-ahead reducer applies identically on both peers.\n'); lines.push(schemaLink('state.schema.json')); if (stateSf) { diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index e20f9f02..e1c418e8 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -643,7 +643,6 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'AnnotationsState' }, { name: 'Annotation' }, { name: 'AnnotationEntry' }, - { name: 'NewAnnotationEntry' }, { name: 'TelemetryCapabilities' }, { name: 'ResourceWatchState' }, { name: 'ResourceChange' }, @@ -1142,12 +1141,6 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: s { name: 'CompletionsParams' }, { name: 'CompletionItem' }, { name: 'CompletionsResult' }, { name: 'InvokeChangesetOperationParams' }, { name: 'InvokeChangesetOperationResult' }, { name: 'ChangesetOperationFollowUp' }, - { name: 'CreateAnnotationParams' }, { name: 'CreateAnnotationResult' }, - { name: 'UpdateAnnotationParams' }, - { name: 'DeleteAnnotationParams' }, - { name: 'AddAnnotationEntryParams' }, { name: 'AddAnnotationEntryResult' }, - { name: 'EditAnnotationEntryParams' }, - { name: 'DeleteAnnotationEntryParams' }, ]; const RECONNECT_RESULT_UNION: UnionConfig = { @@ -1165,7 +1158,7 @@ function generateCommandsFile(project: Project): string { lines.push('#[allow(unused_imports)]'); lines.push('use crate::actions::{ActionEnvelope, StateAction};'); lines.push('#[allow(unused_imports)]'); - lines.push('use crate::state::{AgentSelection, ContentRef, MessageAttachment, ModelSelection, NewAnnotationEntry, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, TextRange, Turn};'); + lines.push('use crate::state::{AgentSelection, ContentRef, MessageAttachment, ModelSelection, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, TextRange, Turn};'); lines.push(''); lines.push('// ─── Enums ────────────────────────────────────────────────────────────\n'); diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index 22b3fd0b..5f54394b 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -557,7 +557,7 @@ const STATE_STRUCTS = [ 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', 'Changeset', 'ChangesetState', 'ChangesetFile', 'ChangesetOperation', - 'AnnotationsSummary', 'AnnotationsState', 'Annotation', 'AnnotationEntry', 'NewAnnotationEntry', + 'AnnotationsSummary', 'AnnotationsState', 'Annotation', 'AnnotationEntry', 'TelemetryCapabilities', 'ResourceWatchState', 'ResourceChange', ]; @@ -1135,12 +1135,6 @@ const COMMAND_STRUCTS = [ 'SessionConfigValueItem', 'CompletionsParams', 'CompletionItem', 'CompletionsResult', 'InvokeChangesetOperationParams', 'InvokeChangesetOperationResult', - 'CreateAnnotationParams', 'CreateAnnotationResult', - 'UpdateAnnotationParams', - 'DeleteAnnotationParams', - 'AddAnnotationEntryParams', 'AddAnnotationEntryResult', - 'EditAnnotationEntryParams', - 'DeleteAnnotationEntryParams', 'ChangesetOperationFollowUp', ]; diff --git a/types/action-origin.generated.ts b/types/action-origin.generated.ts index 33473692..ac12bc23 100644 --- a/types/action-origin.generated.ts +++ b/types/action-origin.generated.ts @@ -259,17 +259,17 @@ export type AnnotationsAction = /** Union of annotations actions that clients may dispatch. */ export type ClientAnnotationsAction = - never -; - -/** Union of annotations actions that only the server may produce. */ -export type ServerAnnotationsAction = | AnnotationsSetAction | AnnotationsRemovedAction | AnnotationsEntrySetAction | AnnotationsEntryRemovedAction ; +/** Union of annotations actions that only the server may produce. */ +export type ServerAnnotationsAction = + never +; + /** Union of all resource-watch-scoped actions. */ export type ResourceWatchAction = | ResourceWatchChangedAction @@ -343,10 +343,10 @@ export const IS_CLIENT_DISPATCHABLE: { readonly [K in StateAction['type']]: bool [ActionType.ChangesetOperationsChanged]: false, [ActionType.ChangesetOperationStatusChanged]: false, [ActionType.ChangesetCleared]: false, - [ActionType.AnnotationsSet]: false, - [ActionType.AnnotationsRemoved]: false, - [ActionType.AnnotationsEntrySet]: false, - [ActionType.AnnotationsEntryRemoved]: false, + [ActionType.AnnotationsSet]: true, + [ActionType.AnnotationsRemoved]: true, + [ActionType.AnnotationsEntrySet]: true, + [ActionType.AnnotationsEntryRemoved]: true, [ActionType.TerminalData]: false, [ActionType.TerminalInput]: true, [ActionType.TerminalResized]: true, diff --git a/types/channels-annotations/actions.ts b/types/channels-annotations/actions.ts index c8df1e6c..8a958df9 100644 --- a/types/channels-annotations/actions.ts +++ b/types/channels-annotations/actions.ts @@ -2,11 +2,14 @@ * Annotations Channel Actions — Mutations of an `ahp-session://annotations` * channel's state. * - * Every annotations action is server-only: clients drive mutations through - * the {@link CreateAnnotationParams | `createAnnotation`}, - * {@link AddAnnotationEntryParams | `addAnnotationEntry`}, etc. commands, and - * the server echoes the resulting state change as one of these actions. - * Mirrors the shape of the `changeset/*` action family. + * Every annotations action is client-dispatchable: rather than issuing + * imperative RPC commands, clients drive mutations by dispatching these + * actions directly — assigning the {@link Annotation.id} / + * {@link AnnotationEntry.id} themselves, applying the action optimistically + * through the write-ahead reducer, and letting the server echo it back on the + * normal `action` envelope stream. The server MAY also originate them (e.g. an + * agent leaving an annotation of its own). Mirrors the shape of the + * `changeset/*` action family. * * @module channels-annotations/actions */ @@ -19,13 +22,19 @@ import type { AnnotationEntry, Annotation } from './state.js'; /** * Upsert an {@link Annotation} in the annotations channel — adds a new * annotation, or replaces an existing one identified by - * {@link Annotation.id}. When replacing, the full annotation payload - * (including its {@link Annotation.entries | entries} list) is - * substituted; producers SHOULD prefer {@link AnnotationsEntrySetAction} - * for per-entry edits to keep wire updates small. + * {@link Annotation.id}. + * + * Dispatched by a client to create an annotation (together with its + * mandatory first entry) or to re-anchor / resolve an existing one; the + * dispatching client assigns the {@link Annotation.id} and the id of any + * new entry. When replacing, the full annotation payload (including its + * {@link Annotation.entries | entries} list) is substituted; producers + * SHOULD prefer {@link AnnotationsEntrySetAction} for per-entry edits to + * keep wire updates small. * * @category Annotations Actions * @version 3 + * @clientDispatchable */ export interface AnnotationsSetAction { type: ActionType.AnnotationsSet; @@ -36,16 +45,14 @@ export interface AnnotationsSetAction { /** * Remove an {@link Annotation} from the channel by its id. * - * The server emits this in two cases: - * 1. The client explicitly invoked - * {@link DeleteAnnotationParams | `deleteAnnotation`}. - * 2. The client invoked {@link DeleteAnnotationEntryParams | - * `deleteAnnotationEntry`} on the last remaining entry in the - * annotation — the protocol collapses the annotation rather than - * leaving an empty one behind. + * Dispatched to delete an entire annotation and every entry it contains. + * Because the protocol forbids empty annotations, a client that wants to + * remove the last remaining entry dispatches this action — collapsing the + * annotation — rather than {@link AnnotationsEntryRemovedAction}. * * @category Annotations Actions * @version 3 + * @clientDispatchable */ export interface AnnotationsRemovedAction { type: ActionType.AnnotationsRemoved; @@ -55,12 +62,14 @@ export interface AnnotationsRemovedAction { /** * Upsert an {@link AnnotationEntry} within an existing annotation — adds a - * new entry, or replaces one identified by {@link AnnotationEntry.id}. If - * {@link annotationId} does not match any current annotation the action is - * a no-op. + * new entry, or replaces one identified by {@link AnnotationEntry.id}. The + * dispatching client assigns the {@link AnnotationEntry.id} of a new entry. + * If {@link annotationId} does not match any current annotation the action + * is a no-op. * * @category Annotations Actions * @version 3 + * @clientDispatchable */ export interface AnnotationsEntrySetAction { type: ActionType.AnnotationsEntrySet; @@ -72,15 +81,16 @@ export interface AnnotationsEntrySetAction { /** * Remove a single {@link AnnotationEntry} from an annotation without - * collapsing the annotation itself. Used when more than one entry remains - * — the server MUST dispatch {@link AnnotationsRemovedAction} instead when - * removing the last entry would otherwise leave the annotation empty. + * collapsing the annotation itself. Used when more than one entry remains — + * to remove the last entry a client dispatches {@link AnnotationsRemovedAction} + * instead, since the protocol forbids empty annotations. * * If either {@link annotationId} or {@link entryId} does not match the * current state the action is a no-op. * * @category Annotations Actions * @version 3 + * @clientDispatchable */ export interface AnnotationsEntryRemovedAction { type: ActionType.AnnotationsEntryRemoved; diff --git a/types/channels-annotations/commands.ts b/types/channels-annotations/commands.ts deleted file mode 100644 index d4636399..00000000 --- a/types/channels-annotations/commands.ts +++ /dev/null @@ -1,203 +0,0 @@ -/** - * Annotations Channel Commands — Client-driven mutations of an - * `ahp-session://annotations` channel. - * - * The protocol forbids empty annotations, so annotation and first-entry - * creation are fused into {@link CreateAnnotationParams | - * `createAnnotation`} and the server collapses an annotation whose last - * entry is deleted into an {@link AnnotationsRemovedAction}. Every - * accepted command echoes back through the normal `annotations/*` action - * stream on the channel. - * - * @module channels-annotations/commands - */ - -import type { URI, StringOrMarkdown, TextRange } from '../common/state.js'; -import type { BaseParams } from '../common/commands.js'; -import type { NewAnnotationEntry } from './state.js'; - -// ─── createAnnotation ───────────────────────────────────────────────────── - -/** - * Create a new {@link Annotation} anchored to a file from a specific - * turn, optionally narrowed to a range within that file. - * - * The initial entry is required — the protocol forbids empty annotations, - * so annotation creation and first-entry creation are fused into one - * command. The created annotation always starts unresolved - * ({@link Annotation.resolved} is `false`). The server assigns both - * {@link CreateAnnotationResult.annotationId} and - * {@link CreateAnnotationResult.entryId}, then broadcasts an - * {@link AnnotationsSetAction} on the channel. - * - * @category Commands - * @method createAnnotation - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface CreateAnnotationParams extends BaseParams { - /** The annotations channel URI, e.g. `ahp-session://annotations`. */ - channel: URI; - /** Turn whose file versions {@link resource} + {@link range} address. */ - turnId: string; - /** Anchored file URI. */ - resource: URI; - /** - * Anchored range within {@link resource}. When omitted the annotation is - * anchored to the entire file. - */ - range?: TextRange; - /** First entry in the annotation. The server assigns its {@link AnnotationEntry.id}. */ - entry: NewAnnotationEntry; -} - -/** - * Result of {@link CreateAnnotationParams | `createAnnotation`}. - * - * @category Commands - */ -export interface CreateAnnotationResult { - /** Server-assigned {@link Annotation.id}. */ - annotationId: string; - /** Server-assigned {@link AnnotationEntry.id} of the initial entry. */ - entryId: string; -} - -// ─── updateAnnotation ───────────────────────────────────────────────────── - -/** - * Re-anchor or resolve an existing {@link Annotation} — typically used - * to re-pin an annotation to a different range or a newer turn after an - * edit, or to mark the annotation {@link Annotation.resolved | resolved} - * (or re-open it). Entries themselves are not modified by this command; - * use {@link AddAnnotationEntryParams | `addAnnotationEntry`}, - * {@link EditAnnotationEntryParams | `editAnnotationEntry`}, or - * {@link DeleteAnnotationEntryParams | `deleteAnnotationEntry`} for that. - * - * Omitted optional fields preserve their current value. The server - * echoes the resulting annotation state as an {@link AnnotationsSetAction}. - * - * @category Commands - * @method updateAnnotation - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface UpdateAnnotationParams extends BaseParams { - /** The annotations channel URI. */ - channel: URI; - /** The {@link Annotation.id} to update. */ - annotationId: string; - /** New {@link Annotation.turnId}, if changing. */ - turnId?: string; - /** New anchored file URI, if changing. */ - resource?: URI; - /** New anchored range, if changing. */ - range?: TextRange; - /** New {@link Annotation.resolved} state, if changing. */ - resolved?: boolean; -} - -// ─── deleteAnnotation ───────────────────────────────────────────────────── - -/** - * Delete an entire annotation (and every entry it contains). The - * server echoes an {@link AnnotationsRemovedAction} on the channel. - * - * @category Commands - * @method deleteAnnotation - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface DeleteAnnotationParams extends BaseParams { - /** The annotations channel URI. */ - channel: URI; - /** The {@link Annotation.id} to delete. */ - annotationId: string; -} - -// ─── addAnnotationEntry ────────────────────────────────────────────────────────────── - -/** - * Append a new {@link AnnotationEntry} to an existing annotation. The - * server assigns the resulting {@link AnnotationEntry.id} and echoes an - * {@link AnnotationsEntrySetAction}. - * - * @category Commands - * @method addAnnotationEntry - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface AddAnnotationEntryParams extends BaseParams { - /** The annotations channel URI. */ - channel: URI; - /** Annotation that receives the new entry. */ - annotationId: string; - /** Entry payload — the server assigns the id. */ - entry: NewAnnotationEntry; -} - -/** - * Result of {@link AddAnnotationEntryParams | `addAnnotationEntry`}. - * - * @category Commands - */ -export interface AddAnnotationEntryResult { - /** Server-assigned {@link AnnotationEntry.id} of the new entry. */ - entryId: string; -} - -// ─── editAnnotationEntry ───────────────────────────────────────────────────────────── - -/** - * Edit the body of an existing entry in place. The server echoes an - * {@link AnnotationsEntrySetAction} carrying the updated entry. - * - * Only the body is mutable through this command; to change - * {@link AnnotationEntry._meta} delete and re-create the entry. - * - * @category Commands - * @method editAnnotationEntry - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface EditAnnotationEntryParams extends BaseParams { - /** The annotations channel URI. */ - channel: URI; - /** Enclosing annotation. */ - annotationId: string; - /** {@link AnnotationEntry.id} to edit. */ - entryId: string; - /** New entry body. See {@link AnnotationEntry.text}. */ - text: StringOrMarkdown; -} - -// ─── deleteAnnotationEntry ─────────────────────────────────────────────────────────── - -/** - * Remove a single entry from an annotation. - * - * If the removal would leave the annotation empty (i.e. the targeted entry - * is the only one remaining), the server collapses the annotation instead - * — it dispatches an {@link AnnotationsRemovedAction} and the annotation - * disappears from {@link AnnotationsState.annotations}. Otherwise the server - * echoes an {@link AnnotationsEntryRemovedAction}. - * - * @category Commands - * @method deleteAnnotationEntry - * @direction Client → Server - * @messageType Request - * @version 3 - */ -export interface DeleteAnnotationEntryParams extends BaseParams { - /** The annotations channel URI. */ - channel: URI; - /** Enclosing annotation. */ - annotationId: string; - /** {@link AnnotationEntry.id} to remove. */ - entryId: string; -} diff --git a/types/channels-annotations/reducer.ts b/types/channels-annotations/reducer.ts index d7bde323..f39f96b7 100644 --- a/types/channels-annotations/reducer.ts +++ b/types/channels-annotations/reducer.ts @@ -13,14 +13,15 @@ import { softAssertNever } from '../common/reducer-helpers.js'; * Pure reducer for annotations state. Handles every {@link AnnotationsAction} * variant. * - * Per the spec, every annotations action is server-only. The reducer + * Per the spec, every annotations action is client-dispatchable; the reducer + * runs identically on the client (optimistic, write-ahead) and the server. It * preserves the dispatch order of annotations (and of entries within an * annotation): new entries are appended; `*Set` actions with a matching id * replace in place, while actions whose target id is unknown are no-ops * (mirroring `changeset/fileRemoved` semantics). The single-entry - * minimum invariant is enforced by the server, not the reducer — a - * malformed server that removes an annotation's last entry via - * {@link AnnotationsEntryRemovedAction} would leave an empty annotation, + * minimum invariant is enforced by producers, not the reducer — removing an + * annotation's last entry via {@link AnnotationsEntryRemovedAction} (instead + * of {@link AnnotationsRemovedAction}) would leave an empty annotation, * which is observable but not catastrophic. */ export function annotationsReducer(state: AnnotationsState, action: AnnotationsAction, log?: (msg: string) => void): AnnotationsState { diff --git a/types/channels-annotations/state.ts b/types/channels-annotations/state.ts index d05a0d4e..2fbeb699 100644 --- a/types/channels-annotations/state.ts +++ b/types/channels-annotations/state.ts @@ -59,17 +59,19 @@ export interface AnnotationsState { * and {@link range} against the turn's changeset. When {@link range} is * omitted the annotation is anchored to the entire file. * - * Every annotation MUST contain at least one {@link AnnotationEntry}. The - * server enforces this invariant: {@link CreateAnnotationParams | - * `createAnnotation`} requires an initial entry, and deleting the - * last remaining entry collapses the annotation into a - * {@link AnnotationsRemovedAction} rather than leaving an empty annotation - * behind. + * Every annotation MUST contain at least one {@link AnnotationEntry}. An + * {@link AnnotationsSetAction} that creates an annotation therefore carries + * its mandatory first entry, and removing the last remaining entry collapses + * the annotation via {@link AnnotationsRemovedAction} rather than leaving an + * empty annotation behind. * * @category Annotations */ export interface Annotation { - /** Stable identifier within the annotations channel. Server-assigned. */ + /** + * Stable identifier within the annotations channel. Assigned by the client + * that dispatches the creating {@link AnnotationsSetAction}. + */ id: string; /** * Turn that produced the file versions this annotation is anchored to. @@ -86,7 +88,8 @@ export interface Annotation { /** * Whether the annotation has been resolved. Newly created annotations are * always unresolved (`false`); a client marks an annotation resolved (or - * re-opens it) through {@link UpdateAnnotationParams | `updateAnnotation`}. + * re-opens it) by dispatching an {@link AnnotationsSetAction} carrying the + * updated flag. */ resolved: boolean; /** @@ -95,7 +98,7 @@ export interface Annotation { */ entries: AnnotationEntry[]; /** - * Server-defined opaque metadata, surfaced to tooling but not + * Producer-defined opaque metadata, surfaced to tooling but not * interpreted by the protocol. */ _meta?: Record; @@ -109,7 +112,11 @@ export interface Annotation { * @category Annotations */ export interface AnnotationEntry { - /** Stable identifier within the enclosing annotation. Server-assigned. */ + /** + * Stable identifier within the enclosing annotation. Assigned by the client + * that dispatches the {@link AnnotationsEntrySetAction} (or the enclosing + * {@link AnnotationsSetAction}) introducing the entry. + */ id: string; /** * Entry body. A bare `string` is rendered as plain text; pass @@ -118,27 +125,8 @@ export interface AnnotationEntry { */ text: StringOrMarkdown; /** - * Server-defined opaque metadata, surfaced to tooling but not + * Producer-defined opaque metadata, surfaced to tooling but not * interpreted by the protocol. */ _meta?: Record; } - -// ─── New Annotation Entry ──────────────────────────────────────────────────── - -/** - * Input shape passed to {@link CreateAnnotationParams | `createAnnotation`} - * and {@link AddAnnotationEntryParams | `addAnnotationEntry`}. The server - * assigns the resulting {@link AnnotationEntry.id}. - * - * @category Annotations - */ -export interface NewAnnotationEntry { - /** Entry body. See {@link AnnotationEntry.text}. */ - text: StringOrMarkdown; - /** - * Server-defined opaque metadata, forwarded onto the resulting - * {@link AnnotationEntry._meta}. - */ - _meta?: Record; -} diff --git a/types/commands.ts b/types/commands.ts index bf82f656..03931e26 100644 --- a/types/commands.ts +++ b/types/commands.ts @@ -12,5 +12,4 @@ export * from './channels-root/commands.js'; export * from './channels-session/commands.js'; export * from './channels-terminal/commands.js'; export * from './channels-changeset/commands.js'; -export * from './channels-annotations/commands.js'; export * from './channels-resource-watch/commands.js'; diff --git a/types/common/messages.ts b/types/common/messages.ts index d33d9cce..f8d8efa4 100644 --- a/types/common/messages.ts +++ b/types/common/messages.ts @@ -66,16 +66,6 @@ import type { InvokeChangesetOperationParams, InvokeChangesetOperationResult, } from '../channels-changeset/commands.js'; -import type { - CreateAnnotationParams, - CreateAnnotationResult, - UpdateAnnotationParams, - DeleteAnnotationParams, - AddAnnotationEntryParams, - AddAnnotationEntryResult, - EditAnnotationEntryParams, - DeleteAnnotationEntryParams, -} from '../channels-annotations/commands.js'; import type { ActionEnvelope } from './actions.js'; import type { @@ -177,12 +167,6 @@ export interface CommandMap { 'sessionConfigCompletions': { params: SessionConfigCompletionsParams; result: SessionConfigCompletionsResult }; 'completions': { params: CompletionsParams; result: CompletionsResult }; 'invokeChangesetOperation': { params: InvokeChangesetOperationParams; result: InvokeChangesetOperationResult }; - 'createAnnotation': { params: CreateAnnotationParams; result: CreateAnnotationResult }; - 'updateAnnotation': { params: UpdateAnnotationParams; result: null }; - 'deleteAnnotation': { params: DeleteAnnotationParams; result: null }; - 'addAnnotationEntry': { params: AddAnnotationEntryParams; result: AddAnnotationEntryResult }; - 'editAnnotationEntry': { params: EditAnnotationEntryParams; result: null }; - 'deleteAnnotationEntry': { params: DeleteAnnotationEntryParams; result: null }; } /** diff --git a/types/index.ts b/types/index.ts index 5d03f192..5a250428 100644 --- a/types/index.ts +++ b/types/index.ts @@ -98,7 +98,6 @@ export type { AnnotationsState, Annotation, AnnotationEntry, - NewAnnotationEntry, TelemetryCapabilities, ResourceWatchState, ResourceChange, @@ -294,14 +293,6 @@ export type { InvokeChangesetOperationResult, ChangesetOperationTarget, ChangesetOperationFollowUp, - CreateAnnotationParams, - CreateAnnotationResult, - UpdateAnnotationParams, - DeleteAnnotationParams, - AddAnnotationEntryParams, - AddAnnotationEntryResult, - EditAnnotationEntryParams, - DeleteAnnotationEntryParams, } from './commands.js'; export { ReconnectResultType, ContentEncoding, CompletionItemKind, ResourceType, ResourceWriteMode } from './commands.js'; diff --git a/types/version/message-checks.ts b/types/version/message-checks.ts index 831b570d..a97ee2f1 100644 --- a/types/version/message-checks.ts +++ b/types/version/message-checks.ts @@ -74,13 +74,7 @@ type _ExpectedCommands = | 'resolveSessionConfig' | 'sessionConfigCompletions' | 'completions' - | 'invokeChangesetOperation' - | 'createAnnotation' - | 'updateAnnotation' - | 'deleteAnnotation' - | 'addAnnotationEntry' - | 'editAnnotationEntry' - | 'deleteAnnotationEntry'; + | 'invokeChangesetOperation'; /** All methods annotated `@messageType Notification` (client → server). */ type _ExpectedClientNotifications =