Skip to content

Commit db8a860

Browse files
authored
Merge branch 'main' into anthonykim1/xtermAug31
2 parents c329064 + bcec367 commit db8a860

32 files changed

Lines changed: 671 additions & 180 deletions

src/vs/platform/agentHost/node/agentSideEffects.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1402,7 +1402,7 @@ export class AgentSideEffects extends Disposable {
14021402
type: ActionType.ChatError,
14031403
turnId: action.turnId,
14041404
duration: execution.duration + execution.stopWatch.elapsed(),
1405-
part: createErrorResponsePart(failure.error, true),
1405+
part: createErrorResponsePart(failure.error),
14061406
});
14071407
const endedTurn = this._completeTurn(channel, action.turnId, 'error', failure);
14081408
this._toolCallTracker.clearSession(channel);

src/vs/platform/agentHost/node/copilot/copilotAgentSession.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,7 +1594,7 @@ export class CopilotAgentSession extends Disposable {
15941594
type: ActionType.ChatError,
15951595
turnId: turn.id,
15961596
duration: turn.duration,
1597-
part: createErrorResponsePart(error, true),
1597+
part: createErrorResponsePart(error),
15981598
});
15991599
this._clearActiveTurn();
16001600
return turn.id;
@@ -2655,7 +2655,7 @@ export class CopilotAgentSession extends Disposable {
26552655
part: createErrorResponsePart({
26562656
errorType: 'developmentRecoverableError',
26572657
message: localize('copilotAgent.developmentRecoverableError', "Injected recoverable development error ({0}/{1}).", attempt, totalFailures),
2658-
}, true),
2658+
}),
26592659
});
26602660
this._clearActiveTurn();
26612661
}
@@ -5029,7 +5029,7 @@ export class CopilotAgentSession extends Disposable {
50295029
type: ActionType.ChatError,
50305030
turnId: this._turnId,
50315031
duration: turn?.duration ?? 0,
5032-
part: createErrorResponsePart(buildChatErrorInfoFromCopilotSdkFields(e.data), !e.agentId && turn !== undefined),
5032+
part: createErrorResponsePart(buildChatErrorInfoFromCopilotSdkFields(e.data)),
50335033
}, parentToolCallId);
50345034
if (!parentToolCallId) {
50355035
this._clearActiveTurn();

src/vs/platform/agentHost/node/copilot/mapSessionEvents.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ export async function mapSessionEvents(
672672
rootRequestActive = false;
673673
parentTurnState = TurnState.Error;
674674
parentTurnTerminated = true;
675-
parentBuilder.responseParts.push(createErrorResponsePart(buildChatErrorInfoFromCopilotSdkFields(e.data), true));
675+
parentBuilder.responseParts.push(createErrorResponsePart(buildChatErrorInfoFromCopilotSdkFields(e.data)));
676676
parentBuilder.waitingStartedAt = currentEventTimestamp;
677677
touch(parentBuilder);
678678
}
@@ -784,7 +784,7 @@ export async function mapSessionEvents(
784784
}
785785

786786
if (options && !(options instanceof URI) && options.interruptedTurnError && parentBuilder && rootRequestActive && parentTurnState !== TurnState.Error) {
787-
parentBuilder.responseParts.push(createErrorResponsePart(options.interruptedTurnError, true));
787+
parentBuilder.responseParts.push(createErrorResponsePart(options.interruptedTurnError));
788788
parentTurnState = TurnState.Error;
789789
}
790790
flushParent();

src/vs/platform/agentHost/test/node/agentService.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ suite('AgentService (node dispatcher)', () => {
791791
});
792792
});
793793

794-
test('finalizes the same turn with another resumable error when continuation fails immediately', async () => {
794+
test('finalizes the same turn with a non-resumable error when continuation fails immediately', async () => {
795795
const { chat } = await createErroredTurn();
796796
copilotAgent.chats.resumeTurn = async () => {
797797
throw new Error('continuation failed');
@@ -814,7 +814,7 @@ suite('AgentService (node dispatcher)', () => {
814814
state: TurnState.Error,
815815
errors: [
816816
createErrorResponsePart({ errorType: 'requestFailed', message: 'failed' }, true),
817-
createErrorResponsePart({ errorType: 'sendFailed', message: 'Error: continuation failed' }, true),
817+
createErrorResponsePart({ errorType: 'sendFailed', message: 'Error: continuation failed' }),
818818
],
819819
durationAtLeastInitial: true,
820820
});

src/vs/platform/agentHost/test/node/copilotAgentSession.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,7 +1483,6 @@ suite('CopilotAgentSession', () => {
14831483
errorType: 'executionInterrupted',
14841484
message: 'The agent was interrupted before this request finished.',
14851485
},
1486-
resumable: true,
14871486
});
14881487
});
14891488

@@ -5724,7 +5723,7 @@ Use the attached image as context.
57245723
});
57255724
});
57265725

