@codingame/monaco-vscode-xterm-common 36.2.6 → 37.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. package/package.json +4 -4
  2. package/vscode/src/vs/workbench/contrib/terminal/browser/agentHostOutputChannel.d.ts +22 -0
  3. package/vscode/src/vs/workbench/contrib/terminal/browser/agentHostOutputChannel.js +48 -0
  4. package/vscode/src/vs/workbench/contrib/terminal/browser/agentHostPty.d.ts +17 -5
  5. package/vscode/src/vs/workbench/contrib/terminal/browser/agentHostPty.js +173 -63
  6. package/vscode/src/vs/workbench/contrib/terminal/browser/agentHostTerminalService.d.ts +13 -3
  7. package/vscode/src/vs/workbench/contrib/terminal/browser/agentHostTerminalService.js +119 -56
  8. package/vscode/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.js +5 -5
  9. package/vscode/src/vs/workbench/contrib/terminal/browser/terminalActions.js +61 -61
  10. package/vscode/src/vs/workbench/contrib/terminal/browser/terminalInstance.d.ts +6 -2
  11. package/vscode/src/vs/workbench/contrib/terminal/browser/terminalInstance.js +71 -49
  12. package/vscode/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.d.ts +1 -0
  13. package/vscode/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.js +9 -7
  14. package/vscode/src/vs/workbench/contrib/terminal/browser/terminalTabsList.js +5 -5
  15. package/vscode/src/vs/workbench/contrib/terminal/browser/terminalTooltip.js +3 -3
  16. package/vscode/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollColorRegistry.js +3 -3
  17. package/vscode/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.js +3 -3
  18. package/vscode/src/vs/platform/agentHost/common/state/protocol/common/notifications.d.ts +0 -51
  19. package/vscode/src/vs/platform/agentHost/common/state/protocol/common/notifications.js +0 -9
  20. package/vscode/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.d.ts +0 -78
  21. package/vscode/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChat.js +0 -83
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codingame/monaco-vscode-xterm-common",
3
- "version": "36.2.6",
3
+ "version": "37.1.0",
4
4
  "private": false,
5
5
  "description": "VSCode public API plugged on the monaco editor - common package depending on xterm",
6
6
  "keywords": [],
@@ -15,9 +15,9 @@
15
15
  },
16
16
  "type": "module",
17
17
  "dependencies": {
18
- "@codingame/monaco-vscode-api": "36.2.6",
19
- "@codingame/monaco-vscode-xterm-addons-common": "36.2.6",
20
- "@xterm/xterm": "6.1.0-beta.288"
18
+ "@codingame/monaco-vscode-api": "37.1.0",
19
+ "@codingame/monaco-vscode-xterm-addons-common": "37.1.0",
20
+ "@xterm/xterm": "6.1.0-beta.304"
21
21
  },
