33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6- import { Disposable , DisposableStore , toDisposable } from '../../../base/common/lifecycle.js' ;
6+ import { Disposable , DisposableStore , IDisposable , toDisposable } from '../../../base/common/lifecycle.js' ;
7+ import { Emitter } from '../../../base/common/event.js' ;
78import * as platform from '../../../base/common/platform.js' ;
89import { ILogService } from '../../log/common/log.js' ;
10+ import { createDecorator } from '../../instantiation/common/instantiation.js' ;
911import { ActionType } from '../common/state/protocol/actions.js' ;
1012import type { ICreateTerminalParams } from '../common/state/protocol/commands.js' ;
1113import { ITerminalClaim , ITerminalInfo , ITerminalState , TerminalClaimKind } from '../common/state/protocol/state.js' ;
1214import { isTerminalAction } from '../common/state/sessionActions.js' ;
1315import type { AgentHostStateManager } from './agentHostStateManager.js' ;
1416
17+ export const IAgentHostTerminalManager = createDecorator < IAgentHostTerminalManager > ( 'agentHostTerminalManager' ) ;
18+
19+ /**
20+ * Service interface for terminal management in the agent host.
21+ */
22+ export interface IAgentHostTerminalManager {
23+ readonly _serviceBrand : undefined ;
24+ createTerminal ( params : ICreateTerminalParams , options ?: { shell ?: string } ) : Promise < void > ;
25+ writeInput ( uri : string , data : string ) : void ;
26+ onData ( uri : string , cb : ( data : string ) => void ) : IDisposable ;
27+ onExit ( uri : string , cb : ( exitCode : number ) => void ) : IDisposable ;
28+ onClaimChanged ( uri : string , cb : ( claim : ITerminalClaim ) => void ) : IDisposable ;
29+ getContent ( uri : string ) : string | undefined ;
30+ getClaim ( uri : string ) : ITerminalClaim | undefined ;
31+ hasTerminal ( uri : string ) : boolean ;
32+ getExitCode ( uri : string ) : number | undefined ;
33+ disposeTerminal ( uri : string ) : void ;
34+ getTerminalInfos ( ) : ITerminalInfo [ ] ;
35+ getTerminalState ( uri : string ) : ITerminalState | undefined ;
36+ }
37+
1538// node-pty is loaded dynamically to avoid bundling issues in non-node environments
1639let nodePtyModule : typeof import ( 'node-pty' ) | undefined ;
1740async function getNodePty ( ) : Promise < typeof import ( 'node-pty' ) > {
@@ -26,6 +49,9 @@ interface IManagedTerminal {
2649 readonly uri : string ;
2750 readonly store : DisposableStore ;
2851 readonly pty : import ( 'node-pty' ) . IPty ;
52+ readonly onDataEmitter : Emitter < string > ;
53+ readonly onExitEmitter : Emitter < number > ;
54+ readonly onClaimChangedEmitter : Emitter < ITerminalClaim > ;
2955 title : string ;
3056 cwd : string ;
3157 cols : number ;
@@ -43,7 +69,8 @@ interface IManagedTerminal {
4369 * actions (input, resize, claim changes) and dispatches server-originated
4470 * PTY output back through the state manager.
4571 */
46- export class AgentHostTerminalManager extends Disposable {
72+ export class AgentHostTerminalManager extends Disposable implements IAgentHostTerminalManager {
73+ declare readonly _serviceBrand : undefined ;
4774
4875 private readonly _terminals = new Map < string , IManagedTerminal > ( ) ;
4976
@@ -110,7 +137,7 @@ export class AgentHostTerminalManager extends Disposable {
110137 * Create a new terminal backed by node-pty.
111138 * Spawns the user's default shell.
112139 */
113- async createTerminal ( params : ICreateTerminalParams ) : Promise < void > {
140+ async createTerminal ( params : ICreateTerminalParams , options ?: { shell ?: string } ) : Promise < void > {
114141 const uri = params . terminal ;
115142 if ( this . _terminals . has ( uri ) ) {
116143 throw new Error ( `Terminal already exists: ${ uri } ` ) ;
@@ -122,7 +149,7 @@ export class AgentHostTerminalManager extends Disposable {
122149 const cols = params . cols ?? 80 ;
123150 const rows = params . rows ?? 24 ;
124151
125- const shell = this . _getDefaultShell ( ) ;
152+ const shell = options ?. shell ?? this . _getDefaultShell ( ) ;
126153 const name = platform . isWindows ? 'cmd' : 'xterm-256color' ;
127154
128155 this . _logService . info ( `[TerminalManager] Creating terminal ${ uri } : shell=${ shell } , cwd=${ cwd } , cols=${ cols } , rows=${ rows } ` ) ;
@@ -138,10 +165,17 @@ export class AgentHostTerminalManager extends Disposable {
138165 const store = new DisposableStore ( ) ;
139166 const claim : ITerminalClaim = params . claim ?? { kind : TerminalClaimKind . Client , clientId : '' } ;
140167
168+ const onDataEmitter = store . add ( new Emitter < string > ( ) ) ;
169+ const onExitEmitter = store . add ( new Emitter < number > ( ) ) ;
170+ const onClaimChangedEmitter = store . add ( new Emitter < ITerminalClaim > ( ) ) ;
171+
141172 const managed : IManagedTerminal = {
142173 uri,
143174 store,
144175 pty : ptyProcess ,
176+ onDataEmitter,
177+ onExitEmitter,
178+ onClaimChangedEmitter,
145179 title : params . name ?? shell ,
146180 cwd,
147181 cols,
@@ -162,6 +196,7 @@ export class AgentHostTerminalManager extends Disposable {
162196 if ( managed . content . length > 100_000 ) {
163197 managed . content = managed . content . slice ( - 80_000 ) ;
164198 }
199+ managed . onDataEmitter . fire ( data ) ;
165200 this . _stateManager . dispatchServerAction ( {
166201 type : ActionType . TerminalData ,
167202 terminal : uri ,
@@ -172,6 +207,7 @@ export class AgentHostTerminalManager extends Disposable {
172207
173208 const exitListener = ptyProcess . onExit ( e => {
174209 managed . exitCode = e . exitCode ;
210+ managed . onExitEmitter . fire ( e . exitCode ) ;
175211 this . _stateManager . dispatchServerAction ( {
176212 type : ActionType . TerminalExited ,
177213 terminal : uri ,
@@ -201,14 +237,66 @@ export class AgentHostTerminalManager extends Disposable {
201237 this . _broadcastTerminalList ( ) ;
202238 }
203239
204- /** Send input data to a terminal's PTY process. */
240+ /** Send input data to a terminal's PTY process (from client-dispatched actions) . */
205241 private _writeInput ( uri : string , data : string ) : void {
242+ this . writeInput ( uri , data ) ;
243+ }
244+
245+ /** Send input data to a terminal's PTY process. */
246+ writeInput ( uri : string , data : string ) : void {
206247 const terminal = this . _terminals . get ( uri ) ;
207248 if ( terminal && terminal . exitCode === undefined ) {
208249 terminal . pty . write ( data ) ;
209250 }
210251 }
211252
253+ /** Register a callback for PTY data events on a terminal. */
254+ onData ( uri : string , cb : ( data : string ) => void ) : IDisposable {
255+ const terminal = this . _terminals . get ( uri ) ;
256+ if ( ! terminal ) {
257+ return toDisposable ( ( ) => { } ) ;
258+ }
259+ return terminal . onDataEmitter . event ( cb ) ;
260+ }
261+
262+ /** Register a callback for PTY exit events on a terminal. */
263+ onExit ( uri : string , cb : ( exitCode : number ) => void ) : IDisposable {
264+ const terminal = this . _terminals . get ( uri ) ;
265+ if ( ! terminal ) {
266+ return toDisposable ( ( ) => { } ) ;
267+ }
268+ return terminal . onExitEmitter . event ( cb ) ;
269+ }
270+
271+ /** Register a callback for terminal claim changes. */
272+ onClaimChanged ( uri : string , cb : ( claim : ITerminalClaim ) => void ) : IDisposable {
273+ const terminal = this . _terminals . get ( uri ) ;
274+ if ( ! terminal ) {
275+ return toDisposable ( ( ) => { } ) ;
276+ }
277+ return terminal . onClaimChangedEmitter . event ( cb ) ;
278+ }
279+
280+ /** Get accumulated scrollback content for a terminal. */
281+ getContent ( uri : string ) : string | undefined {
282+ return this . _terminals . get ( uri ) ?. content ;
283+ }
284+
285+ /** Get the current claim for a terminal. */
286+ getClaim ( uri : string ) : ITerminalClaim | undefined {
287+ return this . _terminals . get ( uri ) ?. claim ;
288+ }
289+
290+ /** Check whether a terminal exists. */
291+ hasTerminal ( uri : string ) : boolean {
292+ return this . _terminals . has ( uri ) ;
293+ }
294+
295+ /** Get the exit code for a terminal, or undefined if still running. */
296+ getExitCode ( uri : string ) : number | undefined {
297+ return this . _terminals . get ( uri ) ?. exitCode ;
298+ }
299+
212300 /** Resize a terminal. */
213301 private _resize ( uri : string , cols : number , rows : number ) : void {
214302 const terminal = this . _terminals . get ( uri ) ;
@@ -224,6 +312,7 @@ export class AgentHostTerminalManager extends Disposable {
224312 const terminal = this . _terminals . get ( uri ) ;
225313 if ( terminal ) {
226314 terminal . claim = claim ;
315+ terminal . onClaimChangedEmitter . fire ( claim ) ;
227316 this . _broadcastTerminalList ( ) ;
228317 }
229318 }
0 commit comments