5727-
test('the development $error-ui path emits a resumable error even with attachments', async () => {
5726+
test('the development $error-ui path emits an error even with attachments', async () => {
57285727
const { session, mockSession, signals } = await createAgentSession(disposables);
57295728

57305729
await session.send('$error-ui', [{
@@ -5750,7 +5749,6 @@ Use the attached image as context.
57505749
errorType: 'developmentRecoverableError',
57515750
message: 'Injected recoverable development error (1/1).',
57525751
},
5753-
resumable: true,
57545752
},
57555753
}],
57565754
});

src/vs/platform/agentHost/test/node/e2e/KNOWN_ISSUES.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -776,18 +776,25 @@ Use the affected provider command with `--grep "<exact test title>"` and tempora
776776
--grep "accepted steering followed by abort"
777777
```
778778

779-
### Mid-turn host shutdown recovery is record-only
779+
### Retryable Copilot errors are temporarily disabled
780780

781-
A user can lose the Agent Host process while a model response is still streaming. Reopening the session should restore the unfinished request as a resumable error, and retrying should continue that same turn without adding another user message.
781+
Copilot errors currently end a turn without offering an in-place retry. The retry protocol remains implemented, but the host intentionally omits the `resumable` marker from live, restored, and repeated errors until the feature is re-enabled.
782782

783-
- Test: `restores and resumes a turn interrupted by host shutdown`.
784-
- Scope: deterministic replay for Copilot.
785-
- Expected: the host dies after streaming starts but before any terminal turn action; restoration synthesizes a resumable `executionInterrupted` error, and a zero-message continuation completes the same turn.
786-
- Observed: replay serves the full recorded response immediately, leaving no active streaming window in which to kill the host before turn completion.
787-
- Gate: direct `AGENT_HOST_REPLAY_RECORD=1` mode only.
788-
- Run:
783+
- Tests:
784+
- `resumes a failed turn in place`
785+
- `resumes the same turn after repeated failures`
786+
- `restores and resumes a turn interrupted by host shutdown`
787+
- Scope: Copilot on all platforms and execution modes.
788+
- Expected when enabled: a failed turn is marked resumable, and retrying continues the same turn without adding another user message. An unfinished request restored after host shutdown has the same behavior.
789+
- Observed: Copilot errors intentionally omit the `resumable` marker, so clients cannot request an in-place retry.
790+
- Gate: all three scenarios are unconditionally skipped. The host-shutdown scenario additionally requires direct `AGENT_HOST_REPLAY_RECORD=1` mode because replay has no active streaming window to terminate.
791+
- Run after removing the temporary gate:
789792

790793
```bash
794+
./scripts/test-integration.sh --run \
795+
src/vs/platform/agentHost/test/node/e2e/providers/copilotAgentHostE2E.integrationTest.ts \
796+
--grep "resumes a failed turn in place|resumes the same turn after repeated failures"
797+
791798
AGENT_HOST_REPLAY_RECORD=1 ./scripts/test-integration.sh --run \
792799
src/vs/platform/agentHost/test/node/e2e/providers/copilotAgentHostE2E.integrationTest.ts \
793800
--grep "restores and resumes a turn interrupted by host shutdown"

src/vs/platform/agentHost/test/node/e2e/providers/copilotAgentHostE2E.integrationTest.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ suite('Agent Host E2E — Copilot (Copilot-specific)', function () {
181181
90_000,
182182
);
183183
const liveErrorPart = (getActionEnvelope(liveNotification).action as ChatErrorAction).part;
184-
assert.strictEqual(liveErrorPart.resumable, true);
184+
assert.strictEqual(liveErrorPart.resumable, undefined);
185185

186186
client = await lease.restart();
187187
client.setWorkingDirectory(workingDirectory);
@@ -201,11 +201,12 @@ suite('Agent Host E2E — Copilot (Copilot-specific)', function () {
201201
}, {
202202
state: TurnState.Error,
203203
error: liveErrorPart.error,
204-
resumable: true,
204+
resumable: undefined,
205205
});
206206
});
207207

