88
return isArchived ? ChatInteractivity.ReadOnly : interactivity;
89
}
91
>
export interface ISessionGitRepository {
92
>
/** The source repository URI. */
93
>
readonly uri: URI;
94
>
/** The working directory URI (e.g., a git worktree or checkout path). */
95
>
readonly workTreeUri: URI | undefined;
96
>
/** Current branch name. */
97
>
readonly branchName?: string;
98
>
/** Name of the base branch. */
99
>
readonly baseBranchName: string | undefined;
100
>
/** Whether the base branch is protected (drives PR vs merge workflow). */
101
>
readonly baseBranchProtected?: boolean;
102
>
/** Whether the repository has a github.com remote. */
103
>
readonly hasGitHubRemote?: boolean;
104
>
/** Upstream tracking branch name (e.g. `origin/feature`). */
105
>
readonly upstreamBranchName?: string;
106
>
/** Number of commits the upstream branch is ahead of the local branch. */
107
>
readonly incomingChanges?: number;
108
>
/** Number of commits the local branch is ahead of the upstream branch. */
109
>
readonly outgoingChanges?: number;
110
>
/** Number of files with uncommitted changes. */
111
>
readonly uncommittedChanges?: number;
112
>
/** Whether a Git operation is currently in progress. */
113
>
readonly hasGitOperationInProgress?: boolean;
114
>
/** GitHub information associated with the repository. */
115
>
readonly gitHubInfo: IObservable<IGitHubInfo | undefined>;
116
>
}
117
>
118
>
/**
119
>
* A folder within a session workspace.
120
>
*/
121
>
export interface ISessionFolder {
122
>
/** Canonical URI of the folder. */
123
>
readonly root: URI;
124
>
/** Working directory used for file operations. */
125
>
readonly workingDirectory: URI;
126
>
/** Display name for the folder (e.g., repository or directory basename). */
127
>
readonly name: string;
128
>
/** Optional description shown alongside the name (e.g., parent folder path). */
129
>
readonly description: string | undefined;
130
>
/** Git repository information associated with this folder. */
131
>
readonly gitRepository?: ISessionGitRepository;
132
>
}
133
>
134
>
/**
135
>
* Workspace information for a session, encapsulating one or more repositories.
136
>
*/
137
>
export interface ISessionWorkspace {
138
>
/** URI identifying the workspace. */
139
>
readonly uri: URI;
140
>
/** Display label for the workspace (e.g., "my-app", "org/repo", "host:/path"). */
141
>
readonly label: string;
142
>
/** Optional description shown alongside the label (e.g., parent folder path "~/work"). */
143
>
readonly description?: string;
144
>
/**
145
>
* Optional group label for categorizing this workspace in pickers. The
146
>
* workspace picker uses this to bucket entries into top-level tabs
147
>
* (e.g. `"Local"`, `"Cloud"`, `"Remote"`). Providers contribute the
148
>
* label — the picker just renders whatever values are present.
149
>
*/
150
>
readonly group?: string;
151
>
/** Icon for the workspace. */
152
>
readonly icon: ThemeIcon;
153
>
/** Folders in this session workspace. */
154
>
readonly folders: ISessionFolder[];
155
>
/** Whether the session requires workspace trust to operate. */
156
>
readonly requiresWorkspaceTrust: boolean;
157
>
/**
158
>
* Whether this workspace is a virtual
159
>
*/
160
>
readonly isVirtualWorkspace: boolean;
161
>
}
162
>
163
>
/**
164
>
* GitHub information associated with a session.
165
>
*/
166
>
export interface IGitHubInfo {
167
>
/** GitHub repository owner. */
168
>
readonly owner: string;
169
>
/** GitHub repository name. */
170
>
readonly repo: string;
171
>
/** Pull request associated with this session, if any. */
172
>
readonly pullRequest?: {
173
>
/** Pull request number. */
174
>
readonly number: number;
175
>
/** URI of the pull request. */
176
>
readonly uri: URI;
177
>
/** Icon reflecting the PR state. */
178
>
readonly icon?: ThemeIcon;
179
>
/** Object ID of the base ref (merge target) commit. */
180
>
readonly baseRefOid?: string;
181
>
/** Object ID of the head ref (PR branch) commit. */
182
>
readonly headRefOid?: string;
183
>
};
184
>
}
185
>
186
>
export interface ISessionChangesSummary {
187
>
readonly files: number;
188
>
readonly additions: number;
189
>
readonly deletions: number;
190
>
}
191
>
192
>
export type ISessionFileChange = IChatSessionFileChange | IChatSessionFileChange2;
193
>
194
>
/**
195
>
* The kind of change applied to a {@link ISessionFile}.
196
>
*
197
>
* A file that is first created and then edited during the session is reported
198
>
* as {@link Created}. A file that is deleted is reported as {@link Deleted}
199
>
* regardless of any earlier creation or edit.
200
>
*/
201
>
export const enum SessionFileOperation {
202
>
/** The file was created during the session (and possibly edited afterwards). */
203
>
Created = 'created',
204
>
/** The file existed before the session and was modified during it. */
205
>
Modified = 'modified',
206
>
/** The file was deleted during the session. */
207
>
Deleted = 'deleted',
208
>
}
209
>
210
>
/**
211
>
* A file that was created, edited or deleted **outside** the session workspace
212
>
* folders during the session. These are surfaced separately from
213
>
* {@link ISession.changes} because they are not part of the workspace and will
214
>
* not be committed.
215
>
*/
216
>
export interface ISessionFile {
217
>
/** The file URI (after-state for create/modify, the deleted path for delete). */
218
>
readonly uri: URI;
219
>
/** The kind of change applied to the file during the session. */
220
>
readonly operation: SessionFileOperation;
221
>
/**
222
>
* URI from which the file's pre-session content can be read, when known.
223
>
* Used to render a diff for {@link SessionFileOperation.Modified} files.
224
>
*/
225
>
readonly originalUri?: URI;
226
>
}
227
>
228
>
/**
229
>
* Well-known id of the changeset that holds the diff between a session's branch
230
>
* and its base (e.g. `main...feature`). Shared so that consumers which always
231
>
* want the branch diff — regardless of the changeset currently selected in the
232
>
* Changes view — can locate it in {@link ISession.changesets} by id.
233
>
*/
234
>
export const BRANCH_CHANGES_CHANGESET_ID = 'branchChanges';
235
>
236
>
/**
237
>
* Well-known id of the changeset that holds the diff made during the session's
238
>
* **last turn** only (as opposed to the cumulative session diff). Consumers that
239
>
* want to reflect just the most recent turn — e.g. the chat input status pills —
240
>
* can locate it in {@link ISession.changesets} by id.
241
>
*
242
>
* Must match the agent host provider's `ChangesetKind.Turn` value.
243
>
*/
244
>
export const TURN_CHANGES_CHANGESET_ID = 'turn';
245
>
246
>
export interface ISessionChangeset {
247
>
/** Unique identifier for the changeset. */
248
>
readonly id: string;
249
>
/** Display label for the changeset. */
250
>
readonly label: string;
251
>
/** Optional description for the changeset. */
252
>
readonly description?: string;
253
>
/** Optional category for the changeset. */
254
>
readonly category?: string;
255
>
/** Whether the changeset is enabled. */
256
>
readonly isEnabled: IObservable<boolean>;
257
>
/**
258
>
* Whether this changeset should be selected by default when the UI
259
>
* switches to its session. May change with session state (e.g. an
260
>
* archived session may default to a snapshot changeset rather than a
261
>
* live one). Producers should ensure at most one changeset in a
262
>
* session reports `true` at any time.
263
>
*/
264
>
readonly isDefault: IObservable<boolean>;
265
>
/**
266
>
* Whether this changeset is currently loading its file changes.
267
>
*/
268
>
readonly isLoadingChanges: IObservable<boolean>;
269
>
/** Observable for the file changes in this changeset. */
270
>
readonly changes: IObservable<readonly ISessionFileChange[]>;
271
>
/** Observable for the operations in this changeset. */
272
>
readonly operations: IObservable<readonly ISessionChangesetOperation[]>;
273
>
/** Reference to the original checkpoint for this changeset. */
274
>
readonly originalCheckpointRef: IObservable<string | undefined>;
275
>
/** Reference to the modified checkpoint for this changeset. */
276
>
readonly modifiedCheckpointRef: IObservable<string | undefined>;
277
>
/** The capabilities of this changeset. */
278
>
readonly capabilities?: ISessionChangesetCapabilities;
279
>
280
>
/**
281
>
* Invoke an operation declared in {@link operations}. `target` must be
282
>
* provided for resource-scoped operations and omitted for changeset-
283
>
* scoped ones — implementations are expected to validate this against
284
>
* the corresponding {@link ISessionChangesetOperation.scopes}.
285
>
*/
286
>
invokeOperation(operationId: string, target?: ISessionChangesetOperationTarget): Promise<void>;
287
>
288
>
/**
289
>
* Sets the review state for a list of resources when the changeset supports review.
290
>
*/
291
>
setReviewState?(resources: readonly URI[], reviewed: boolean): void;
292
>
}
293
>
294
>
export type ISessionChangesetOperationTarget =
295
>
| { readonly kind: 'resource'; readonly resource: URI };
296
>
297
>
export const enum SessionChangesetOperationScope {
298
>
Changeset = 'changeset',
299
>
Resource = 'resource',
300
>
Range = 'range',
301
>
}
302
>
303
>
/**
304
>
* Execution status of a changeset operation.
305
>
*/
306
>
export const enum SessionChangesetOperationStatus {
307
>
/** The operation is ready to be invoked. */
308
>
Idle = 'idle',
309
>
/** An invocation is currently in flight. */
310
>
Running = 'running',
311
>
/** The most recent invocation failed. */
312
>
Error = 'error',
313
>
/** The operation is currently disabled and cannot be invoked. */
314
>
Disabled = 'disabled',
315
>
}
316
>
317
>
export interface ISessionChangesetOperation {
318
>
/** Unique identifier for the operation. */
319
>
readonly id: string;
320
>
/** Display label for the operation. */
321
>
readonly label: string;
322
>
/** Optional description for the operation. */
323
>
readonly description?: string;
324
>
/** Optional icon for the operation. */
325
>
readonly icon?: ThemeIcon;
326
>
/** Optional group identifier, used to group related operations together. */
327
>
readonly group?: string;
328
>
/** The scopes to which this operation applies. */
329
>
readonly scopes: SessionChangesetOperationScope[];
330
>
/** Current execution status for this operation. */
331
>
readonly status: SessionChangesetOperationStatus;
332
>
/**
333
>
* Optional confirmation prompt to display before invoking the operation.
334
>
* When present, callers MUST show this message to the user (typically in
335
>
* a confirmation dialog) and only invoke the operation after the user
336
>
* accepts. The presence of this field also signals that the operation
337
>
* is destructive — callers SHOULD style the affirmative button
338
>
* accordingly. The message may contain `{0}` which will be substituted
339
>
* with the target resource's basename when applicable.
340
>
*/
341
>
readonly confirmation?: string | IMarkdownString;
342
>
}
343
>
344
>
export interface ISessionChangesetCapabilities {
345
>
/** Whether the changeset supports review workflow. */
346
>
readonly review?: boolean;
347
>
}
348
>
349
>
/**
350
>
* A custom agent reference used by session-level selection. Mirrors the Agent
351
>
* Host protocol's `AgentSelection` shape but lives in the sessions layer so the
352
>
* sessions service API does not leak the protocol type to non-Agent-Host
353
>
* consumers.
354
>
*/
355
>
export interface ISessionAgentRef {
356
>
/** Stable agent URI (matches the contributing customization's agent ref). */
357
>
readonly uri: string;
358
>
/** Agent name. */
359
>
readonly name: string;
360
>
}
361
>
362
>
export interface IChatCheckpoints {
363
>
/** Reference to the first checkpoint in the chat. */
364
>
readonly firstCheckpointRef: string;
365
>
/** Reference to the last checkpoint in the chat. */
366
>
readonly lastCheckpointRef: string;
367
>
}
368
>
369
>
export const enum ChatOriginKind {
370
>
Tool = 'tool',
371
>
User = 'user',
372
>
Fork = 'fork',
373
>
SideChat = 'sideChat',
374
>
}
375
>
376
>
export interface ISideChatSelection {
377
>
readonly text: string;
378
>
readonly responsePartId?: string;
379
>
}
380
>
381
>
export interface IChatOrigin {
382
>
readonly kind: ChatOriginKind;
383
>
/**
384
>
* For a chat spawned by another chat (e.g. a subagent worker chat, kind
385
>
* {@link ChatOriginKind.Tool}, or a {@link ChatOriginKind.Fork}), the
386
>
* resource of the chat that spawned it. Undefined for user-originated chats.
387
>
*/
388
>
readonly parentChat?: URI;
389
>
readonly selection?: ISideChatSelection;
390
>
}
391
>
392
>
/**
393
>
* Per-chat capabilities. Consumers gate chat-management UI (rename, delete) on
394
>
* these flags rather than on the chat's origin/provider, so the affordances are
395
>
* offered exactly where the backing chat supports them. A worker (subagent)
396
>
* chat, for example, is neither renameable nor deletable.
397
>
*/
398
>
export interface IChatCapabilities {
399
>
/** Whether this chat's title can be renamed. */
400
>
readonly canRename: boolean;
401
>
/** Whether this chat can be permanently deleted. */
402
>
readonly canDelete: boolean;
403
>
}
404
>
405
>
/** Capabilities assumed for a chat that does not advertise its own. */
406
>
export const DEFAULT_CHAT_CAPABILITIES: IChatCapabilities = { canRename: true, canDelete: true };
407
>
408
>
/**
409
>
* A single chat within a session, produced by the sessions management layer.
410
>
*/
411
>
export interface IChat {
412
>
/** Resource URI identifying this chat. */
413
>
readonly resource: URI;
414
>
/** When the chat was created. */
415
>
readonly createdAt: Date;
416
>
417
>
// Reactive properties
418
>
419
>
/** Chat display title (changes when auto-titled or renamed). */
420
>
readonly title: IObservable<string>;
421
>
/** When the chat was last updated. */
422
>
readonly updatedAt: IObservable<Date>;
423
>
/** Current chat status. */
424
>
readonly status: IObservable<SessionStatus>;
425
>
/** File changes produced by the chat. */
426
>
readonly changes: IObservable<readonly ISessionFileChange[]>;
427
>
/**
428
>
* File changes produced by the chat's **last turn** only (as opposed to the
429
>
* cumulative chat {@link changes}). Derived from the chat's live output
430
>
* stream so consumers — e.g. the chat input status pills — can reflect just
431
>
* what the most recent request produced. Providers that cannot determine
432
>
* this omit the observable.
433
>
*/
434
>
readonly lastTurnChanges?: IObservable<readonly ISessionFileChange[]>;
435
>
/** Checkpoints associated with the chat. */
436
>
readonly checkpoints: IObservable<IChatCheckpoints | undefined>;
437
>
/** Currently selected model identifier. */
438
>
readonly modelId: IObservable<string | undefined>;
439
>
/** Currently selected mode identifier and kind. */
440
>
readonly mode: IObservable<{ readonly id: string; readonly kind: string } | undefined>;
441
>
/** Whether the chat is archived. */
442
>
readonly isArchived: IObservable<boolean>;
443
>
/** Whether the chat has been read. */
444
>
readonly isRead: IObservable<boolean>;
445
>
/**
446
>
* Whether and how the user can interact with this chat. Providers that do
447
>
* not distinguish read-only chats report {@link ChatInteractivity.Full}.
448
>
*
449
>
* - {@link ChatInteractivity.Full}: the user can send messages (default).
450
>
* - {@link ChatInteractivity.ReadOnly}: the chat is shown but the composer is
451
>
* hidden (e.g. an agent-team worker chat the user can watch but not steer).
452
>
* - {@link ChatInteractivity.Hidden}: the chat is an internal worker that
453
>
* should not be surfaced in the UI at all; the visible session model filters
454
>
* these out of the tab strip and never makes them the active chat.
455
>
*/
456
>
readonly interactivity: IObservable<ChatInteractivity>;
457
>
/** Status description shown while the chat is active (e.g., current agent action). */
458
>
readonly description: IObservable<IMarkdownString | undefined>;
459
>
/** Timestamp of when the last agent turn ended, if any. */
460
>
readonly lastTurnEnd: IObservable<Date | undefined>;
461
>
/** How the chat came into existence, if provided by the backend. */
462
>
readonly origin?: IChatOrigin;
463
>
/**
464
>
* Capabilities of this chat (rename/delete). Absent means the chat inherits
465
>
* {@link DEFAULT_CHAT_CAPABILITIES} (fully capable); read via
466
>
* {@link getChatCapabilities}.
467
>
*/
468
>
readonly capabilities?: IObservable<IChatCapabilities>;
469
>
}
470
>
471
>
/**
472
>
* Resolve a chat's effective capabilities. Combines the chat's own advertised
473
>
* {@link IChat.capabilities} (falling back to {@link DEFAULT_CHAT_CAPABILITIES})
474
>
* with the session-level invariant that a session's main chat can never be
475
>
* deleted — it lives and dies with the session. Pass the owning session so the
476
>
* main-chat rule applies; omit it to read only the chat's own capabilities.
477
>
*/
478
>
export function getChatCapabilities(chat: IChat, session: ISession | undefined, reader: IReader | undefined): IChatCapabilities {
479
const own = chat.capabilities?.read(reader) ?? DEFAULT_CHAT_CAPABILITIES;
480
if (session && isEqual(chat.resource, session.mainChat.read(reader).resource)) {