diff --git a/docs/.changes/20260731-export-all-public-types.json b/docs/.changes/20260731-export-all-public-types.json new file mode 100644 index 00000000..20ced09d --- /dev/null +++ b/docs/.changes/20260731-export-all-public-types.json @@ -0,0 +1,5 @@ +{ + "type": "fixed", + "message": "The package entry point now re-exports every public protocol declaration. 60 were unreachable from it — including the whole customization subsystem (`Customization`, `CustomizationType` and every variant), the whole MCP-server state machine (`McpServerState`, `McpServerStatus`, `McpAuthRequiredReason` and the state variants), 13 action types, and `Icon`, `ChatInteractivity`, `InputRequestResponsePart`, `ToolCallContributor` and `SessionMetadata`.", + "targets": ["typescript"] +} diff --git a/docs/specification/chat-channel.md b/docs/specification/chat-channel.md index a2c8d020..647646ac 100644 --- a/docs/specification/chat-channel.md +++ b/docs/specification/chat-channel.md @@ -14,7 +14,7 @@ Multiple chat channels may be active simultaneously. Clients subscribe to each c ## State -Subscribers receive a [`ChatState`](/reference/chat#chatstate) snapshot. `ChatState` denormalizes the [`ChatSummary`](/reference/chat#chatsummary) fields directly onto itself (`resource`, `title`, `status`, `activity`, `modifiedAt`, `origin`, `workingDirectory`) and adds the conversation contents (history of completed turns, the active turn if any, pending messages, outstanding input requests, and the user's in-progress [`draft`](#drafts)). Producers MUST keep the chat's `ChatSummary` in the session catalog consistent with these inlined summary fields — typically by dispatching a matching [`session/chatUpdated`](/reference/session#actions) whenever any summary field on the chat changes. Refer to the [State Model guide](/guide/state-model) for a structural overview. +Subscribers receive a [`ChatState`](/reference/chat#chatstate) snapshot. `ChatState` denormalizes the [`ChatSummary`](/reference/chat#chatsummary) fields directly onto itself (`resource`, `title`, `status`, `activity`, `modifiedAt`, `origin`, `workingDirectories`) and adds the conversation contents (history of completed turns, the active turn if any, pending messages, outstanding input requests, and the user's in-progress [`draft`](#drafts)). Producers MUST keep the chat's `ChatSummary` in the session catalog consistent with these inlined summary fields — typically by dispatching a matching [`session/chatUpdated`](/reference/session#actions) whenever any summary field on the chat changes. Refer to the [State Model guide](/guide/state-model) for a structural overview. When a client subscribes with `view.turns`, the server MAY expose only a tail of the most recent completed turns in the initial snapshot. The requested number is @@ -39,7 +39,7 @@ Clients MAY periodically sync their local input state into the draft by dispatch ### Per-chat working directory -`ChatState.workingDirectory` (and its mirror on [`ChatSummary`](/reference/chat#chatsummary)) is **optional**. When absent, the chat inherits the session's [`workingDirectory`](/reference/session#sessionsummary). Hosts MAY set a per-chat working directory to give individual chats their own filesystem context — for example, allocating a separate git worktree per chat so multiple chats in the same session can make independent edits that the orchestrating chat later merges back. The session-level `workingDirectory` is then the default/primary location for chats that do not override it. +`ChatState.workingDirectories` (and its mirror on [`ChatSummary`](/reference/chat#chatsummary)) is **optional**. When absent, the chat inherits the session's full [`workingDirectories`](/reference/session#sessionsummary) set; when present it MUST be a subset of that set. Hosts MAY set a per-chat subset to give individual chats their own filesystem context — for example, allocating a separate git worktree per chat so multiple chats in the same session can make independent edits that the orchestrating chat later merges back. ## Relationship to the session channel diff --git a/docs/specification/session-channel.md b/docs/specification/session-channel.md index d03f0c39..e326cb59 100644 --- a/docs/specification/session-channel.md +++ b/docs/specification/session-channel.md @@ -58,14 +58,14 @@ The producer of the chat's own [`ChatState`](./chat-channel#state) is responsibl ### Chat aggregation -[`SessionSummary`](/reference/session#sessionsummary) carries session-wide identity (`resource`, `provider`, `createdAt`, `workingDirectory`) but several of its mutable fields are aggregates derived from the session's chats. Producers SHOULD apply these rules so clients that only consume the session summary (a session list, for example) still see meaningful state: +[`SessionSummary`](/reference/session#sessionsummary) carries session-wide identity (`resource`, `provider`, `createdAt`, `workingDirectories`) but several of its mutable fields are aggregates derived from the session's chats. Producers SHOULD apply these rules so clients that only consume the session summary (a session list, for example) still see meaningful state: | Field | Derivation rule | |---|---| | `status` | Take the activity bits (`Idle` / `InProgress` / `InputNeeded` / `Error`) from the [`defaultChat`](#defaultchat) when set, else from the most recently modified chat. Promote `InputNeeded` if **any** chat needs input. Promote `Error` if **any** chat is in an error state. The orthogonal `IsRead` / `IsArchived` flags remain session-scoped and pass through unchanged. | | `activity` | Mirror the activity string of the chat that contributes the activity bits — usually the default chat, but the chat that raised `InputNeeded` / `Error` when a non-default chat wins the promotion. | | `modifiedAt` | The maximum of every chat's `modifiedAt`. | -| `workingDirectory` | The session-level **default**. Individual chats MAY override via [`ChatSummary.workingDirectory`](/reference/chat#chatsummary); aggregating per-chat overrides up is meaningless and SHOULD NOT be attempted. | +| `workingDirectories` | The session-level set. Individual chats MAY restrict to a subset via [`ChatSummary.workingDirectories`](/reference/chat#chatsummary); aggregating per-chat subsets up is meaningless and SHOULD NOT be attempted. | | `changes` | Optional roll-up. Producers MAY sum per-chat changeset stats or report the most expensive chat's stats — whichever is cheaper to compute. | Sessions with a single chat satisfy all of the above trivially (the chat's values pass through). The rules only matter once a session carries multiple chats. diff --git a/scripts/public-api.test.ts b/scripts/public-api.test.ts new file mode 100644 index 00000000..b45e1323 --- /dev/null +++ b/scripts/public-api.test.ts @@ -0,0 +1,76 @@ +/** + * Asserts that every public protocol declaration is reachable from + * `types/index.ts`, which is the published TypeScript client's root export. + * + * Run: npx tsx --test scripts/public-api.test.ts + */ + +import { describe, it, before } from 'node:test'; +import { strict as assert } from 'node:assert'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { Project } from 'ts-morph'; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); + +/** Declarations deliberately withheld from the entry point. */ +const INTENTIONALLY_INTERNAL: ReadonlySet = new Set([ + 'softAssertNever', // reducer-internal exhaustiveness helper +]); + +const CHANNEL_MODULE_RE = /types[\\/](common|channels-[a-z-]+)[\\/]/; + +describe('public API surface', () => { + let declared: Set; + let exported: Set; + + before(() => { + const project = new Project({ + tsConfigFilePath: resolve(root, 'types/tsconfig.json'), + skipAddingFilesFromTsConfig: false, + }); + + declared = new Set(); + for (const sourceFile of project.getSourceFiles()) { + const filePath = sourceFile.getFilePath(); + if (!CHANNEL_MODULE_RE.test(filePath)) continue; + if (filePath.endsWith('.test.ts')) continue; + for (const name of sourceFile.getExportedDeclarations().keys()) { + declared.add(name); + } + } + + exported = new Set( + project.getSourceFileOrThrow(resolve(root, 'types/index.ts')) + .getExportedDeclarations() + .keys(), + ); + }); + + it('finds the protocol declarations (guards the extraction itself)', () => { + assert.ok(declared.size > 100, `found only ${declared.size} declarations — the extraction is probably broken`); + assert.ok(exported.size > 100, `types/index.ts re-exports only ${exported.size} declarations`); + }); + + it('re-exports every public declaration from types/index.ts', () => { + const missing = [...declared] + .filter(name => !exported.has(name) && !INTENTIONALLY_INTERNAL.has(name)) + .sort(); + + assert.deepEqual( + missing, + [], + `${missing.length} declaration(s) unreachable from types/index.ts, so consumers of the published package cannot name them:\n ${missing.join('\n ')}`, + ); + }); + + it('keeps every intentionally-internal declaration internal', () => { + const leaked = [...INTENTIONALLY_INTERNAL].filter(name => exported.has(name)).sort(); + + assert.deepEqual( + leaked, + [], + `${leaked.length} internal declaration(s) are now exported — remove them from INTENTIONALLY_INTERNAL if that is intended:\n ${leaked.join('\n ')}`, + ); + }); +}); diff --git a/types/index.ts b/types/index.ts index 172400d5..a9b0f0f3 100644 --- a/types/index.ts +++ b/types/index.ts @@ -7,260 +7,20 @@ * are generated. */ -// State types -export type { - URI, - StringOrMarkdown, - RootState, - RootConfigState, - AgentInfo, - AgentCapabilities, - MultipleChatsCapability, - ProtectedResourceMetadata, - ProjectInfo, - SessionModelInfo, - ModelSelection, - AgentSelection, - SessionState, - SessionSummary, - ChatState, - ChatSummary, - ChatOrigin, - SideChatSelection, - ChangesSummary, - SessionConfigState, - Turn, - ActiveTurn, - Message, - MessageOrigin, - MessageAttachment, - MessageAttachmentBase, - TextPosition, - TextRange, - TextSelection, - SimpleMessageAttachment, - MessageEmbeddedResourceAttachment, - MessageResourceAttachment, - MessageAnnotationsAttachment, - MessageChatAttachment, - MarkdownResponsePart, - ContentRef, - ToolCallResponsePart, - ReasoningResponsePart, - SystemNotificationResponsePart, - ResponsePart, - ToolCallResult, - ToolInput, - ToolCallStreamingState, - ToolCallPendingConfirmationState, - ToolCallRunningState, - ToolCallAuthRequiredState, - ToolCallPendingResultConfirmationState, - ToolCallCompletedState, - ToolCallCancelledState, - ToolCallState, - ToolCallConfirmationState, - ToolCallRiskAssessmentLoadingState, - ToolCallRiskAssessmentCompleteState, - ToolCallRiskAssessment, - ConfirmationOption, - ToolDefinition, - ToolAnnotations, - ToolResultTextContent, - ToolResultEmbeddedResourceContent, - ToolResultResourceContent, - ToolResultContent, - FileEdit, - ToolResultFileEditContent, - ToolResultTerminalContent, - TerminalCommandResult, - ToolResultSubagentContent, - SessionActiveClient, - SessionInputRequest, - SessionChatInputRequest, - SessionToolConfirmationRequest, - SessionToolClientExecutionRequest, - SessionToolAuthenticationRequest, - McpOAuthClient, - McpAuthRequirement, - PendingMessage, - ChatInputAnswer, - ChatInputAnswerValue, - ChatInputTextAnswerValue, - ChatInputNumberAnswerValue, - ChatInputBooleanAnswerValue, - ChatInputSelectedAnswerValue, - ChatInputSelectedManyAnswerValue, - ChatInputAnswered, - ChatInputSkipped, - ChatInputOption, - ChatInputQuestion, - ChatInputTextQuestion, - ChatInputNumberQuestion, - ChatInputBooleanQuestion, - ChatInputSingleSelectQuestion, - ChatInputMultiSelectQuestion, - ChatInputRequest, - UsageInfo, - ErrorInfo, - Snapshot, - TerminalInfo, - TerminalClientClaim, - TerminalSessionClaim, - TerminalClaim, - TerminalState, - TerminalContentPart, - TerminalUnclassifiedPart, - TerminalCommandPart, - Changeset, - ChangesetState, - ChangesetFile, - ChangesetOperation, - AnnotationsSummary, - AnnotationsState, - Annotation, - AnnotationEntry, - TelemetryCapabilities, - ResourceWatchState, - ResourceChange, -} from './state.js'; - -export { - PolicyState, - SessionLifecycle, - SessionStatus, - SessionInputRequestKind, - ChatOriginKind, - TurnState, - MessageKind, - MessageAttachmentKind, - ResponsePartKind, - ToolCallStatus, - ToolCallConfirmationReason, - ToolCallRiskAssessmentKind, - ToolCallRiskAssessmentStatus, - ToolCallCancellationReason, - ConfirmationOptionKind, - ToolResultContentType, - PendingMessageKind, - ChatInputAnswerState, - ChatInputAnswerValueKind, - ChatInputQuestionKind, - ChatInputResponseKind, - TerminalClaimKind, - ChangesetStatus, - ChangesetOperationStatus, - ChangesetOperationScope, - ResourceChangeType, -} from './state.js'; - -// Action types -export type { - ActionEnvelope, - ActionOrigin, - RootAgentsChangedAction, - RootActiveSessionsChangedAction, - SessionReadyAction, - SessionCreationFailedAction, - SessionChatAddedAction, - SessionChatRemovedAction, - SessionChatUpdatedAction, - SessionDefaultChatChangedAction, - ChatTurnStartedAction, - ChatDeltaAction, - ChatResponsePartAction, - ChatToolCallStartAction, - ChatToolCallDeltaAction, - ChatToolCallReadyAction, - ChatToolCallApprovedAction, - ChatToolCallDeniedAction, - ChatToolCallConfirmedAction, - ChatToolCallCompleteAction, - ChatToolCallResultConfirmedAction, - ChatToolCallContentChangedAction, - ChatToolCallAuthRequiredAction, - ChatToolCallAuthResolvedAction, - ChatTurnCompleteAction, - ChatTurnCancelledAction, - ChatErrorAction, - ChatActivityChangedAction, - SessionTitleChangedAction, - ChatUsageAction, - ChatReasoningAction, - SessionServerToolsChangedAction, - SessionActiveClientSetAction, - SessionActiveClientRemovedAction, - SessionInputNeededSetAction, - SessionInputNeededRemovedAction, - ChatPendingMessageSetAction, - ChatPendingMessageRemovedAction, - ChatQueuedMessagesReorderedAction, - ChatDraftChangedAction, - ChatInputAnswerChangedAction, - ChatInputCompletedAction, - ChatInputRequestedAction, - ChatTruncatedAction, - ChatTurnsLoadedAction, - SessionIsReadChangedAction, - SessionIsArchivedChangedAction, - SessionActivityChangedAction, - SessionConfigChangedAction, - ChangesetStatusChangedAction, - ChangesetFileSetAction, - ChangesetFileRemovedAction, - ChangesetContentChangedAction, - ChangesetOperationsChangedAction, - ChangesetOperationStatusChangedAction, - ChangesetClearedAction, - AnnotationsSetAction, - AnnotationsRemovedAction, - AnnotationsEntrySetAction, - AnnotationsEntryRemovedAction, - StateAction, - RootTerminalsChangedAction, - RootConfigChangedAction, - TerminalDataAction, - TerminalInputAction, - TerminalResizedAction, - TerminalClaimedAction, - TerminalTitleChangedAction, - TerminalCwdChangedAction, - TerminalExitedAction, - TerminalClearedAction, - TerminalCommandExecutedAction, - TerminalCommandFinishedAction, - TerminalCommandDetectionAvailableAction, - ResourceWatchChangedAction, -} from './actions.js'; - -export { ActionType } from './actions.js'; - -// Generated action origin types -export type { - RootAction, - SessionAction, - ClientSessionAction, - ServerSessionAction, - ChatAction, - ClientChatAction, - ServerChatAction, - TerminalAction, - ClientTerminalAction, - ServerTerminalAction, - ChangesetAction, - ClientChangesetAction, - ServerChangesetAction, - AnnotationsAction, - ClientAnnotationsAction, - ServerAnnotationsAction, - ResourceWatchAction, - ClientResourceWatchAction, - ServerResourceWatchAction, -} from './action-origin.generated.js'; - -export { IS_CLIENT_DISPATCHABLE } from './action-origin.generated.js'; - -// Reducer functions +export * from './state.js'; +export * from './actions.js'; +export * from './action-origin.generated.js'; +export * from './commands.js'; +export * from './notifications.js'; +export * from './messages.js'; +export * from './errors.js'; +export * from './version/registry.js'; + +// `ChatAction` is declared both by hand and in the generated origin module, so +// disambiguate the `export *`s above. +export type { ChatAction } from './action-origin.generated.js'; + +// Explicit: the shim also re-exports the internal `softAssertNever`. export { rootReducer, sessionReducer, @@ -271,142 +31,3 @@ export { resourceWatchReducer, isClientDispatchable, } from './reducers.js'; - -// Command types -export type { - InitializeParams, - InitializeResult, - ClientCapabilities, - PingParams, - ReconnectParams, - ReconnectReplayResult, - ReconnectSnapshotResult, - ReconnectResult, - SubscribeParams, - SubscribeView, - SubscriptionDeliveryOptions, - SubscribeResult, - CreateSessionParams, - SessionForkSource, - DisposeSessionParams, - CreateChatParams, - ChatSource, - ForkChatSource, - SideChatSource, - DisposeChatParams, - CreateTerminalParams, - DisposeTerminalParams, - PaginatedParams, - PaginatedResult, - ListSessionsParams, - ListSessionsResult, - ResourceReadParams, - ResourceReadResult, - ResourceWriteParams, - ResourceWriteResult, - ResourceListParams, - DirectoryEntry, - ResourceListResult, - ResourceCopyParams, - ResourceCopyResult, - ResourceDeleteParams, - ResourceDeleteResult, - ResourceMoveParams, - ResourceMoveResult, - ResourceResolveParams, - ResourceResolveResult, - ResourceMkdirParams, - ResourceMkdirResult, - ResourceRequestParams, - ResourceRequestResult, - CreateResourceWatchParams, - CreateResourceWatchResult, - FetchTurnsParams, - FetchTurnsResult, - UnsubscribeParams, - DispatchActionParams, - AuthenticateParams, - AuthenticateResult, - ResolveSessionConfigParams, - ResolveSessionConfigResult, - ConfigPropertySchema, - ConfigSchema, - SessionConfigPropertySchema, - SessionConfigSchema, - SessionConfigCompletionsParams, - SessionConfigCompletionsResult, - SessionConfigValueItem, - CompletionsParams, - CompletionsResult, - CompletionItem, - InvokeChangesetOperationParams, - InvokeChangesetOperationResult, - ChangesetOperationTarget, - ChangesetOperationFollowUp, -} from './commands.js'; - -export { ReconnectResultType, ChatSourceKind, ContentEncoding, CompletionItemKind, ResourceType, ResourceWriteMode } from './commands.js'; - -// Notification types -export type { - SessionAddedParams, - SessionRemovedParams, - SessionSummaryChangedParams, - ProgressParams, - AuthRequiredParams, - OtlpExportLogsParams, - OtlpExportTracesParams, - OtlpExportMetricsParams, -} from './notifications.js'; - -export { AuthRequiredReason } from './notifications.js'; - -// Message types (JSON-RPC wire format) -export type { - JsonRpcRequest, - JsonRpcSuccessResponse, - JsonRpcErrorResponse, - JsonRpcResponse, - JsonRpcNotification, - AhpErrorResponse, - CommandMap, - ServerCommandMap, - ClientNotificationMap, - ServerNotificationMap, - AhpRequest, - AhpServerRequest, - AhpSuccessResponse, - AhpServerSuccessResponse, - AhpResponse, - AhpServerResponse, - AhpNotification, - AhpClientNotification, - AhpServerNotification, - ProtocolMessage, -} from './messages.js'; - -// Error codes -export { - JsonRpcErrorCodes, - AhpErrorCodes, -} from './errors.js'; -export type { - AhpErrorCode, - JsonRpcErrorCode, - AhpError, - AhpErrorCodeWithData, - AhpErrorDetailsMap, - AuthRequiredErrorData, - PermissionDeniedErrorData, -} from './errors.js'; - -// Version registry -export { - PROTOCOL_VERSION, - SUPPORTED_PROTOCOL_VERSIONS, - ACTION_INTRODUCED_IN, - NOTIFICATION_INTRODUCED_IN, - isActionKnownToVersion, - isNotificationKnownToVersion, - compareProtocolVersions, -} from './version/registry.js';