208-
test('resumes a failed turn in place', async function () {
208+
// Retryable errors are temporarily disabled.
209+
test.skip('resumes a failed turn in place', async function () {
209210
this.timeout(180_000);
210211
const workingDirectory = await mkdtemp(join(tmpdir(), 'copilot-failed-turn-resume-'));
211212
tempDirs.push(workingDirectory);
@@ -344,7 +345,7 @@ suite('Agent Host E2E — Copilot (Copilot-specific)', function () {
344345
}
345346
});
346347

347-
test('resumes the same turn after repeated failures', async function () {
348+
test.skip('resumes the same turn after repeated failures', async function () {
348349
this.timeout(180_000);
349350
const workingDirectory = await mkdtemp(join(tmpdir(), 'copilot-repeated-failed-turn-resume-'));
350351
tempDirs.push(workingDirectory);
@@ -425,8 +426,8 @@ suite('Agent Host E2E — Copilot (Copilot-specific)', function () {
425426
});
426427
});
427428

428-
// Replay serves the full recorded response immediately, so it has no active streaming window to terminate.
429-
(RECORD_ONLY ? test : test.skip)('restores and resumes a turn interrupted by host shutdown', async function () {
429+
// Retryable errors are temporarily disabled.
430+
test.skip('restores and resumes a turn interrupted by host shutdown', async function () {
430431
this.timeout(240_000);
431432
const workingDirectory = await mkdtemp(join(tmpdir(), 'copilot-host-shutdown-resume-'));
432433
tempDirs.push(workingDirectory);

src/vs/platform/agentHost/test/node/mapSessionEvents.test.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ suite('mapSessionEvents — history replay', () => {
146146
});
147147
});
148148

149-
test('restores an unfinished request as a resumable error on the same turn', async () => {
149+
test('restores an unfinished request as an error on the same turn', async () => {
150150
const events: ISessionEvent[] = [
151151
{ type: 'user.message', id: 'interrupted-turn', data: { interactionId: 'm1', content: 'Keep working' } },
152152
{ type: 'assistant.turn_start', data: { turnId: 'sdk-turn' } },
@@ -171,7 +171,6 @@ suite('mapSessionEvents — history replay', () => {
171171
errorPart: {
172172
kind: ResponsePartKind.Error,
173173
error: interruptedTurnError,
174-
resumable: true,
175174
},
176175
});
177176
});
@@ -233,11 +232,11 @@ suite('mapSessionEvents — history replay', () => {
233232
{ kind: ResponsePartKind.Markdown, content: 'Second segment' },
234233
{ kind: ResponsePartKind.Error },
235234
],
236-
resumable: true,
235+
resumable: undefined,
237236
});
238237
});
239238

240-
test('keeps a resumable error terminal when a later notification starts another turn', async () => {
239+
test('keeps an error terminal when a later notification starts another turn', async () => {
241240
const events: ISessionEvent[] = [
242241
{ type: 'user.message', id: 'failed-turn', timestamp: '2026-08-11T00:00:00.000Z', data: { interactionId: 'm1', content: 'Start the background agent' } },
243242
{ type: 'assistant.turn_start', timestamp: '2026-08-11T00:00:00.100Z', data: { turnId: 'sdk-turn-1' } },
@@ -274,10 +273,10 @@ suite('mapSessionEvents — history replay', () => {
274273
state: TurnState.Complete,
275274
parts: [{ kind: ResponsePartKind.Markdown, content: 'The background agent finished.' }],
276275
}]);
277-
assert.strictEqual(getErrorResponsePart(turns[0])?.resumable, true);
276+
assert.strictEqual(getErrorResponsePart(turns[0])?.resumable, undefined);
278277
});
279278

280-
test('keeps a resumable error as the final part when a late tool completion arrives', async () => {
279+
test('keeps an error as the final part when a late tool completion arrives', async () => {
281280
const events: ISessionEvent[] = [
282281
{ type: 'user.message', id: 'failed-turn', data: { interactionId: 'm1', content: 'Run a command' } },
283282
{ type: 'assistant.turn_start', data: { turnId: 'sdk-turn-1' } },
@@ -295,7 +294,7 @@ suite('mapSessionEvents — history replay', () => {
295294
}, {
296295
state: TurnState.Error,
297296
parts: [{ kind: ResponsePartKind.Error }],
298-
resumable: true,
297+
resumable: undefined,
299298
});
300299
});
301300

src/vs/sessions/contrib/chat/browser/media/chatWidget.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@
286286
font-size: var(--vscode-codiconFontSize);
287287
}
288288

289+
.sessions-chat-picker-slot.sessions-workspace-category-picker-slot .action-label > .sessions-chat-dropdown-chevron {
290+
font-size: var(--vscode-codiconFontSize-compact);
291+
}
292+
289293
.sessions-workspace-category-picker .sessions-chat-dropdown-label {
290294
margin-left: 0;
291295
}

src/vs/sessions/contrib/chat/browser/newChatWidget.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ export class NewChatWidget extends Disposable {
210210
loading,
211211
historyKey: constObservable(undefined), // no persisted history for the new-session view
212212
placeholder: localize('newSessionPromptPlaceholder', "Pitch your idea"),
213-
sessionTypePickerOptions: { showChevron: false },
214213
supportsBackground: true,
215214
deferredNotificationsEnabled,
216215
petHostPreferred: this.options.petHostPreferred,
@@ -707,7 +706,6 @@ export class NewChatWidget extends Disposable {
707706
label: localize('newSessionWorkspacePicker.githubContext', "Issue/PR"),
708707
ariaLabel: localize('newSessionWorkspacePicker.githubContextAriaLabel', "Attach a GitHub issue or pull request to the new session"),
709708
tooltip: localize('newSessionWorkspacePicker.githubContextTooltip', "Attach an issue or pull request as context"),
710-
icon: Codicon.add,
711709
hideIconWhenAttached: true,
712710
group: SESSION_WORKSPACE_GROUP_GITHUB,
713711
attachesContext: true,

0 commit comments

Comments
 (0)