Agents - scaffold Changesets model (#313577)

* Agents - scaffold Changesets model

* Pull request feedback
This commit is contained in:
Ladislau Szomoru
2026-05-04 13:26:58 +00:00
committed by GitHub
parent a0afc4cbe9
commit ebfbb71de2
10 changed files with 64 additions and 3 deletions

View File

@@ -32,7 +32,7 @@ import { diffsEqual, diffsToChanges, mapProtocolStatus } from './agentHostDiffs.
import { buildMutableConfigSchema, IAgentHostSessionsProvider, resolvedConfigsEqual } from '../../../common/agentHostSessionsProvider.js';
import { agentHostSessionWorkspaceKey } from '../../../common/agentHostSessionWorkspace.js';
import { isSessionConfigComplete } from '../../../common/sessionConfig.js';
import { IChat, IGitHubInfo, ISession, ISessionType, ISessionWorkspace, ISessionWorkspaceBrowseAction, SessionStatus, toSessionId } from '../../../services/sessions/common/session.js';
import { IChat, IGitHubInfo, ISession, ISessionChangeset, ISessionType, ISessionWorkspace, ISessionWorkspaceBrowseAction, SessionStatus, toSessionId } from '../../../services/sessions/common/session.js';
import { ISendRequestOptions, ISessionChangeEvent } from '../../../services/sessions/common/sessionsProvider.js';
// ============================================================================
@@ -74,6 +74,7 @@ export class AgentHostSessionAdapter implements ISession {
readonly updatedAt: ISettableObservable<Date>;
readonly status: ISettableObservable<SessionStatus>;
readonly changes = observableValue<readonly (IChatSessionFileChange | IChatSessionFileChange2)[]>('changes', []);
readonly changesets = observableValue<readonly ISessionChangeset[]>('changesets', []);
readonly modelId: ISettableObservable<string | undefined>;
modelSelection: ModelSelection | undefined;
readonly mode = observableValue<{ readonly id: string; readonly kind: string } | undefined>('mode', undefined);
@@ -162,6 +163,7 @@ export class AgentHostSessionAdapter implements ISession {
updatedAt: this.updatedAt,
status: this.status,
changes: this.changes,
changesets: this.changesets,
modelId: this.modelId,
mode: this.mode,
isArchived: this.isArchived,
@@ -381,6 +383,7 @@ class NewSession extends Disposable {
this._status = observableValue<SessionStatus>(this, SessionStatus.Untitled);
const title = observableValue<string>(this, '');
const updatedAt = observableValue(this, new Date());
const changesets = observableValue<readonly ISessionChangeset[]>(this, []);
const changes = observableValue<readonly (IChatSessionFileChange | IChatSessionFileChange2)[]>(this, []);
this._modelId = observableValue<string | undefined>(this, undefined);
const mode = observableValue<{ readonly id: string; readonly kind: string } | undefined>(this, undefined);
@@ -394,6 +397,7 @@ class NewSession extends Disposable {
const mainChat: IChat = {
resource, createdAt, title, updatedAt,
status: this._status,
changesets,
changes,
modelId: this._modelId,
mode, isArchived, isRead, description, lastTurnEnd,
@@ -411,6 +415,7 @@ class NewSession extends Disposable {
title,
updatedAt,
status: this._status,
changesets,
changes,
modelId: this._modelId,
mode,

View File

@@ -31,6 +31,7 @@ function makeActiveSession(providerId: string): IActiveSession {
title: observableValue('t', 'Test'),
updatedAt: observableValue('u', new Date()),
status: observableValue('s', 0),
changesets: observableValue('cs', []),
changes: observableValue('c', []),
modelId: observableValue('m', undefined),
mode: observableValue('mo', undefined),
@@ -50,6 +51,7 @@ function makeActiveSession(providerId: string): IActiveSession {
title: chat.title,
updatedAt: chat.updatedAt,
status: chat.status,
changesets: chat.changesets,
changes: chat.changes,
modelId: chat.modelId,
mode: chat.mode,

View File

@@ -41,6 +41,7 @@ function makeSession(opts: { repository?: URI; worktree?: URI } = {}): ISession
title: observableValue('title', 'session'),
updatedAt: observableValue('updatedAt', new Date()),
status: observableValue('status', SessionStatus.Untitled),
changesets: observableValue('changesets', []),
changes: observableValue('changes', []),
modelId: observableValue('modelId', undefined),
mode: observableValue('mode', undefined),
@@ -60,6 +61,7 @@ function makeSession(opts: { repository?: URI; worktree?: URI } = {}): ISession
title: chat.title,
updatedAt: chat.updatedAt,
status: chat.status,
changesets: chat.changesets,
changes: chat.changes,
modelId: chat.modelId,
mode: chat.mode,

View File

@@ -23,7 +23,7 @@ import { AgentSessionProviders, AgentSessionTarget } from '../../../../workbench
import { IChatService, IChatSendRequestOptions } from '../../../../workbench/contrib/chat/common/chatService/chatService.js';
import { IChatResponseModel } from '../../../../workbench/contrib/chat/common/model/chatModel.js';
import { ChatSessionStatus, IChatSessionsService, IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, SessionType } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';
import { ISession, IChat, ISessionRepository, ISessionWorkspace, SessionStatus, GITHUB_REMOTE_FILE_SCHEME, IGitHubInfo, CopilotCLISessionType, CopilotCloudSessionType, ClaudeCodeSessionType, ISessionType, ISessionWorkspaceBrowseAction, ISessionFileChange, toSessionId, SESSION_WORKSPACE_GROUP_LOCAL } from '../../../services/sessions/common/session.js';
import { ISession, IChat, ISessionRepository, ISessionWorkspace, SessionStatus, GITHUB_REMOTE_FILE_SCHEME, IGitHubInfo, CopilotCLISessionType, CopilotCloudSessionType, ClaudeCodeSessionType, ISessionType, ISessionWorkspaceBrowseAction, ISessionFileChange, toSessionId, SESSION_WORKSPACE_GROUP_LOCAL, ISessionChangeset } from '../../../services/sessions/common/session.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, ChatPermissionLevel, isChatPermissionLevel } from '../../../../workbench/contrib/chat/common/constants.js';
import { basename, dirname, isEqual } from '../../../../base/common/resources.js';
import { ISendRequestOptions, ISessionChangeEvent, ISessionsProvider } from '../../../services/sessions/common/sessionsProvider.js';
@@ -71,6 +71,8 @@ export interface ICopilotChatSession {
readonly updatedAt: IObservable<Date>;
/** Current session status. */
readonly status: IObservable<SessionStatus>;
/** File changesets produced by the session. */
readonly changesets: IObservable<readonly ISessionChangeset[]>;
/** File changes produced by the session. */
readonly changes: IObservable<readonly ISessionFileChange[]>;
/** Currently selected model identifier. */
@@ -180,6 +182,9 @@ class CopilotCLISession extends Disposable implements ICopilotChatSession {
private readonly _loading = observableValue(this, true);
readonly loading: IObservable<boolean> = this._loading;
private readonly _changesets: ReturnType<typeof observableValue<readonly ISessionChangeset[]>>;
readonly changesets: IObservable<readonly ISessionChangeset[]>;
private readonly _changes: ReturnType<typeof observableValue<readonly ISessionFileChange[]>>;
readonly changes: IObservable<readonly ISessionFileChange[]>;
@@ -259,6 +264,9 @@ class CopilotCLISession extends Disposable implements ICopilotChatSession {
this._description = observableValue(this, undefined);
this.description = this._description;
this._changesets = observableValue<readonly ISessionChangeset[]>(this, []);
this.changesets = this._changesets;
this._changes = observableValue<readonly ISessionFileChange[]>(this, []);
this.changes = this._changes;
}
@@ -416,6 +424,7 @@ class CopilotCLISession extends Disposable implements ICopilotChatSession {
this._title.set(session.title.get(), undefined);
this._status.set(session.status.get(), undefined);
this._updatedAt.set(session.updatedAt.get(), undefined);
this._changesets.set(session.changesets.get(), undefined);
this._changes.set(session.changes.get(), undefined);
this._description.set(session.description.get(), undefined);
}
@@ -463,6 +472,7 @@ export class RemoteNewSession extends Disposable implements ICopilotChatSession
private readonly _workspaceData = observableValue<ISessionWorkspace | undefined>(this, undefined);
readonly workspace: IObservable<ISessionWorkspace | undefined> = this._workspaceData;
readonly changesets: IObservable<readonly ISessionChangeset[]> = observableValue<readonly ISessionChangeset[]>(this, []);
readonly changes: IObservable<readonly ISessionFileChange[]> = observableValue<readonly ISessionFileChange[]>(this, []);
private readonly _modelIdObservable = observableValue<string | undefined>(this, undefined);
@@ -705,6 +715,7 @@ class ClaudeCodeNewSession extends Disposable implements ICopilotChatSession {
private readonly _workspaceData = observableValue<ISessionWorkspace | undefined>(this, undefined);
readonly workspace: IObservable<ISessionWorkspace | undefined> = this._workspaceData;
readonly changesets: IObservable<readonly ISessionChangeset[]> = observableValue<readonly ISessionChangeset[]>(this, []);
readonly changes: IObservable<readonly ISessionFileChange[]> = observableValue<readonly ISessionFileChange[]>(this, []);
private readonly _modelIdObservable = observableValue<string | undefined>(this, undefined);
@@ -844,6 +855,9 @@ class AgentSessionAdapter implements ICopilotChatSession {
private readonly _status: ReturnType<typeof observableValue<SessionStatus>>;
readonly status: IObservable<SessionStatus>;
private readonly _changesets: ReturnType<typeof observableValue<readonly ISessionChangeset[]>>;
readonly changesets: IObservable<readonly ISessionChangeset[]>;
private readonly _changes: ReturnType<typeof observableValue<readonly ISessionFileChange[]>>;
readonly changes: IObservable<readonly ISessionFileChange[]>;
@@ -896,6 +910,9 @@ class AgentSessionAdapter implements ICopilotChatSession {
this._status = observableValue(this, toSessionStatus(session.status));
this.status = this._status;
this._changesets = observableValue<readonly ISessionChangeset[]>(this, this._extractChangesets(session));
this.changesets = this._changesets;
this._changes = observableValue<readonly ISessionFileChange[]>(this, this._extractChanges(session));
this.changes = this._changes;
@@ -1094,6 +1111,10 @@ class AgentSessionAdapter implements ICopilotChatSession {
return undefined;
}
private _extractChangesets(session: IAgentSession): readonly ISessionChangeset[] {
return [];
}
private _extractChanges(session: IAgentSession): readonly ISessionFileChange[] {
if (!session.changes) {
return [];
@@ -2593,6 +2614,7 @@ export class CopilotChatSessionsProvider extends Disposable implements ISessions
title: primaryChat.title,
updatedAt: chatsObs.map((chats, reader) => this._latestDate(chats, c => c.updatedAt.read(reader))!),
status: chatsObs.map((chats, reader) => this._aggregateStatus(chats, reader)),
changesets: primaryChat.changesets,
changes: primaryChat.changes,
modelId: primaryChat.modelId,
mode: primaryChat.mode,
@@ -2623,6 +2645,7 @@ export class CopilotChatSessionsProvider extends Disposable implements ISessions
title: chat.title,
updatedAt: chat.updatedAt,
status: chat.status,
changesets: chat.changesets,
changes: chat.changes,
modelId: chat.modelId,
mode: chat.mode,
@@ -2645,6 +2668,7 @@ export class CopilotChatSessionsProvider extends Disposable implements ISessions
title: chat.title,
updatedAt: chat.updatedAt,
status: chat.status,
changesets: chat.changesets,
changes: chat.changes,
modelId: chat.modelId,
mode: chat.mode,

View File

@@ -15,7 +15,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/tes
import { mock } from '../../../../../base/test/common/mock.js';
import { GitHubPullRequestPollingContribution } from '../../browser/github.contribution.js';
import { IGitHubService } from '../../browser/githubService.js';
import { IChat, IGitHubInfo, ISession, ISessionCapabilities, ISessionFileChange, ISessionWorkspace, SessionStatus } from '../../../../services/sessions/common/session.js';
import { IChat, IGitHubInfo, ISession, ISessionCapabilities, ISessionChangeset, ISessionFileChange, ISessionWorkspace, SessionStatus } from '../../../../services/sessions/common/session.js';
import { IActiveSession, ISessionsChangeEvent, ISessionsManagementService } from '../../../../services/sessions/common/sessionsManagement.js';
suite('GitHubPullRequestPollingContribution', () => {
@@ -152,6 +152,7 @@ class TestSession implements ISession {
readonly title: ReturnType<typeof observableValue<string>>;
readonly updatedAt: ReturnType<typeof observableValue<Date>>;
readonly status: ReturnType<typeof observableValue<SessionStatus>>;
readonly changesets: ReturnType<typeof observableValue<readonly ISessionChangeset[]>>;
readonly changes: ReturnType<typeof observableValue<readonly ISessionFileChange[]>>;
readonly workspace: ReturnType<typeof observableValue<ISessionWorkspace | undefined>>;
readonly modelId: ReturnType<typeof observableValue<string | undefined>>;
@@ -172,6 +173,7 @@ class TestSession implements ISession {
this.title = observableValue<string>(`test.title.${id}`, id);
this.updatedAt = observableValue<Date>(`test.updatedAt.${id}`, new Date(0));
this.status = observableValue<SessionStatus>(`test.status.${id}`, SessionStatus.Completed);
this.changesets = observableValue<readonly ISessionChangeset[]>(`test.changesets.${id}`, []);
this.changes = observableValue<readonly ISessionFileChange[]>(`test.changes.${id}`, []);
this.workspace = observableValue<ISessionWorkspace | undefined>(`test.workspace.${id}`, undefined);
this.modelId = observableValue<string | undefined>(`test.modelId.${id}`, undefined);
@@ -188,6 +190,7 @@ class TestSession implements ISession {
title: this.title,
updatedAt: this.updatedAt,
status: this.status,
changesets: this.changesets,
changes: this.changes,
modelId: this.modelId,
mode: this.mode,

View File

@@ -35,6 +35,7 @@ function createSession(id: string, opts: {
title: observableValue(`title-${id}`, id),
updatedAt: observableValue(`updatedAt-${id}`, updatedAt),
status: observableValue(`status-${id}`, SessionStatus.Completed),
changesets: observableValue(`changesets-${id}`, []),
changes: observableValue(`changes-${id}`, []),
modelId: observableValue(`modelId-${id}`, undefined),
mode: observableValue(`mode-${id}`, undefined),

View File

@@ -28,6 +28,7 @@ function createSession(id: string): ISession {
title: observableValue(`title-${id}`, id),
updatedAt: observableValue(`updatedAt-${id}`, new Date()),
status: observableValue(`status-${id}`, SessionStatus.Completed),
changesets: observableValue(`changesets-${id}`, []),
changes: observableValue(`changes-${id}`, []),
modelId: observableValue(`modelId-${id}`, undefined),
mode: observableValue(`mode-${id}`, undefined),

View File

@@ -64,6 +64,7 @@ function makeAgentSession(opts: {
title: observableValue('test.title', 'Test Session'),
updatedAt: observableValue('test.updatedAt', new Date()),
status: observableValue('test.status', 0),
changesets: observableValue('test.changesets', []),
changes: observableValue('test.changes', []),
modelId: observableValue('test.modelId', undefined),
mode: observableValue('test.mode', undefined),
@@ -83,6 +84,7 @@ function makeAgentSession(opts: {
title: chat.title,
updatedAt: chat.updatedAt,
status: chat.status,
changesets: chat.changesets,
changes: chat.changes,
modelId: chat.modelId,
mode: chat.mode,
@@ -113,6 +115,7 @@ function makeNonAgentSession(opts: { repository?: URI; worktree?: URI; providerT
title: observableValue('test.title', 'Test Session'),
updatedAt: observableValue('test.updatedAt', new Date()),
status: observableValue('test.status', 0),
changesets: observableValue('test.changesets', []),
changes: observableValue('test.changes', []),
modelId: observableValue('test.modelId', undefined),
mode: observableValue('test.mode', undefined),
@@ -132,6 +135,7 @@ function makeNonAgentSession(opts: { repository?: URI; worktree?: URI; providerT
title: chat.title,
updatedAt: chat.updatedAt,
status: chat.status,
changesets: chat.changesets,
changes: chat.changes,
modelId: chat.modelId,
mode: chat.mode,

View File

@@ -150,6 +150,19 @@ export interface IGitHubInfo {
export type ISessionFileChange = IChatSessionFileChange | IChatSessionFileChange2;
export interface ISessionChangeset {
/** Unique identifier for the changeset. */
readonly id: string;
/** Display label for the changeset. */
readonly label: string;
/** Optional description for the changeset. */
readonly description?: string;
/** Whether the changeset is enabled. */
readonly enabled: IObservable<boolean>;
/** File changes associated with this changeset. */
readonly changes: IObservable<readonly ISessionFileChange[]>;
}
/**
* A single chat within a session, produced by the sessions management layer.
*/
@@ -169,6 +182,8 @@ export interface IChat {
readonly status: IObservable<SessionStatus>;
/** File changes produced by the chat. */
readonly changes: IObservable<readonly ISessionFileChange[]>;
/** Changesets produced by the chat. */
readonly changesets: IObservable<readonly ISessionChangeset[]>;
/** Currently selected model identifier. */
readonly modelId: IObservable<string | undefined>;
/** Currently selected mode identifier and kind. */
@@ -213,6 +228,8 @@ export interface ISession {
readonly status: IObservable<SessionStatus>;
/** File changes produced by the session. */
readonly changes: IObservable<readonly ISessionFileChange[]>;
/** Changesets produced by the session. */
readonly changesets: IObservable<readonly ISessionChangeset[]>;
/** Currently selected model identifier. */
readonly modelId: IObservable<string | undefined>;
/** Currently selected mode identifier and kind. */

View File

@@ -18,6 +18,7 @@ const stubChat: IChat = {
title: constObservable('Chat'),
updatedAt: constObservable(new Date()),
status: constObservable(0),
changesets: constObservable([]),
changes: constObservable([]),
modelId: constObservable(undefined),
mode: constObservable(undefined),
@@ -37,6 +38,7 @@ function stubSession(overrides: Partial<ISession> & Pick<ISession, 'sessionId' |
title: constObservable('Test'),
updatedAt: constObservable(new Date()),
status: constObservable(0),
changesets: constObservable([]),
changes: constObservable([]),
modelId: constObservable(undefined),
mode: constObservable(undefined),