From 70072c5e4e8c7abd00a20b977808c67431147fb5 Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 27 Mar 2026 12:54:22 +0100 Subject: [PATCH 1/2] sessions - add approval timestamp to session approval info and update approval retrieval logic --- .../sessions/browser/views/sessionsList.ts | 25 +++++++++++++++---- .../agentSessionApprovalModel.ts | 2 ++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts b/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts index b8d6f8234d5..95218d4cc52 100644 --- a/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts +++ b/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts @@ -14,7 +14,7 @@ import { Emitter, Event } from '../../../../../base/common/event.js'; import { FuzzyScore } from '../../../../../base/common/filters.js'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; import { MarkdownString } from '../../../../../base/common/htmlContent.js'; -import { autorun } from '../../../../../base/common/observable.js'; +import { IReader, autorun } from '../../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../../base/common/themables.js'; import { URI } from '../../../../../base/common/uri.js'; import { fromNow } from '../../../../../base/common/date.js'; @@ -32,7 +32,7 @@ import { asCssVariable } from '../../../../../platform/theme/common/colorUtils.j import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; import { GITHUB_REMOTE_FILE_SCHEME, ISessionData, ISessionWorkspace, SessionStatus } from '../../common/sessionData.js'; import { ISessionsManagementService } from '../sessionsManagementService.js'; -import { AgentSessionApprovalModel } from '../../../../../workbench/contrib/chat/browser/agentSessions/agentSessionApprovalModel.js'; +import { AgentSessionApprovalModel, IAgentSessionApprovalInfo } from '../../../../../workbench/contrib/chat/browser/agentSessions/agentSessionApprovalModel.js'; import { Button } from '../../../../../base/browser/ui/button/button.js'; import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js'; import { Separator } from '../../../../../base/common/actions.js'; @@ -103,7 +103,7 @@ class SessionsTreeDelegate implements IListVirtualDelegate { let height = SessionsTreeDelegate.ITEM_HEIGHT; if (this._approvalModel) { - const approval = this._approvalModel.getApproval(element.resource).get(); + const approval = getFirstApprovalAcrossChats(this._approvalModel, element as ISessionData, undefined); if (approval) { height += SessionItemRenderer.getApprovalRowHeight(approval.label); } @@ -369,7 +369,7 @@ class SessionItemRenderer implements ITreeRenderer { buttonStore.clear(); - const info = approvalModel.getApproval(element.resource).read(reader); + const info = getFirstApprovalAcrossChats(approvalModel, element, reader); const visible = !!info; template.approvalRow.classList.toggle('visible', visible); @@ -1175,6 +1175,21 @@ export class SessionsList extends Disposable implements ISessionsList { //#endregion +//#region Approval Helpers + +function getFirstApprovalAcrossChats(approvalModel: AgentSessionApprovalModel, session: ISessionData, reader: IReader | undefined,): IAgentSessionApprovalInfo | undefined { + let oldest: IAgentSessionApprovalInfo | undefined; + for (const chat of session.chats.read(reader)) { + const approval = approvalModel.getApproval(chat.resource).read(reader); + if (approval && (!oldest || approval.since.getTime() < oldest.since.getTime())) { + oldest = approval; + } + } + return oldest; +} + +//#endregion + //#region Sorting & Grouping Helpers export function sortSessions(sessions: ISessionData[], sorting: SessionsSorting): ISessionData[] { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionApprovalModel.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionApprovalModel.ts index 5b2aa56855a..42eb85316ff 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionApprovalModel.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionApprovalModel.ts @@ -15,6 +15,7 @@ import { ILanguageService } from '../../../../../editor/common/languages/languag export interface IAgentSessionApprovalInfo { readonly label: string; readonly languageId: string | undefined; + readonly since: Date; confirm(): void; } @@ -112,6 +113,7 @@ export class AgentSessionApprovalModel extends Disposable { setIfChanged({ label, languageId, + since: new Date(), confirm: () => confirmState.confirm({ type: ToolConfirmKind.UserAction }), }); return; From 6bfb9bcca9e89fad7224544b7cc92182825f755d Mon Sep 17 00:00:00 2001 From: BeniBenj Date: Fri, 27 Mar 2026 13:06:55 +0100 Subject: [PATCH 2/2] fix: add missing 'since' property to IAgentSessionApprovalInfo literals in fixture --- .../componentFixtures/agentSessionsViewer.fixture.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/vs/workbench/test/browser/componentFixtures/agentSessionsViewer.fixture.ts b/src/vs/workbench/test/browser/componentFixtures/agentSessionsViewer.fixture.ts index 2fcc86f53b8..eada5fec15b 100644 --- a/src/vs/workbench/test/browser/componentFixtures/agentSessionsViewer.fixture.ts +++ b/src/vs/workbench/test/browser/componentFixtures/agentSessionsViewer.fixture.ts @@ -513,6 +513,7 @@ export default defineThemedFixtureGroup({ const approvalModel = createMockApprovalModel(resource, { label: '{ "action": "deleteFile", "path": "/src/old-module.ts" }', languageId: 'json', + since: new Date(), confirm: () => { }, }); renderSessionItem(ctx, createMockSession({ @@ -535,6 +536,7 @@ export default defineThemedFixtureGroup({ const approvalModel = createMockApprovalModel(resource, { label: 'npm install --save express@latest', languageId: 'sh', + since: new Date(), confirm: () => { }, }); renderSessionItem(ctx, createMockSession({ @@ -557,6 +559,7 @@ export default defineThemedFixtureGroup({ const approvalModel = createMockApprovalModel(resource, { label: 'Start-Job -ScriptBlock { Set-Location \'c:\\some\\path\'; npm install } | Out-Null', languageId: 'pwsh', + since: new Date(), confirm: () => { }, }); renderSessionItem(ctx, createMockSession({ @@ -579,6 +582,7 @@ export default defineThemedFixtureGroup({ const approvalModel = createMockApprovalModel(resource, { label: 'rm -rf node_modules && npm cache clean --force && npm install --legacy-peer-deps --ignore-scripts', languageId: 'sh', + since: new Date(), confirm: () => { }, }); renderSessionItem(ctx, createMockSession({ @@ -603,6 +607,7 @@ export default defineThemedFixtureGroup({ const approvalModel = createMockApprovalModel(resource, { label: 'npm install --save express@latest', languageId: 'sh', + since: new Date(), confirm: () => { }, }); renderSessionItem(ctx, createMockSession({ @@ -625,6 +630,7 @@ export default defineThemedFixtureGroup({ const approvalModel = createMockApprovalModel(resource, { label: 'cd /workspace/project\nnpm install', languageId: 'sh', + since: new Date(), confirm: () => { }, }); renderSessionItem(ctx, createMockSession({ @@ -647,6 +653,7 @@ export default defineThemedFixtureGroup({ const approvalModel = createMockApprovalModel(resource, { label: 'cd /workspace/project\nnpm install\nnpm run build', languageId: 'sh', + since: new Date(), confirm: () => { }, }); renderSessionItem(ctx, createMockSession({ @@ -669,6 +676,7 @@ export default defineThemedFixtureGroup({ const approvalModel = createMockApprovalModel(resource, { label: 'cd /workspace/project\nnpm install\nnpm run build\nnpm run test -- --coverage', languageId: 'sh', + since: new Date(), confirm: () => { }, }); renderSessionItem(ctx, createMockSession({ @@ -691,6 +699,7 @@ export default defineThemedFixtureGroup({ const approvalModel = createMockApprovalModel(resource, { label: 'RUSTFLAGS="-C target-cpu=native -C opt-level=3" cargo build --release --target x86_64-unknown-linux-gnu\nfind ./target/release -name "*.so" -exec strip --strip-unneeded {} \\; && tar czf release-bundle.tar.gz -C target/release .\ncurl -X POST https://deploy.internal.example.com/api/v2/artifacts/upload --header "Authorization: Bearer $DEPLOY_TOKEN" --form "bundle=@release-bundle.tar.gz"', languageId: 'sh', + since: new Date(), confirm: () => { }, }); renderSessionItem(ctx, createMockSession({