sessionDataService.ts ×12

Frontier kind: Code frontier

unlabeled · c_ac88fbb8b066

11 tests · 20633 LOC · 63 files · introduces 0 tests · 90 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
12 ranges90 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1952 ranges20633 lines · 63 files · Browse complete extent
All tests (intent)
11 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 90 introduced LOC across 12 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/sessionDataService.ts 90 introduced LOC · 12 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- sessionDataService.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { IReference, ReferenceCollection } from '../../../base/common/lifecycle.js';
7 > import { URI } from '../../../base/common/uri.js';
8 > import { Emitter, Event } from '../../../base/common/event.js';
9 > import { IFileService } from '../../files/common/files.js';
10 > import { ILogService } from '../../log/common/log.js';
11 > import { AgentSession } from '../common/agentService.js';
12 > import { ISessionDatabase, ISessionDataService, IWillDeleteSessionDataEvent, SESSION_DB_FILENAME } from '../common/sessionDataService.js';
13 > import { SessionDatabase } from './sessionDatabase.js';
14 >
15 > class SessionDatabaseCollection extends ReferenceCollection<ISessionDatabase> {
16 >
17 > /**
18 > * The set of currently-open databases. Mirrors what's held by the
19 > * underlying ref-counted map, but exposed so {@link SessionDataService.whenIdle}
20 > * can iterate without reaching into private state.
21 > */
22 > readonly liveDatabases = new Set<ISessionDatabase>();
23 >
24 > constructor(
25 > private readonly _getDbPath: (key: string) => string,
26 > private readonly _logService: ILogService,
27 > ) {
28 > super();
29 > }
30 >
31 > protected createReferencedObject(key: string): ISessionDatabase {
32 const dbPath = this._getDbPath(key);
33 this._logService.trace(`[SessionDataService] Opening database: ${dbPath}`);
36 return db;
37 }
39 > protected destroyReferencedObject(_key: string, object: ISessionDatabase): void {
40 this.liveDatabases.delete(object);
41 object.dispose();
42 }
44 >
45 > /**
46 > * Implementation of {@link ISessionDataService} that stores per-session data
47 > * under `{userDataPath}/agentSessionData/{sessionId}/`.
48 > */
49 > export class SessionDataService implements ISessionDataService {
50 > declare readonly _serviceBrand: undefined;
51 >
52 > private readonly _basePath: URI;
53 > private readonly _databases: SessionDatabaseCollection;
54 > private readonly _onWillDeleteSessionData = new Emitter<IWillDeleteSessionDataEvent>();
55 >
56 > get onWillDeleteSessionData(): Event<IWillDeleteSessionDataEvent> {
57 > return this._onWillDeleteSessionData.event;
58 > }
59 >
60 > constructor(
61 > userDataPath: URI,
62 > @IFileService private readonly _fileService: IFileService,
63 > @ILogService private readonly _logService: ILogService,
64 > getDbPath?: (key: string) => string, // for testing
65 > ) {
66 > this._basePath = URI.joinPath(userDataPath, 'agentSessionData');
67 > this._databases = new SessionDatabaseCollection(
68 > getDbPath ?? (key => URI.joinPath(this._basePath, key, SESSION_DB_FILENAME).fsPath),
69 > this._logService,
70 > );
71 > }
72 >
73 > getSessionDataDir(session: URI): URI {
74 return URI.joinPath(this._basePath, this._sanitizedSessionKey(session));
75 }
77 > getSessionDataDirById(sessionId: string): URI {
78 const sanitized = sessionId.replace(/[^a-zA-Z0-9_.-]/g, '-');
79 return URI.joinPath(this._basePath, sanitized);
80 }
82 > private _sanitizedSessionKey(session: URI): string {
83 return this._dataKey(session).replace(/[^a-zA-Z0-9_.-]/g, '-');
84 }
86 > /**
87 > * Derives the per-URI storage key. Chat channel URIs
88 > * (`ahp-chat://<chatId>/<base64(session)>`) carry the chat id in the
89 > * authority while encoding the SAME owning-session URI in the path, so
90 > * keying only by the path (via {@link AgentSession.id}) would collapse
91 > * every peer chat of a session onto one data directory and database.
92 > * Prefixing with the authority gives each chat its own storage while
93 > * leaving plain session URIs (no authority) unchanged.
94 > */
95 > private _dataKey(uri: URI): string {
96 const id = AgentSession.id(uri);
97 return uri.authority ? `${uri.authority}-${id}` : id;
98 }
100 > openDatabase(session: URI): IReference<ISessionDatabase> {
101 return this._databases.acquire(this._sanitizedSessionKey(session));
102 }
104 > async tryOpenDatabase(session: URI): Promise<IReference<ISessionDatabase> | undefined> {
105 const key = this._sanitizedSessionKey(session);
106 const dbPath = URI.joinPath(this._basePath, key, SESSION_DB_FILENAME);
110 return this._databases.acquire(key);
111 }
113 > async deleteSessionData(session: URI): Promise<void> {
114 const dir = this.getSessionDataDir(session);
115 // Fire the will-delete event first so subscribers (notably the
143 }
144 }
146 > async cleanupOrphanedData(knownSessionIds: Set<string>): Promise<void> {
147 try {
148 const exists = await this._fileService.exists(this._basePath);
177 }
178 }
180 > async whenIdle(): Promise<void> {
181 // Each `SessionDatabase.whenIdle()` already loops internally until
182 // that DB is quiescent, so the outer loop only needs to handle the