22
22
  "exports": {
23
23
  ".": {
@@ -0,0 +1,22 @@
1
+ import { Event } from "@codingame/monaco-vscode-api/vscode/vs/base/common/event";
2
+ import { Disposable } from "@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle";
3
+ import { URI } from "@codingame/monaco-vscode-api/vscode/vs/base/common/uri";
4
+ import { IAgentConnection } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/agentService";
5
+ import type { IChatTerminalOutputSource } from "@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/terminal/browser/terminal";
6
+ /**
7
+ * Read-only view of an AHP output channel. Unlike {@link AgentHostPty}, this
8
+ * does not create a terminal process or an {@code ITerminalInstance}; chat
9
+ * renders the accumulated plain-text state in its own detached xterm.
10
+ */
11
+ export declare class AgentHostOutputChannel extends Disposable implements IChatTerminalOutputSource {
12
+ private readonly _onDidChange;
13
+ readonly onDidChange: Event<void>;
14
+ private _output;
15
+ get output(): string;
16
+ private _hasExited;
17
+ get hasExited(): boolean;
18
+ private _exitCode;
19
+ get exitCode(): number | undefined;
20
+ constructor(connection: IAgentConnection, terminalUri: URI);
21
+ private _acceptState;
22
+ }
@@ -0,0 +1,48 @@
1
+
2
+ import { Emitter } from '@codingame/monaco-vscode-api/vscode/vs/base/common/event';
3
+ import { Disposable } from '@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle';
4
+ import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-root/state';
5
+ import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-session/state';
6
+ import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-chat/state';
7
+ import { TerminalLifecycleStatus } from '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-terminal/state';
8
+ import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-changeset/state';
9
+ import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-resource-watch/state';
10
+ import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-automation/state';
11
+ import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-automation-run/state';
12
+ import { StateComponents } from '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/sessionState';
13
+
14
+ class AgentHostOutputChannel extends Disposable {
15
+ get output() {
16
+ return this._output;
17
+ }
18
+ get hasExited() {
19
+ return this._hasExited;
20
+ }
21
+ get exitCode() {
22
+ return this._exitCode;
23
+ }
24
+ constructor(connection, terminalUri) {
25
+ super();
26
+ this._onDidChange = this._register(( new Emitter()));
27
+ this.onDidChange = this._onDidChange.event;
28
+ this._output = "";
29
+ this._hasExited = false;
30
+ const subscriptionRef = this._register(
31
+ connection.getSubscription(StateComponents.Terminal, terminalUri, "AgentHostOutputChannel")
32
+ );
33
+ const subscription = subscriptionRef.object;
34
+ if (subscription.value && !(subscription.value instanceof Error)) {
35
+ this._acceptState(subscription.value);
36
+ }
37
+ this._register(subscription.onDidChange(state => this._acceptState(state)));
38
+ }
39
+ _acceptState(state) {
40
+ this._output = ( state.content.map(part => part.type === "command" ? part.output : part.value)).join("").replace(/\r?\n/g, "\r\n");
41
+ const lifecycle = state.lifecycle;
42
+ this._hasExited = lifecycle.status === TerminalLifecycleStatus.Exited;
43
+ this._exitCode = lifecycle.status === TerminalLifecycleStatus.Exited ? lifecycle.exitCode : undefined;
44
+ this._onDidChange.fire();
45
+ }
46
+ }
47
+
48
+ export { AgentHostOutputChannel };
@@ -1,7 +1,8 @@
1
1
  import { Event } from "@codingame/monaco-vscode-api/vscode/vs/base/common/event";
2
2
  import { URI } from "@codingame/monaco-vscode-api/vscode/vs/base/common/uri";
3
3
  import { IProcessPropertyMap, ITerminalChildProcess, ITerminalLaunchError, ITerminalLaunchResult, ProcessPropertyType } from "@codingame/monaco-vscode-api/vscode/vs/platform/terminal/common/terminal";
4
- import { IAgentConnection } from "@codingame/monaco-vscode-chat-service-override/vscode/vs/platform/agentHost/common/agentService";
4
+ import { ITerminalLogService } from "@codingame/monaco-vscode-api/vscode/vs/platform/terminal/common/terminal.service";
5
+ import { IAgentConnection } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/agentService";
5
6
  import { BasePty } from "../common/basePty.js";
6
7
  /**
7
8
  * Options for creating a new terminal on an agent host.
@@ -56,11 +57,14 @@ export declare function getAhpCommandMarkId(commandId: string, kind: AhpCommandM
56
57
  export declare class AgentHostPty extends BasePty implements ITerminalChildProcess {
57
58
  private _connection;
58
59
  private readonly _terminalUri;
59
- private readonly _options?;
60
+ private readonly _options;
61
+ private readonly _logService;
60
62
  private readonly _startBarrier;
61
- private readonly _subscriptionDisposables;
62
- private _subscriptionRef;
63
+ private readonly _lifetime;
64
+ private readonly _subscription;
65
+ private _terminalCreation;
63
66
  private _initialCwd;
67
+ private _didSignalReady;
64
68
  private readonly _onCommandExecuted;
65
69
  readonly onCommandExecuted: Event<IAgentHostPtyCommandExecutedEvent>;
66
70
  private readonly _onCommandFinished;
@@ -76,8 +80,10 @@ export declare class AgentHostPty extends BasePty implements ITerminalChildProce
76
80
  * echo as a real command — we filter those out here.
77
81
  */
78
82
  private readonly _suppressedCommandIds;
79
- constructor(id: number, _connection: IAgentConnection, _terminalUri: URI, _options?: IAgentHostPtyOptions | undefined);
83
+ constructor(id: number, _connection: IAgentConnection, _terminalUri: URI, _options: IAgentHostPtyOptions | undefined, _logService: ITerminalLogService);
80
84
  start(): Promise<ITerminalLaunchError | ITerminalLaunchResult | undefined>;
85
+ private _clearTerminalCreation;
86
+ private _signalReady;
81
87
  private _handleAction;
82
88
  /**
83
89
  * Replays structured terminal content parts from the initial state snapshot.
@@ -90,9 +96,15 @@ export declare class AgentHostPty extends BasePty implements ITerminalChildProce
90
96
  * are unwrapped to their original URI via {@link fromAgentHostUri}.
91
97
  */
92
98
  private _resolveCwdForProtocol;
99
+ private _createSubscription;
100
+ private _waitForHydration;
93
101
  input(data: string): void;
94
102
  resize(cols: number, rows: number): void;
95
103
  shutdown(_immediate: boolean): void;
104
+ private _requestHostTerminalDisposal;
105
+ private _disposeHostTerminal;
106
+ private _logHostDisposalError;
107
+ private _exitAndDispose;
96
108
  getInitialCwd(): Promise<string>;
97
109
  getCwd(): Promise<string>;
98
110
  clearBuffer(): Promise<void>;
@@ -1,17 +1,20 @@
1
1
 
2
- import { Barrier } from '@codingame/monaco-vscode-api/vscode/vs/base/common/async';
3
- import { Emitter } from '@codingame/monaco-vscode-api/vscode/vs/base/common/event';
4
- import { DisposableStore } from '@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle';
2
+ import { Barrier, raceCancellation, DeferredPromise, disposableTimeout } from '@codingame/monaco-vscode-api/vscode/vs/base/common/async';
3
+ import { CancellationTokenSource } from '@codingame/monaco-vscode-api/vscode/vs/base/common/cancellation';
4
+ import { Emitter, Event } from '@codingame/monaco-vscode-api/vscode/vs/base/common/event';
5
+ import { MutableDisposable, DisposableStore, toDisposable } from '@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle';
5
6
  import { ProcessPropertyType } from '@codingame/monaco-vscode-api/vscode/vs/platform/terminal/common/terminal';
6
7
  import { AGENT_HOST_SCHEME, fromAgentHostUri } from '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/agentHostUri';
7
8
  import { ActionType } from '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/common/actions';
8
- import '../../../../platform/agentHost/common/state/protocol/common/notifications.js';
9
+ import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/common/notifications';
9
10
  import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-root/state';
10
11
  import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-session/state';
11
12
  import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-chat/state';
12
13
  import { TerminalClaimKind } from '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-terminal/state';
13
14
  import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-changeset/state';
14
15
  import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-resource-watch/state';
16
+ import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-automation/state';
17
+ import '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/channels-automation-run/state';
15
18
  import { StateComponents } from '@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/sessionState';
16
19
  import { BasePty } from '../common/basePty.js';
17
20
 
@@ -34,14 +37,17 @@ class AgentHostPty extends BasePty {
34
37
  get supportsCommandDetection() {
35
38
  return this._supportsCommandDetection;
36
39
  }
37
- constructor(id, _connection, _terminalUri, _options) {
40
+ constructor(id, _connection, _terminalUri, _options, _logService) {
38
41
  super(id, false);
39
42
  this._connection = _connection;
40
43
  this._terminalUri = _terminalUri;
41
44
  this._options = _options;
45
+ this._logService = _logService;
42
46
  this._startBarrier = ( new Barrier());
43
- this._subscriptionDisposables = this._register(( new DisposableStore()));
47
+ this._lifetime = this._register(( new CancellationTokenSource()));
48
+ this._subscription = this._register(( new MutableDisposable()));
44
49
  this._initialCwd = "";
50
+ this._didSignalReady = false;
45
51
  this._onCommandExecuted = this._register(( new Emitter()));
46
52
  this.onCommandExecuted = this._onCommandExecuted.event;
47
53
  this._onCommandFinished = this._register(( new Emitter()));
@@ -52,30 +58,45 @@ class AgentHostPty extends BasePty {
52
58
  this._suppressedCommandIds = ( new Set());
53
59
  }
54
60
  async start() {
61
+ const connection = this._connection;
55
62
  try {
63
+ if (this._lifetime.token.isCancellationRequested) {
64
+ return undefined;
65
+ }
56
66
  if (!this._options?.attachOnly) {
57
- await this._connection.createTerminal({
67
+ const terminalCreation = connection.createTerminal({
58
68
  channel: ( this._terminalUri.toString()),
59
69
  claim: {
60
70
  kind: TerminalClaimKind.Client,
61
- clientId: this._connection.clientId
71
+ clientId: connection.clientId
62
72
  },
63
73
  name: this._options?.name,
64
74
  cwd: this._resolveCwdForProtocol(this._options?.cwd),
65
75
  cols: this._lastDimensions.cols > 0 ? this._lastDimensions.cols : undefined,
66
76
  rows: this._lastDimensions.rows > 0 ? this._lastDimensions.rows : undefined
67
77
  });
78
+ this._terminalCreation = terminalCreation;
79
+ terminalCreation.then(
80
+ () => this._clearTerminalCreation(terminalCreation),
81
+ () => this._clearTerminalCreation(terminalCreation)
82
+ );
83
+ const created = await raceCancellation(terminalCreation.then(() => true), this._lifetime.token, false);
84
+ if (!created) {
85
+ return undefined;
86
+ }
68
87
  }
69
- this._subscriptionRef = this._connection.getSubscription(StateComponents.Terminal, this._terminalUri, "AgentHostPty");
70
- const subscription = this._subscriptionRef.object;
71
- if (subscription.value === undefined) {
72
- await ( new Promise(resolve => {
73
- const listener = subscription.onDidChange(() => {
74
- listener.dispose();
75
- resolve();
76
- });
77
- this._subscriptionDisposables.add(listener);
78
- }));
88
+ if (this._lifetime.token.isCancellationRequested || this._connection !== connection) {
89
+ return undefined;
90
+ }
91
+ const {
92
+ subscription,
93
+ store
94
+ } = this._createSubscription(this._connection);
95
+ if (!(await this._waitForHydration(subscription, store))) {
96
+ return undefined;
97
+ }
98
+ if (this._lifetime.token.isCancellationRequested || this._subscription.value !== store) {
99
+ return undefined;
79
100
  }
80
101
  const state = subscription.value;
81
102
  if (state.supportsCommandDetection) {
@@ -89,31 +110,49 @@ class AgentHostPty extends BasePty {
89
110
  if (state.title) {
90
111
  this._properties.title = state.title;
91
112
  }
92
- this._subscriptionDisposables.add(subscription.onDidApplyAction(envelope => {
113
+ this._signalReady();
114
+ store.add(subscription.onDidApplyAction(envelope => {
93
115
  this._handleAction(envelope);
94
116
  }));
95
- this._startBarrier.open();
96
- this.handleReady({
97
- pid: -1,
98
- cwd: this._initialCwd,
99
- windowsPty: undefined
100
- });
101
117
  return undefined;
102
118
  } catch (err) {
103
- this._startBarrier.open();
119
+ if (this._lifetime.token.isCancellationRequested || this._connection !== connection) {
120
+ return undefined;
121
+ }
104
122
  return {
105
123
  message: err instanceof Error ? err.message : String(err)
106
124
  };
125
+ } finally {
126
+ this._startBarrier.open();
107
127
  }
108
128
  }
129
+ _clearTerminalCreation(terminalCreation) {
130
+ if (this._terminalCreation === terminalCreation) {
131
+ this._terminalCreation = undefined;
132
+ }
133
+ }
134
+ _signalReady() {
135
+ if (this._didSignalReady || this._lifetime.token.isCancellationRequested) {
136
+ return;
137
+ }
138
+ this._didSignalReady = true;
139
+ this.handleReady({
140
+ pid: -1,
141
+ cwd: this._initialCwd,
142
+ windowsPty: undefined
143
+ });
144
+ }
109
145
  _handleAction(envelope) {
146
+ if (this._lifetime.token.isCancellationRequested) {
147
+ return;
148
+ }
110
149
  const action = envelope.action;
111
150
  switch (action.type) {
112
151
  case ActionType.TerminalData:
113
152
  this.handleData(action.data);
114
153
  break;
115
154
  case ActionType.TerminalExited:
116
- this.handleExit(action.exitCode);
155
+ this._exitAndDispose(action.exitCode);
117
156
  break;
118
157
  case ActionType.TerminalCwdChanged:
119
158
  this._properties.cwd = ( action.cwd.toString());
@@ -211,11 +250,43 @@ class AgentHostPty extends BasePty {
211
250
  }
212
251
  return ( cwd.toString());
213
252
  }
253
+ _createSubscription(connection) {
254
+ const store = ( new DisposableStore());
255
+ try {
256
+ const subscription = store.add(
257
+ connection.getSubscription(StateComponents.Terminal, this._terminalUri, "AgentHostPty")
258
+ ).object;
259
+ this._subscription.value = store;
260
+ return {
261
+ subscription,
262
+ store
263
+ };
264
+ } catch (error) {
265
+ store.dispose();
266
+ throw error;
267
+ }
268
+ }
269
+ async _waitForHydration(subscription, store, timeoutMs) {
270
+ if (subscription.value !== undefined) {
271
+ return true;
272
+ }
273
+ const hydration = ( new DeferredPromise());
274
+ const timeout = timeoutMs === undefined ? undefined : disposableTimeout(() => hydration.error(( new Error("Reconnect hydration timed out"))), timeoutMs, store);
275
+ store.add(toDisposable(() => hydration.complete(false)));
276
+ store.add(Event.once(subscription.onDidChange)(() => {
277
+ timeout?.dispose();
278
+ hydration.complete(true);
279
+ }));
280
+ return raceCancellation(hydration.p, this._lifetime.token, false);
281
+ }
214
282
  input(data) {
215
- if (this._inReplay) {
283
+ if (this._inReplay || this._lifetime.token.isCancellationRequested) {
216
284
  return;
217
285
  }
218
286
  this._startBarrier.wait().then(() => {
287
+ if (this._lifetime.token.isCancellationRequested) {
288
+ return;
289
+ }
219
290
  this._connection.dispatch(( this._terminalUri.toString()), {
220
291
  type: ActionType.TerminalInput,
221
292
  data
@@ -223,12 +294,15 @@ class AgentHostPty extends BasePty {
223
294
  });
224
295
  }
225
296
  resize(cols, rows) {
226
- if (this._inReplay || (this._lastDimensions.cols === cols && this._lastDimensions.rows === rows)) {
297
+ if (this._lifetime.token.isCancellationRequested || this._inReplay || (this._lastDimensions.cols === cols && this._lastDimensions.rows === rows)) {
227
298
  return;
228
299
  }
229
300
  this._lastDimensions.cols = cols;
230
301
  this._lastDimensions.rows = rows;
231
302
  this._startBarrier.wait().then(() => {
303
+ if (this._lifetime.token.isCancellationRequested) {
304
+ return;
305
+ }
232
306
  this._connection.dispatch(( this._terminalUri.toString()), {
233
307
  type: ActionType.TerminalResized,
234
308
  cols,
@@ -237,14 +311,43 @@ class AgentHostPty extends BasePty {
237
311
  });
238
312
  }
239
313
  shutdown(_immediate) {
240
- this._startBarrier.wait().then(() => {
241
- if (!this._options?.attachOnly) {
242
- this._connection.disposeTerminal(this._terminalUri);
243
- }
244
- this._subscriptionRef?.dispose();
245
- this._subscriptionRef = undefined;
246
- this._subscriptionDisposables.clear();
247
- this.handleExit(undefined);
314
+ if (this._lifetime.token.isCancellationRequested) {
315
+ return;
316
+ }
317
+ this._requestHostTerminalDisposal();
318
+ this._exitAndDispose(undefined);
319
+ }
320
+ _requestHostTerminalDisposal() {
321
+ if (this._options?.attachOnly) {
322
+ return;
323
+ }
324
+ this._disposeHostTerminal();
325
+ if (this._terminalCreation) {
326
+ void this._terminalCreation.then(() => this._disposeHostTerminal(), () => this._disposeHostTerminal());
327
+ }
328
+ }
329
+ _disposeHostTerminal() {
330
+ try {
331
+ void this._connection.disposeTerminal(this._terminalUri).catch(err => this._logHostDisposalError(err));
332
+ } catch (err) {
333
+ this._logHostDisposalError(err);
334
+ }
335
+ }
336
+ _logHostDisposalError(err) {
337
+ this._logService.warn(
338
+ `[AgentHostPty] Failed to dispose host terminal: ${err instanceof Error ? err.message : String(err)}`
339
+ );
340
+ }
341
+ _exitAndDispose(exitCode) {
342
+ if (this._lifetime.token.isCancellationRequested) {
343
+ return;
344
+ }
345
+ this._lifetime.cancel();
346
+ this._subscription.clear();
347
+ this._startBarrier.open();
348
+ queueMicrotask(() => {
349
+ this.handleExit(exitCode);
350
+ this.dispose();
248
351
  });
249
352
  }
250
353
  async getInitialCwd() {
@@ -254,6 +357,9 @@ class AgentHostPty extends BasePty {
254
357
  return this._properties.cwd || this._initialCwd;
255
358
  }
256
359
  async clearBuffer() {
360
+ if (this._lifetime.token.isCancellationRequested) {
361
+ return;
362
+ }
257
363
  this._connection.dispatch(( this._terminalUri.toString()), {
258
364
  type: ActionType.TerminalCleared
259
365
  });
@@ -269,27 +375,22 @@ class AgentHostPty extends BasePty {
269
375
  }
270
376
  async updateProperty(_type, _value) {}
271
377
  async reconnect(newConnection) {
272
- this._subscriptionDisposables.clear();
273
- this._subscriptionRef?.dispose();
274
- this._subscriptionRef = undefined;
378
+ if (this._lifetime.token.isCancellationRequested) {
379
+ return false;
380
+ }
381
+ this._subscription.clear();
275
382
  this._connection = newConnection;
383
+ let subscriptionStore;
276
384
  try {
277
- this._subscriptionRef = this._connection.getSubscription(StateComponents.Terminal, this._terminalUri, "AgentHostPty");
278
- const subscription = this._subscriptionRef.object;
279
- if (subscription.value === undefined) {
280
- const RECONNECT_HYDRATE_TIMEOUT_MS = 10_000;
281
- await ( new Promise((resolve, reject) => {
282
- const timer = setTimeout(() => {
283
- listener.dispose();
284
- reject(( new Error("Reconnect hydration timed out")));
285
- }, RECONNECT_HYDRATE_TIMEOUT_MS);
286
- const listener = subscription.onDidChange(() => {
287
- clearTimeout(timer);
288
- listener.dispose();
289
- resolve();
290
- });
291
- this._subscriptionDisposables.add(listener);
292
- }));
385
+ const result = this._createSubscription(this._connection);
386
+ const subscription = result.subscription;
387
+ subscriptionStore = result.store;
388
+ const RECONNECT_HYDRATE_TIMEOUT_MS = 10_000;
389
+ if (!(await this._waitForHydration(subscription, subscriptionStore, RECONNECT_HYDRATE_TIMEOUT_MS))) {
390
+ return false;
391
+ }
392
+ if (this._lifetime.token.isCancellationRequested || this._subscription.value !== subscriptionStore) {
393
+ return false;
293
394
  }
294
395
  const state = subscription.value;
295
396
  if (state.supportsCommandDetection && !this._supportsCommandDetection) {
@@ -304,15 +405,24 @@ class AgentHostPty extends BasePty {
304
405
  if (state.title) {
305
406
  this._properties.title = state.title;
306
407
  }
307
- this._subscriptionDisposables.add(subscription.onDidApplyAction(envelope => {
408
+ this._signalReady();
409
+ subscriptionStore.add(subscription.onDidApplyAction(envelope => {
308
410
  this._handleAction(envelope);
309
411
  }));
310
412
  return true;
311
413
  } catch (err) {
312
- console.warn(
313
- "[AgentHostPty] Reconnection failed:",
314
- err instanceof Error ? err.message : String(err)
315
- );
414
+ if (!this._lifetime.token.isCancellationRequested) {
415
+ this._logService.warn(
416
+ `[AgentHostPty] Reconnection failed: ${err instanceof Error ? err.message : String(err)}`
417
+ );
418
+ }
419
+ if (subscriptionStore && this._subscription.value === subscriptionStore) {
420
+ this._subscription.clear();
421
+ }
422
+ if (!this._didSignalReady && !this._lifetime.token.isCancellationRequested) {
423
+ this._requestHostTerminalDisposal();
424
+ this._exitAndDispose(undefined);
425
+ }
316
426
  return false;
317
427
  }
318
428
  }
@@ -320,8 +430,8 @@ class AgentHostPty extends BasePty {
320
430
  return this._terminalUri;
321
431
  }
322
432
  dispose() {
323
- this._subscriptionRef?.dispose();
324
- this._subscriptionRef = undefined;
433
+ this._lifetime.cancel();
434
+ this._subscription.clear();
325
435
  super.dispose();
326
436
  }
327
437
  }
@@ -1,8 +1,9 @@
1
1
  import { Disposable, IDisposable } from "@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle";
2
2
  import { IObservable } from "@codingame/monaco-vscode-api/vscode/vs/base/common/observable";
3
3
  import { URI } from "@codingame/monaco-vscode-api/vscode/vs/base/common/uri";
4
- import { IAgentConnection } from "@codingame/monaco-vscode-chat-service-override/vscode/vs/platform/agentHost/common/agentService";
4
+ import { IAgentConnection } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/agentService";
5
5
  import { IQuickInputService } from "@codingame/monaco-vscode-api/vscode/vs/platform/quickinput/common/quickInput.service";
6
+ import { ITerminalLogService } from "@codingame/monaco-vscode-api/vscode/vs/platform/terminal/common/terminal.service";
6
7
  import { ITerminalInstance, ITerminalLocationOptions } from "@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/terminal/browser/terminal";
7
8
  import { ITerminalChatService } from "@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/terminal/browser/terminal.service";
8
9
  import { ITerminalService } from "@codingame/monaco-vscode-api/vscode/vs/workbench/contrib/terminal/browser/terminal.service";
@@ -35,6 +36,7 @@ export declare class AgentHostTerminalService extends Disposable implements IAge
35
36
  private readonly _terminalChatService;
36
37
  private readonly _terminalProfileService;
37
38
  private readonly _quickInputService;
39
+ private readonly _logService;
38
40
  readonly _serviceBrand: undefined;
39
41
  private readonly _entries;
40
42
  private readonly _usedHosts;
@@ -49,8 +51,8 @@ export declare class AgentHostTerminalService extends Disposable implements IAge
49
51
  * keyed by terminal URI string. Used for reconnection scoping.
50
52
  */
51
53
  private readonly _activePtys;
52
- private readonly _reviveSequencer;
53
- constructor(_terminalService: ITerminalService, _terminalChatService: ITerminalChatService, _terminalProfileService: ITerminalProfileService, _quickInputService: IQuickInputService);
54
+ private readonly _pendingRevives;
55
+ constructor(_terminalService: ITerminalService, _terminalChatService: ITerminalChatService, _terminalProfileService: ITerminalProfileService, _quickInputService: IQuickInputService, _logService: ITerminalLogService);
54
56
  registerEntry(entry: IAgentHostEntry): IDisposable;
55
57
  getProfileForConnection(address: string): IAgentHostTerminalProfileInfo | undefined;
56
58
  setDefaultCwd(cwd: URI | undefined): void;
@@ -59,7 +61,15 @@ export declare class AgentHostTerminalService extends Disposable implements IAge
59
61
  createTerminalForEntry(address: string, options?: IAgentHostTerminalCreateOptions): Promise<ITerminalInstance | undefined>;
60
62
  createTerminal(connection: IAgentConnection, options?: IAgentHostTerminalCreateOptions): Promise<ITerminalInstance>;
61
63
  reviveTerminal(connection: IAgentConnection, terminalUri: URI, terminalToolSessionId: string): Promise<ITerminalInstance>;
64
+ attachOutputTerminal(connection: IAgentConnection, terminalUri: URI, terminalToolSessionId: string): IDisposable;
62
65
  private _doReviveTerminal;
66
+ /** Creates the registration that owns the pty produced for {@link key}. */
67
+ private _createPtyRegistration;
68
+ /**
69
+ * Ties the registration's lifetime to the terminal instance so the local
70
+ * pty is disposed even on paths that never call `shutdown()`.
71
+ */
72
+ private _registerInstancePtyCleanup;
63
73
  reconnectTerminals(newConnection: IAgentConnection, oldClientId: string): Promise<{
64
74
  recovered: number;
65
75
  total: number;