agentHostStateManager.ts ×16

Frontier kind: Code frontier

unlabeled · c_683dfa1b509c

186 tests · 19791 LOC · 83 files · introduces 0 tests · 75 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
16 ranges75 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1960 ranges19791 lines · 83 files · Browse complete extent
All tests (intent)
186 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: 75 introduced LOC across 16 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/agentHostStateManager.ts 75 introduced LOC · 16 ranges

Open complete file

1208
1209 if (isChatAction(action)) {
1210 > if (!isAhpChatChannel(channel)) { agentHostStateManager.ts
1211 throw new Error(`[AgentHostStateManager] Chat action dispatched to non-chat channel: ${channel}, type=${action.type}`);
1212 }
1214 > const chatAction = action as ChatAction;
1215 > const sessionKey = parseRequiredSessionUriFromChatUri(channel);
1216 > const chat = this._chatStates.get(channel);
1217 > if (chat && sessionKey !== undefined) {
1218 > const newChat = chatReducer(chat, chatAction, this._log);
1219 > this._chatStates.set(channel, newChat);
1220 > this._onChatStateChanged(sessionKey, channel, chat, newChat);
1221 > resultingState = newChat;
1222 > } else {
1223 this._logService.warn(`[AgentHostStateManager] Action for unknown chat: ${channel}, type=${action.type}`);
1224 }
1226
1227 if (isChangesetAction(action)) {
1310 */
1311 private _onChatStateChanged(sessionKey: string, chatUri: string, prev: ChatState, next: ChatState): void {
1312 > // Active turn tracking — derive from the reducer's view of state, agentHostStateManager.ts
1313 > // never from raw action turn-ids, so out-of-order lifecycle actions
1314 > // can't desync the count from reality. Track active turns per chat so a
1315 > // session stays active until ALL of its concurrent chat turns finish;
1316 > // only notify when the session's overall active state actually flips.
1317 > const hadActive = !!prev.activeTurn;
1318 > const hasActive = !!next.activeTurn;
1319 > if (hadActive !== hasActive) {
1320 if (hasActive) {
1321 let activeChats = this._sessionsWithActiveTurn.get(sessionKey);
1334 }
1335 }
1337 > const entry = this._sessionStates.get(sessionKey);
1338 > if (!entry) {
1339 return;
1340 }
1341 > const sessionState = entry.state; agentHostStateManager.ts
1342 >
1343 > // Mirror denormalized chat summary fields onto the session, aggregating
1344 > // across the whole chat catalog per the SessionSummary rules.
1345 > const nextEntry = chatSummaryFromState(next);
1346 > const prevEntry = sessionState.chats.find(c => c.resource === chatUri);
1347 > const chats = sessionState.chats.map(c => c.resource === chatUri ? nextEntry : c);
1348 >
1349 > // Forward the chat's own status to the session catalog so full
1350 > // SessionState subscribers (the per-chat tabs) reflect this chat's
1351 > // progress — not just the aggregated session summary. Status changes
1352 > // at most a couple of times per turn, so this won't flood the channel.
1353 > if (prevEntry?.status !== nextEntry.status) {
1354 this.dispatchServerAction(sessionKey, {
1355 type: ActionType.SessionChatUpdated,
1358 });
1359 }
1361 > const aggregate = this._aggregateChatSummaries(chats, sessionState.defaultChat);
1362 > const newStatus = aggregate.status !== undefined ? this._mergeSessionStatus(sessionState.status, aggregate.status) : sessionState.status;
1363 > const statusChanged = newStatus !== sessionState.status;
1364 > const activityChanged = aggregate.activity !== sessionState.activity;
1365 > entry.state = {
1366 > ...sessionState,
1367 > chats,
1368 > ...(statusChanged ? { status: newStatus } : undefined),
1369 > ...(activityChanged ? { activity: aggregate.activity } : undefined),
1370 > };
1371 >
1372 > // Roll the aggregated `modifiedAt` into the catalog-only timestamp.
1373 > const newModifiedAt = aggregate.modifiedAt !== undefined ? new Date(aggregate.modifiedAt).toISOString() : undefined;
1374 > const modifiedAtChanged = newModifiedAt !== undefined && newModifiedAt !== entry.modifiedAt;
1375 > if (modifiedAtChanged) {
1376 entry.modifiedAt = newModifiedAt;
1377 }
1379 > if (statusChanged || activityChanged || modifiedAtChanged) {
1380 this._summaryNotifier.markDirty(sessionKey);
1381 }
1383
1384 /**
1393 */
1394 private _aggregateChatSummaries(chats: readonly ChatSummary[], defaultChat: URI | undefined): { status?: SessionStatus; activity?: string; modifiedAt?: number } {
1395 > if (chats.length === 0) { agentHostStateManager.ts
1396 return {};
1397 }
1398 > const activityMask = ~(SessionStatus.IsRead | SessionStatus.IsArchived); agentHostStateManager.ts
1399 > const base = (defaultChat !== undefined ? chats.find(c => c.resource === defaultChat) : undefined)
1400 ?? chats.reduce((a, b) => Date.parse(b.modifiedAt) > Date.parse(a.modifiedAt) ? b : a);
1401 > let status = base.status & activityMask; agentHostStateManager.ts
1402 > let driver = base;
1403 > const errorChat = chats.find(c => (c.status & SessionStatus.Error) === SessionStatus.Error);
1404 > const inputChat = chats.find(c => (c.status & SessionStatus.InputNeeded) === SessionStatus.InputNeeded);
1405 > // `InputNeeded` is a superset of the `InProgress` bit, so exclude
1406 > // input-needed chats here to find one that is purely streaming.
1407 > const inProgressChat = chats.find(c => (c.status & SessionStatus.InputNeeded) === SessionStatus.InProgress);
1408 > if (inputChat) {
1409 status = SessionStatus.InputNeeded;
1410 driver = inputChat;
1411 > } else if (errorChat) { agentHostStateManager.ts
1412 status = SessionStatus.Error;
1413 driver = errorChat;
1414 > } else if (inProgressChat) { agentHostStateManager.ts
1415 status = SessionStatus.InProgress;
1416 driver = inProgressChat;
1417 }
1418 > const modifiedAt = chats.reduce((max, c) => Math.max(max, Date.parse(c.modifiedAt)), 0); agentHostStateManager.ts
1419 > return { status, activity: driver.activity, modifiedAt };
1420 > }
1421
1422 /**
1426 */
1427 private _mergeSessionStatus(sessionStatus: SessionStatus, chatStatus: SessionStatus): SessionStatus {
1428 > const metaFlags = sessionStatus & (SessionStatus.IsRead | SessionStatus.IsArchived); agentHostStateManager.ts
1429 > const activityBits = chatStatus & ~(SessionStatus.IsRead | SessionStatus.IsArchived);
1430 > return activityBits | metaFlags;
1431 > }
1432
1433 /**