mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
Subagents render as a single line, similar to 'collapsed' thinking mode (#290059)
* Subagents render as a single line, similar to 'collapsed' thinking mode * Remove now-unused eslint disable directives
This commit is contained in:
@@ -2120,4 +2120,21 @@ export default tseslint.config(
|
||||
'@typescript-eslint/consistent-generic-constructors': ['warn', 'constructor'],
|
||||
}
|
||||
},
|
||||
// Allow querySelector/querySelectorAll in test files - it's acceptable for test assertions
|
||||
{
|
||||
files: [
|
||||
'src/**/test/**/*.ts',
|
||||
'extensions/**/test/**/*.ts',
|
||||
],
|
||||
rules: {
|
||||
'no-restricted-syntax': [
|
||||
'warn',
|
||||
// Keep the Intl helper restriction even in tests
|
||||
{
|
||||
'selector': `NewExpression[callee.object.name='Intl']`,
|
||||
'message': 'Use safeIntl helper instead for safe and lazy use of potentially expensive Intl methods.'
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import assert from 'assert';
|
||||
import { fillInIncompleteTokens, renderMarkdown, renderAsPlaintext } from '../../browser/markdownRenderer.js';
|
||||
import { IMarkdownString, MarkdownString } from '../../common/htmlContent.js';
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import assert from 'assert';
|
||||
import { Sash, SashState } from '../../../../browser/ui/sash/sash.js';
|
||||
import { IView, LayoutPriority, Sizing, SplitView } from '../../../../browser/ui/splitview/splitview.js';
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import assert from 'assert';
|
||||
import { IIdentityProvider, IListVirtualDelegate } from '../../../../browser/ui/list/list.js';
|
||||
import { AsyncDataTree, CompressibleAsyncDataTree, ITreeCompressionDelegate } from '../../../../browser/ui/tree/asyncDataTree.js';
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import assert from 'assert';
|
||||
import { IIdentityProvider, IListVirtualDelegate } from '../../../../browser/ui/list/list.js';
|
||||
import { ICompressedTreeNode } from '../../../../browser/ui/tree/compressedObjectTreeModel.js';
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import assert from 'assert';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
|
||||
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
|
||||
|
||||
@@ -546,7 +546,6 @@ suite('HoverService', () => {
|
||||
hoverService.showAndFocusLastHover();
|
||||
|
||||
// Verify there is a hover in the DOM (it's a new hover instance)
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const hoverElements = mainWindow.document.querySelectorAll('.monaco-hover');
|
||||
assert.ok(hoverElements.length > 0, 'A hover should be recreated and in the DOM');
|
||||
|
||||
@@ -554,10 +553,8 @@ suite('HoverService', () => {
|
||||
hoverService.hideHover(true);
|
||||
|
||||
// Verify cleanup
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const remainingHovers = mainWindow.document.querySelectorAll('.monaco-hover');
|
||||
assert.strictEqual(remainingHovers.length, 0, 'No hovers should remain in DOM after cleanup');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import { CollapsibleListPool } from './chatReferencesContentPart.js';
|
||||
import { EditorPool } from './chatContentCodePools.js';
|
||||
import { CodeBlockModelCollection } from '../../../common/widget/codeBlockModelCollection.js';
|
||||
import { ChatToolInvocationPart } from './toolInvocationParts/chatToolInvocationPart.js';
|
||||
import { IChatMarkdownAnchorService } from './chatMarkdownAnchorService.js';
|
||||
import { MarkdownString } from '../../../../../../base/common/htmlContent.js';
|
||||
import './media/chatSubagentContent.css';
|
||||
|
||||
const MAX_TITLE_LENGTH = 100;
|
||||
@@ -62,6 +64,14 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
private pendingPromptRender: boolean = false;
|
||||
private pendingResultText: string | undefined;
|
||||
|
||||
// Current tool message for collapsed title (persists even after tool completes)
|
||||
private currentRunningToolMessage: string | undefined;
|
||||
|
||||
// Confirmation auto-expand tracking
|
||||
private toolsWaitingForConfirmation: number = 0;
|
||||
private userManuallyExpanded: boolean = false;
|
||||
private autoExpandedForConfirmation: boolean = false;
|
||||
|
||||
/**
|
||||
* Extracts subagent info (description, agentName, prompt) from a tool invocation.
|
||||
*/
|
||||
@@ -108,6 +118,7 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
private readonly codeBlockModelCollection: CodeBlockModelCollection,
|
||||
private readonly announcedToolProgressKeys: Set<string>,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IChatMarkdownAnchorService private readonly chatMarkdownAnchorService: IChatMarkdownAnchorService,
|
||||
@IHoverService hoverService: IHoverService,
|
||||
) {
|
||||
// Extract description, agentName, and prompt from toolInvocation
|
||||
@@ -135,8 +146,8 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
|
||||
this._register(autorun(r => {
|
||||
this.expanded.read(r);
|
||||
if (this._collapseButton && this.wrapper) {
|
||||
if (this.wrapper.classList.contains('chat-thinking-streaming') && !this.element.isComplete && this.isActive) {
|
||||
if (this._collapseButton) {
|
||||
if (!this.element.isComplete && this.isActive) {
|
||||
this._collapseButton.icon = ThemeIcon.modify(Codicon.loading, 'spin');
|
||||
} else {
|
||||
this._collapseButton.icon = Codicon.check;
|
||||
@@ -155,6 +166,27 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
// Start collapsed - fixed scrolling mode shows limited height when collapsed
|
||||
this.setExpanded(false);
|
||||
|
||||
// Track user manual expansion
|
||||
// If the user expands (not via auto-expand for confirmation), mark it as manual
|
||||
// Only clear autoExpandedForConfirmation when user collapses, so re-expand is detected as manual
|
||||
this._register(autorun(r => {
|
||||
const expanded = this._isExpanded.read(r);
|
||||
if (expanded) {
|
||||
if (!this.autoExpandedForConfirmation) {
|
||||
this.userManuallyExpanded = true;
|
||||
}
|
||||
} else {
|
||||
// User collapsed - reset flags so next confirmation cycle can auto-collapse again
|
||||
if (this.autoExpandedForConfirmation) {
|
||||
this.autoExpandedForConfirmation = false;
|
||||
}
|
||||
// Reset manual expansion flag when user collapses, so future confirmation cycles can auto-collapse
|
||||
if (this.userManuallyExpanded) {
|
||||
this.userManuallyExpanded = false;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Scheduler for coalescing layout operations
|
||||
this.layoutScheduler = this._register(new AnimationFrameScheduler(this.domNode, () => this.performLayout()));
|
||||
|
||||
@@ -166,17 +198,17 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
}
|
||||
|
||||
protected override initContent(): HTMLElement {
|
||||
const baseClasses = '.chat-used-context-list.chat-thinking-collapsible';
|
||||
const classes = this.isInitiallyComplete
|
||||
? baseClasses
|
||||
: `${baseClasses}.chat-thinking-streaming`;
|
||||
this.wrapper = $(classes);
|
||||
this.wrapper = $('.chat-used-context-list.chat-thinking-collapsible');
|
||||
|
||||
// Hide initially until there are tool calls
|
||||
if (!this.hasToolItems) {
|
||||
this.wrapper.style.display = 'none';
|
||||
}
|
||||
|
||||
// Materialize any deferred content now that wrapper exists
|
||||
// This handles the case where the subclass autorun ran before this base class autorun
|
||||
this.materializePendingContent();
|
||||
|
||||
// Use ResizeObserver to trigger layout when wrapper content changes
|
||||
const resizeObserver = this._register(new DisposableResizeObserver(() => this.layoutScheduler.schedule()));
|
||||
this._register(resizeObserver.observe(this.wrapper));
|
||||
@@ -186,15 +218,16 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
|
||||
/**
|
||||
* Renders the prompt as a collapsible section at the start of the content.
|
||||
* If the subagent is initially complete (old/restored), this is deferred until expanded.
|
||||
* If the wrapper doesn't exist yet (lazy init) or subagent is initially complete,
|
||||
* this is deferred until expanded.
|
||||
*/
|
||||
private renderPromptSection(): void {
|
||||
if (!this.prompt || this.promptContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Defer rendering for old completed subagents until expanded
|
||||
if (this.isInitiallyComplete && !this.isExpanded() && !this.hasExpandedOnce) {
|
||||
// Defer rendering when wrapper doesn't exist yet (lazy init) or for old completed subagents until expanded
|
||||
if (!this.wrapper || (this.isInitiallyComplete && !this.isExpanded() && !this.hasExpandedOnce)) {
|
||||
this.pendingPromptRender = true;
|
||||
return;
|
||||
}
|
||||
@@ -245,6 +278,11 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
} else {
|
||||
dom.append(this.wrapper, this.promptContainer);
|
||||
}
|
||||
|
||||
// Show the container if it was hidden (no tool items yet)
|
||||
if (this.wrapper.style.display === 'none') {
|
||||
this.wrapper.style.display = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,10 +292,6 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
|
||||
public markAsInactive(): void {
|
||||
this.isActive = false;
|
||||
// With lazy rendering, wrapper may not be created yet if content hasn't been expanded
|
||||
if (this.wrapper) {
|
||||
this.wrapper.classList.remove('chat-thinking-streaming');
|
||||
}
|
||||
if (this._collapseButton) {
|
||||
this._collapseButton.icon = Codicon.check;
|
||||
}
|
||||
@@ -274,11 +308,59 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
}
|
||||
|
||||
private updateTitle(): void {
|
||||
if (this._collapseButton) {
|
||||
const prefix = this.agentName || localize('chat.subagent.prefix', 'Subagent');
|
||||
const finalLabel = `${prefix}: ${this.description}`;
|
||||
this._collapseButton.label = finalLabel;
|
||||
const prefix = this.agentName || localize('chat.subagent.prefix', 'Subagent');
|
||||
let finalLabel = `${prefix}: ${this.description}`;
|
||||
if (this.currentRunningToolMessage && this.isActive) {
|
||||
finalLabel += ` \u2014 ${this.currentRunningToolMessage}`;
|
||||
}
|
||||
this.setTitleWithWidgets(new MarkdownString(finalLabel), this.instantiationService, this.chatMarkdownAnchorService, this.chatContentMarkdownRenderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks a tool invocation's state for:
|
||||
* 1. Updating the title with the current tool message (persists even after completion)
|
||||
* 2. Auto-expanding when a tool is waiting for confirmation
|
||||
* 3. Auto-collapsing when the confirmation is addressed
|
||||
* This method is public to support testing.
|
||||
*/
|
||||
public trackToolState(toolInvocation: IChatToolInvocation | IChatToolInvocationSerialized): void {
|
||||
// Only track live tool invocations
|
||||
if (toolInvocation.kind !== 'toolInvocation') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the title immediately when tool is added - like thinking part does
|
||||
const message = toolInvocation.invocationMessage;
|
||||
const messageText = typeof message === 'string' ? message : message.value;
|
||||
this.currentRunningToolMessage = messageText;
|
||||
this.updateTitle();
|
||||
|
||||
let wasWaitingForConfirmation = false;
|
||||
this._register(autorun(r => {
|
||||
const state = toolInvocation.state.read(r);
|
||||
|
||||
// Track confirmation state changes
|
||||
const isWaitingForConfirmation = state.type === IChatToolInvocation.StateKind.WaitingForConfirmation;
|
||||
|
||||
if (isWaitingForConfirmation && !wasWaitingForConfirmation) {
|
||||
// Tool just started waiting for confirmation
|
||||
this.toolsWaitingForConfirmation++;
|
||||
if (!this.isExpanded()) {
|
||||
this.autoExpandedForConfirmation = true;
|
||||
this.setExpanded(true);
|
||||
}
|
||||
} else if (!isWaitingForConfirmation && wasWaitingForConfirmation) {
|
||||
// Tool is no longer waiting for confirmation
|
||||
this.toolsWaitingForConfirmation--;
|
||||
if (this.toolsWaitingForConfirmation === 0 && this.autoExpandedForConfirmation && !this.userManuallyExpanded) {
|
||||
// Auto-collapse only if we auto-expanded and user didn't manually expand
|
||||
this.autoExpandedForConfirmation = false;
|
||||
this.setExpanded(false);
|
||||
}
|
||||
}
|
||||
|
||||
wasWaitingForConfirmation = isWaitingForConfirmation;
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,15 +411,16 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
|
||||
/**
|
||||
* Renders the result text as a collapsible section.
|
||||
* If the subagent is initially complete (old/restored), this is deferred until expanded.
|
||||
* If the wrapper doesn't exist yet (lazy init) or subagent is initially complete,
|
||||
* this is deferred until expanded.
|
||||
*/
|
||||
public renderResultText(resultText: string): void {
|
||||
if (this.resultContainer || !resultText) {
|
||||
return; // Already rendered or no content
|
||||
}
|
||||
|
||||
// Defer rendering for old completed subagents until expanded
|
||||
if (this.isInitiallyComplete && !this.isExpanded() && !this.hasExpandedOnce) {
|
||||
// Defer rendering when wrapper doesn't exist yet (lazy init) or for old completed subagents until expanded
|
||||
if (!this.wrapper || (this.isInitiallyComplete && !this.isExpanded() && !this.hasExpandedOnce)) {
|
||||
this.pendingResultText = resultText;
|
||||
return;
|
||||
}
|
||||
@@ -406,15 +489,15 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
}
|
||||
}
|
||||
|
||||
// Render immediately if:
|
||||
// - The section is expanded
|
||||
// - It has been expanded once before
|
||||
// - It's actively streaming (not an old completed subagent being restored)
|
||||
if (this.isExpanded() || this.hasExpandedOnce || !this.isInitiallyComplete) {
|
||||
// Track tool state for title updates and auto-expand/collapse on confirmation
|
||||
this.trackToolState(toolInvocation);
|
||||
|
||||
// Render immediately only if already expanded or has been expanded before
|
||||
if (this.isExpanded() || this.hasExpandedOnce) {
|
||||
const part = this.createToolPart(toolInvocation, codeBlockStartIndex);
|
||||
this.appendToolPartToDOM(part, toolInvocation);
|
||||
} else {
|
||||
// Defer rendering until expanded (for old completed subagents)
|
||||
// Defer rendering until expanded
|
||||
const item: ILazyToolItem = {
|
||||
lazy: new Lazy(() => this.createToolPart(toolInvocation, codeBlockStartIndex)),
|
||||
toolInvocation,
|
||||
@@ -425,7 +508,8 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
}
|
||||
|
||||
protected override shouldInitEarly(): boolean {
|
||||
return !this.isInitiallyComplete;
|
||||
// Never init early - subagent is collapsed while running, content only shown on expand
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -494,8 +578,15 @@ export class ChatSubagentContentPart extends ChatCollapsibleContentPart implemen
|
||||
|
||||
/**
|
||||
* Materializes all pending lazy content (prompt, tool items, result) when the section is expanded.
|
||||
* This is called when first expanded, but the wrapper must exist (created by base class initContent).
|
||||
*/
|
||||
private materializePendingContent(): void {
|
||||
// Wrapper may not be created yet if this autorun runs before the base class autorun
|
||||
// that calls initContent(). In that case, initContent() will call this logic.
|
||||
if (!this.wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Render pending prompt section
|
||||
if (this.pendingPromptRender) {
|
||||
this.pendingPromptRender = false;
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import assert from 'assert';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js';
|
||||
import { renderFileWidgets } from '../../../../browser/widget/chatContentParts/chatInlineAnchorWidget.js';
|
||||
@@ -145,4 +143,3 @@ suite('ChatInlineAnchorWidget Metadata Validation', () => {
|
||||
assert.ok(!widget, 'Widget should not be rendered for malformed URI');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* eslint-disable no-restricted-syntax */ // Tests legitimately need querySelector/querySelectorAll for DOM assertions
|
||||
|
||||
import assert from 'assert';
|
||||
import { mainWindow } from '../../../../../../../base/browser/window.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js';
|
||||
|
||||
@@ -95,6 +95,10 @@ suite('ChatSubagentContentPart', () => {
|
||||
return {
|
||||
type: IChatToolInvocation.StateKind.WaitingForConfirmation,
|
||||
parameters,
|
||||
confirmationMessages: {
|
||||
title: 'Confirm action',
|
||||
message: 'Are you sure you want to proceed?'
|
||||
},
|
||||
confirm: () => { }
|
||||
};
|
||||
case IChatToolInvocation.StateKind.WaitingForPostApproval:
|
||||
@@ -117,13 +121,16 @@ suite('ChatSubagentContentPart', () => {
|
||||
|
||||
function createMockToolInvocation(options: {
|
||||
toolId?: string;
|
||||
toolCallId?: string;
|
||||
subAgentInvocationId?: string;
|
||||
toolSpecificData?: IChatSubagentToolInvocationData;
|
||||
stateType?: IChatToolInvocation.StateKind;
|
||||
parameters?: ToolInvocationParameters;
|
||||
invocationMessage?: string;
|
||||
} = {}): IChatToolInvocation {
|
||||
const stateType = options.stateType ?? IChatToolInvocation.StateKind.Streaming;
|
||||
const stateValue = createState(stateType, options.parameters);
|
||||
const toolCallId = options.toolCallId ?? 'tool-call-' + Math.random().toString(36).substring(7);
|
||||
|
||||
const toolInvocation: IChatToolInvocation = {
|
||||
presentation: undefined,
|
||||
@@ -134,11 +141,11 @@ suite('ChatSubagentContentPart', () => {
|
||||
prompt: 'Test prompt'
|
||||
},
|
||||
originMessage: undefined,
|
||||
invocationMessage: 'Running subagent...',
|
||||
invocationMessage: options.invocationMessage ?? 'Running subagent...',
|
||||
pastTenseMessage: undefined,
|
||||
source: ToolDataSource.Internal,
|
||||
toolId: options.toolId ?? RunSubagentTool.Id,
|
||||
toolCallId: options.subAgentInvocationId ?? 'test-tool-call-id',
|
||||
toolCallId: toolCallId,
|
||||
subAgentInvocationId: options.subAgentInvocationId ?? 'test-subagent-id',
|
||||
state: observableValue('state', stateValue),
|
||||
kind: 'toolInvocation',
|
||||
@@ -482,8 +489,10 @@ suite('ChatSubagentContentPart', () => {
|
||||
});
|
||||
|
||||
test('should return true for runSubagent tool using toolCallId as effective ID', () => {
|
||||
const sharedToolCallId = 'shared-tool-call-id';
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolId: RunSubagentTool.Id,
|
||||
toolCallId: sharedToolCallId,
|
||||
subAgentInvocationId: 'call-abc'
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
@@ -492,6 +501,7 @@ suite('ChatSubagentContentPart', () => {
|
||||
|
||||
const otherInvocation = createMockToolInvocation({
|
||||
toolId: RunSubagentTool.Id,
|
||||
toolCallId: sharedToolCallId,
|
||||
subAgentInvocationId: 'call-abc'
|
||||
});
|
||||
|
||||
@@ -575,4 +585,598 @@ suite('ChatSubagentContentPart', () => {
|
||||
assert.strictEqual(button.getAttribute('aria-expanded'), 'true', 'Should have aria-expanded="true" when expanded');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Lazy rendering', () => {
|
||||
test('should defer prompt/result rendering until expanded when initially complete', () => {
|
||||
const serializedInvocation = createMockSerializedToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Completed task',
|
||||
agentName: 'FinishedAgent',
|
||||
prompt: 'Original prompt for the task',
|
||||
result: 'Task completed successfully'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(true); // isComplete = true
|
||||
|
||||
const part = createPart(serializedInvocation, context);
|
||||
|
||||
// Content should be collapsed - no wrapper content initially visible
|
||||
// Just verify that the domNode has the collapsed class
|
||||
assert.ok(part.domNode.classList.contains('chat-used-context-collapsed'), 'Should be collapsed initially');
|
||||
|
||||
// Expand to trigger lazy rendering
|
||||
const button = getCollapseButton(part);
|
||||
assert.ok(button, 'Expand button should exist');
|
||||
button.click();
|
||||
|
||||
// After expanding, the content containers should be rendered
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false, 'Should be expanded');
|
||||
|
||||
// Verify prompt and result sections exist in the expanded content
|
||||
const wrapperContent = part.domNode.querySelector('.chat-used-context-list');
|
||||
assert.ok(wrapperContent, 'Wrapper content should exist after expand');
|
||||
|
||||
// Check that sections were inserted
|
||||
const sections = wrapperContent.querySelectorAll('.chat-subagent-section');
|
||||
assert.ok(sections.length >= 2, 'Should have prompt and result sections after expand');
|
||||
});
|
||||
|
||||
test('should not render wrapper content while subagent is running (truly collapsed)', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Running task',
|
||||
agentName: 'RunningAgent',
|
||||
prompt: 'Prompt text'
|
||||
},
|
||||
stateType: IChatToolInvocation.StateKind.Streaming
|
||||
});
|
||||
const context = createMockRenderContext(false); // Not complete
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// Should be collapsed with just the title visible
|
||||
assert.ok(part.domNode.classList.contains('chat-used-context-collapsed'), 'Should be collapsed while running');
|
||||
|
||||
// Wrapper content should not be initialized yet (lazy)
|
||||
const wrapperContent = part.domNode.querySelector('.chat-used-context-list');
|
||||
assert.strictEqual(wrapperContent, null, 'Wrapper content should not be rendered while running and collapsed');
|
||||
});
|
||||
|
||||
test('should show prompt on expand when no tool items yet', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Starting task',
|
||||
agentName: 'RunningAgent',
|
||||
prompt: 'This is the prompt to execute'
|
||||
},
|
||||
stateType: IChatToolInvocation.StateKind.Streaming
|
||||
});
|
||||
const context = createMockRenderContext(false); // Not complete
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// Initially collapsed with no content
|
||||
assert.ok(part.domNode.classList.contains('chat-used-context-collapsed'), 'Should be collapsed initially');
|
||||
let wrapperContent = part.domNode.querySelector('.chat-used-context-list');
|
||||
assert.strictEqual(wrapperContent, null, 'Wrapper should not exist initially');
|
||||
|
||||
// Expand
|
||||
const button = getCollapseButton(part);
|
||||
assert.ok(button, 'Expand button should exist');
|
||||
button.click();
|
||||
|
||||
// Wrapper should now exist and be visible
|
||||
wrapperContent = part.domNode.querySelector('.chat-used-context-list');
|
||||
assert.ok(wrapperContent, 'Wrapper should exist after expand');
|
||||
|
||||
// Prompt section should be rendered
|
||||
const promptSection = wrapperContent.querySelector('.chat-subagent-section');
|
||||
assert.ok(promptSection, 'Prompt section should be visible after expand');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Current running tool in title', () => {
|
||||
test('should update title with current running tool invocation message', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Working on task',
|
||||
agentName: 'TestAgent'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// Add a child tool invocation
|
||||
const childTool = createMockToolInvocation({
|
||||
toolId: 'readFile',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId,
|
||||
stateType: IChatToolInvocation.StateKind.Executing,
|
||||
invocationMessage: 'Reading config.ts'
|
||||
});
|
||||
|
||||
part.appendToolInvocation(childTool, 0);
|
||||
|
||||
// The title should include the current running tool message
|
||||
const button = getCollapseButton(part);
|
||||
assert.ok(button, 'Should have collapse button');
|
||||
const labelElement = getCollapseButtonLabel(button);
|
||||
const buttonText = labelElement?.textContent ?? button.textContent ?? '';
|
||||
assert.ok(buttonText.includes('Reading config.ts'), 'Title should include current running tool message');
|
||||
});
|
||||
|
||||
test('should show latest tool when multiple tools are added', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Working on task',
|
||||
agentName: 'TestAgent'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// Add first tool
|
||||
const firstTool = createMockToolInvocation({
|
||||
toolId: 'readFile',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId,
|
||||
stateType: IChatToolInvocation.StateKind.Executing,
|
||||
invocationMessage: 'Reading file1.ts'
|
||||
});
|
||||
part.appendToolInvocation(firstTool, 0);
|
||||
|
||||
// Add second tool
|
||||
const secondTool = createMockToolInvocation({
|
||||
toolId: 'searchFiles',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId,
|
||||
stateType: IChatToolInvocation.StateKind.Executing,
|
||||
invocationMessage: 'Searching for patterns'
|
||||
});
|
||||
part.appendToolInvocation(secondTool, 1);
|
||||
|
||||
const button = getCollapseButton(part);
|
||||
assert.ok(button, 'Should have collapse button');
|
||||
const labelElement = getCollapseButtonLabel(button);
|
||||
const buttonText = labelElement?.textContent ?? button.textContent ?? '';
|
||||
// Should show the latest tool message
|
||||
assert.ok(buttonText.includes('Searching for patterns'), 'Title should include latest tool message');
|
||||
});
|
||||
|
||||
test('should keep showing running tool when another tool completes', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Working on task',
|
||||
agentName: 'TestAgent'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// Add first tool (will complete)
|
||||
const firstToolState = observableValue('state', createState(IChatToolInvocation.StateKind.Executing));
|
||||
const firstTool: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'readFile',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: firstToolState,
|
||||
invocationMessage: 'Reading file1.ts'
|
||||
};
|
||||
part.trackToolState(firstTool);
|
||||
|
||||
// Add second tool (will keep running)
|
||||
const secondToolState = observableValue('state', createState(IChatToolInvocation.StateKind.Executing));
|
||||
const secondTool: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'searchFiles',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: secondToolState,
|
||||
invocationMessage: 'Searching for patterns'
|
||||
};
|
||||
part.trackToolState(secondTool);
|
||||
|
||||
// Verify title shows second tool
|
||||
const button = getCollapseButton(part);
|
||||
assert.ok(button, 'Button should exist');
|
||||
const labelElement = getCollapseButtonLabel(button);
|
||||
let buttonText = labelElement?.textContent ?? button?.textContent ?? '';
|
||||
assert.ok(buttonText.includes('Searching for patterns'), 'Title should show second tool');
|
||||
|
||||
// Complete the first tool
|
||||
firstToolState.set(createState(IChatToolInvocation.StateKind.Completed), undefined);
|
||||
|
||||
// Title should still show the second tool (which is still running and owns the title)
|
||||
buttonText = labelElement?.textContent ?? button?.textContent ?? '';
|
||||
assert.ok(buttonText.includes('Searching for patterns'), 'Title should still show second tool after first completes');
|
||||
});
|
||||
|
||||
test('should keep title when tool is cancelled', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Working on task',
|
||||
agentName: 'TestAgent'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// Add a tool that will be cancelled
|
||||
const toolState = observableValue('state', createState(IChatToolInvocation.StateKind.Executing));
|
||||
const childTool: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'readFile',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: toolState,
|
||||
invocationMessage: 'Reading file.ts'
|
||||
};
|
||||
part.trackToolState(childTool);
|
||||
|
||||
// Verify title includes tool message
|
||||
const button = getCollapseButton(part);
|
||||
assert.ok(button, 'Button should exist');
|
||||
const labelElement = getCollapseButtonLabel(button);
|
||||
let buttonText = labelElement?.textContent ?? button?.textContent ?? '';
|
||||
assert.ok(buttonText.includes('Reading file.ts'), 'Title should include tool message while running');
|
||||
|
||||
// Cancel the tool
|
||||
toolState.set(createState(IChatToolInvocation.StateKind.Cancelled), undefined);
|
||||
|
||||
// Title should still include the tool message (persists like thinking part)
|
||||
buttonText = labelElement?.textContent ?? button?.textContent ?? '';
|
||||
assert.ok(buttonText.includes('Reading file.ts'),
|
||||
'Title should still include tool message after cancellation');
|
||||
});
|
||||
|
||||
test('should keep showing last tool message when that tool completes', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Working on task',
|
||||
agentName: 'TestAgent'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// First tool starts
|
||||
const firstToolState = observableValue('state', createState(IChatToolInvocation.StateKind.Executing));
|
||||
const firstTool: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'readFile',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: firstToolState,
|
||||
invocationMessage: 'Reading file1.ts'
|
||||
};
|
||||
part.trackToolState(firstTool);
|
||||
|
||||
// Verify title shows first tool
|
||||
const button = getCollapseButton(part);
|
||||
assert.ok(button, 'Button should exist');
|
||||
const labelElement = getCollapseButtonLabel(button);
|
||||
let buttonText = labelElement?.textContent ?? button?.textContent ?? '';
|
||||
assert.ok(buttonText.includes('Reading file1.ts'), 'Title should show first tool');
|
||||
|
||||
// Second tool starts and becomes the current title
|
||||
const secondToolState = observableValue('state', createState(IChatToolInvocation.StateKind.Executing));
|
||||
const secondTool: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'searchFiles',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: secondToolState,
|
||||
invocationMessage: 'Searching for patterns'
|
||||
};
|
||||
part.trackToolState(secondTool);
|
||||
|
||||
// Verify title shows second tool
|
||||
buttonText = labelElement?.textContent ?? button?.textContent ?? '';
|
||||
assert.ok(buttonText.includes('Searching for patterns'), 'Title should show second tool');
|
||||
|
||||
// Second tool completes
|
||||
secondToolState.set(createState(IChatToolInvocation.StateKind.Completed), undefined);
|
||||
|
||||
// Title should still show second tool (persists like thinking part)
|
||||
buttonText = labelElement?.textContent ?? button?.textContent ?? '';
|
||||
assert.ok(buttonText.includes('Searching for patterns'),
|
||||
'Title should still show last tool message after completion');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Auto-expand on confirmation', () => {
|
||||
test('should auto-expand when tool state becomes WaitingForConfirmation', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Working on task',
|
||||
agentName: 'TestAgent'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// Verify initially collapsed
|
||||
assert.ok(part.domNode.classList.contains('chat-used-context-collapsed'), 'Should start collapsed');
|
||||
|
||||
// Create a tool invocation that starts in executing state, then changes to WaitingForConfirmation
|
||||
const stateObservable = observableValue('state', createState(IChatToolInvocation.StateKind.Executing));
|
||||
const childTool: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'readFile',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: stateObservable,
|
||||
invocationMessage: 'Reading file'
|
||||
};
|
||||
|
||||
// Track this tool's state (this registers observers)
|
||||
part.trackToolState(childTool);
|
||||
|
||||
// Should still be collapsed since tool is executing, not waiting for confirmation
|
||||
assert.ok(part.domNode.classList.contains('chat-used-context-collapsed'), 'Should still be collapsed when tool is executing');
|
||||
|
||||
// Now change state to WaitingForConfirmation
|
||||
stateObservable.set(createState(IChatToolInvocation.StateKind.WaitingForConfirmation), undefined);
|
||||
|
||||
// Should auto-expand when tool needs confirmation
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false,
|
||||
'Should auto-expand when tool needs confirmation');
|
||||
});
|
||||
|
||||
test('should auto-collapse when confirmation is addressed', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Working on task',
|
||||
agentName: 'TestAgent'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// Create a tool invocation that is waiting for confirmation
|
||||
const stateObservable = observableValue('state', createState(IChatToolInvocation.StateKind.WaitingForConfirmation));
|
||||
const childTool: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'runInTerminal',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: stateObservable,
|
||||
invocationMessage: 'Run npm install'
|
||||
};
|
||||
|
||||
// Track this tool's state
|
||||
part.trackToolState(childTool);
|
||||
|
||||
// Should be expanded now
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false,
|
||||
'Should be expanded when waiting for confirmation');
|
||||
|
||||
// Now simulate confirmation being addressed (tool moves to executing)
|
||||
stateObservable.set(createState(IChatToolInvocation.StateKind.Executing), undefined);
|
||||
|
||||
// Should auto-collapse after confirmation is addressed
|
||||
assert.ok(part.domNode.classList.contains('chat-used-context-collapsed'),
|
||||
'Should auto-collapse after confirmation is addressed');
|
||||
});
|
||||
|
||||
test('should not auto-collapse if user manually expanded', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Working on task',
|
||||
agentName: 'TestAgent'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// User manually expands
|
||||
const button = getCollapseButton(part);
|
||||
button?.click();
|
||||
|
||||
// Should be expanded
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false, 'Should be expanded after user click');
|
||||
|
||||
// Create a tool that goes through confirmation cycle
|
||||
const stateObservable = observableValue('state', createState(IChatToolInvocation.StateKind.WaitingForConfirmation));
|
||||
const childTool: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'runInTerminal',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: stateObservable,
|
||||
invocationMessage: 'Run npm install'
|
||||
};
|
||||
|
||||
// Track this tool's state
|
||||
part.trackToolState(childTool);
|
||||
|
||||
// Confirm the tool (move to executing)
|
||||
stateObservable.set(createState(IChatToolInvocation.StateKind.Executing), undefined);
|
||||
|
||||
// Since user manually expanded, it should stay expanded
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false,
|
||||
'Should stay expanded when user manually expanded');
|
||||
});
|
||||
|
||||
test('should respect manual expansion after auto-expand', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Working on task',
|
||||
agentName: 'TestAgent'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// Verify initially collapsed
|
||||
assert.ok(part.domNode.classList.contains('chat-used-context-collapsed'), 'Should start collapsed');
|
||||
|
||||
// Create a tool that needs confirmation
|
||||
const stateObservable = observableValue('state', createState(IChatToolInvocation.StateKind.WaitingForConfirmation));
|
||||
const childTool: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'runInTerminal',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: stateObservable,
|
||||
invocationMessage: 'Run npm install'
|
||||
};
|
||||
|
||||
part.trackToolState(childTool);
|
||||
|
||||
// Should auto-expand
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false,
|
||||
'Should auto-expand for confirmation');
|
||||
|
||||
// User manually collapses
|
||||
const button = getCollapseButton(part);
|
||||
button?.click();
|
||||
assert.ok(part.domNode.classList.contains('chat-used-context-collapsed'), 'Should collapse after user click');
|
||||
|
||||
// User manually expands again
|
||||
button?.click();
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false,
|
||||
'Should expand after second user click');
|
||||
|
||||
// Confirm the tool (move to executing)
|
||||
stateObservable.set(createState(IChatToolInvocation.StateKind.Executing), undefined);
|
||||
|
||||
// Since user manually re-expanded after auto-expand, should stay expanded
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false,
|
||||
'Should stay expanded when user manually re-expanded after auto-expand');
|
||||
});
|
||||
|
||||
test('should resume auto-collapse after user manually expands then collapses', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Working on task',
|
||||
agentName: 'TestAgent'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// First confirmation cycle - user manually expands
|
||||
const stateObservable1 = observableValue('state1', createState(IChatToolInvocation.StateKind.WaitingForConfirmation));
|
||||
const childTool1: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'runInTerminal',
|
||||
toolCallId: 'tool1',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: stateObservable1,
|
||||
invocationMessage: 'First tool'
|
||||
};
|
||||
|
||||
part.trackToolState(childTool1);
|
||||
|
||||
// Should auto-expand for first confirmation
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false,
|
||||
'Should auto-expand for first confirmation');
|
||||
|
||||
// User manually collapses
|
||||
const button = getCollapseButton(part);
|
||||
button?.click();
|
||||
assert.ok(part.domNode.classList.contains('chat-used-context-collapsed'), 'Should collapse after user click');
|
||||
|
||||
// User manually expands (this sets userManuallyExpanded = true)
|
||||
button?.click();
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false,
|
||||
'Should expand after user re-expands');
|
||||
|
||||
// Complete first tool (should not auto-collapse since user manually expanded)
|
||||
stateObservable1.set(createState(IChatToolInvocation.StateKind.Completed), undefined);
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false,
|
||||
'Should stay expanded after first tool completes (user manually expanded)');
|
||||
|
||||
// User manually collapses again (this resets userManuallyExpanded)
|
||||
button?.click();
|
||||
assert.ok(part.domNode.classList.contains('chat-used-context-collapsed'), 'Should collapse after user manually collapses');
|
||||
|
||||
// Second confirmation cycle - should auto-collapse now since userManuallyExpanded was reset
|
||||
const stateObservable2 = observableValue('state2', createState(IChatToolInvocation.StateKind.WaitingForConfirmation));
|
||||
const childTool2: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'runInTerminal',
|
||||
toolCallId: 'tool2',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: stateObservable2,
|
||||
invocationMessage: 'Second tool'
|
||||
};
|
||||
|
||||
part.trackToolState(childTool2);
|
||||
|
||||
// Should auto-expand for second confirmation
|
||||
assert.strictEqual(part.domNode.classList.contains('chat-used-context-collapsed'), false,
|
||||
'Should auto-expand for second confirmation');
|
||||
|
||||
// Complete second tool - should auto-collapse since userManuallyExpanded was reset by the earlier collapse
|
||||
stateObservable2.set(createState(IChatToolInvocation.StateKind.Executing), undefined);
|
||||
assert.ok(part.domNode.classList.contains('chat-used-context-collapsed'),
|
||||
'Should auto-collapse after second confirmation is addressed (userManuallyExpanded was reset)');
|
||||
});
|
||||
|
||||
test('should clear current running tool message when tool completes', () => {
|
||||
const toolInvocation = createMockToolInvocation({
|
||||
toolSpecificData: {
|
||||
kind: 'subagent',
|
||||
description: 'Working on task',
|
||||
agentName: 'TestAgent'
|
||||
}
|
||||
});
|
||||
const context = createMockRenderContext(false);
|
||||
|
||||
const part = createPart(toolInvocation, context);
|
||||
|
||||
// Create a tool that will complete
|
||||
const stateObservable = observableValue('state', createState(IChatToolInvocation.StateKind.Executing));
|
||||
const childTool: IChatToolInvocation = {
|
||||
...createMockToolInvocation({
|
||||
toolId: 'readFile',
|
||||
subAgentInvocationId: toolInvocation.subAgentInvocationId
|
||||
}),
|
||||
state: stateObservable,
|
||||
invocationMessage: 'Reading config.ts'
|
||||
};
|
||||
|
||||
part.trackToolState(childTool);
|
||||
|
||||
// Verify title includes tool message
|
||||
const button = getCollapseButton(part);
|
||||
assert.ok(button, 'Button should exist');
|
||||
const labelElement = getCollapseButtonLabel(button);
|
||||
let buttonText = labelElement?.textContent ?? button?.textContent ?? '';
|
||||
assert.ok(buttonText.includes('Reading config.ts'), 'Title should include tool message while running');
|
||||
|
||||
// Complete the tool
|
||||
stateObservable.set(createState(IChatToolInvocation.StateKind.Completed), undefined);
|
||||
|
||||
// Title should still include the tool message (persists like thinking part)
|
||||
buttonText = labelElement?.textContent ?? button?.textContent ?? '';
|
||||
assert.ok(buttonText.includes('Reading config.ts'),
|
||||
'Title should still include tool message after completion');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import assert from 'assert';
|
||||
import { $ } from '../../../../../../../base/browser/dom.js';
|
||||
import { Event } from '../../../../../../../base/common/event.js';
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import assert from 'assert';
|
||||
import { Event } from '../../../../../../../base/common/event.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js';
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import assert from 'assert';
|
||||
import * as dom from '../../../../../base/browser/dom.js';
|
||||
import { HighlightedLabel } from '../../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';
|
||||
|
||||
@@ -58,7 +58,6 @@ class TestObjectTree<T> extends ObjectTree<T, any> {
|
||||
}
|
||||
|
||||
public getRendered(getProperty?: string) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const elements = element.querySelectorAll<HTMLElement>('.monaco-tl-contents');
|
||||
const sorted = [...elements].sort((a, b) => pos(a) - pos(b));
|
||||
const chain: SerializedTree[] = [{ e: '', children: [] }];
|
||||
|
||||
@@ -15,8 +15,6 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/comm
|
||||
import { DisposableStore } from '../../../base/common/lifecycle.js';
|
||||
import { mainWindow } from '../../../base/browser/window.js';
|
||||
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
suite('Workbench parts', () => {
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
Reference in New Issue
Block a user