@@ -15,10 +15,14 @@ import { listAgentProcesses, enrichProcesses } from '../../utils/process.js';
1515import { matchProcessesToSessions , generateAgentName } from '../../utils/matching.js' ;
1616import * as crypto from 'crypto' ;
1717
18- vi . mock ( '../../utils/process.js' , ( ) => ( {
19- listAgentProcesses : vi . fn ( ) ,
20- enrichProcesses : vi . fn ( ) ,
21- } ) ) ;
18+ vi . mock ( '../../utils/process.js' , async ( importOriginal ) => {
19+ const actual = await importOriginal ( ) as typeof import ( '../../utils/process.js' ) ;
20+ return {
21+ ...actual ,
22+ listAgentProcesses : vi . fn ( ) ,
23+ enrichProcesses : vi . fn ( ) ,
24+ } ;
25+ } ) ;
2226
2327vi . mock ( '../../utils/matching.js' , ( ) => ( {
2428 matchProcessesToSessions : vi . fn ( ) ,
@@ -158,6 +162,77 @@ describe('GeminiCliAdapter', () => {
158162 } ) ;
159163 } ) ;
160164
165+ it ( 'should suppress Gemini wrapper process-only agents before a session file exists' , async ( ) => {
166+ const wrapperProc : ProcessInfo = {
167+ pid : 20452 ,
168+ ppid : 17530 ,
169+ command : '/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini' ,
170+ cwd : '/repo' ,
171+ tty : 'ttys007' ,
172+ startTime : new Date ( '2026-06-13T08:25:21Z' ) ,
173+ } ;
174+ const childProc : ProcessInfo = {
175+ pid : 21373 ,
176+ ppid : 20452 ,
177+ command : '/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini' ,
178+ cwd : '/repo' ,
179+ tty : 'ttys007' ,
180+ startTime : new Date ( '2026-06-13T08:25:26Z' ) ,
181+ } ;
182+ mockedListAgentProcesses . mockReturnValue ( [ wrapperProc , childProc ] ) ;
183+
184+ const agents = await adapter . detectAgents ( ) ;
185+
186+ expect ( agents ) . toHaveLength ( 1 ) ;
187+ expect ( agents [ 0 ] ) . toMatchObject ( {
188+ pid : 21373 ,
189+ sessionId : 'pid-21373' ,
190+ summary : 'Gemini CLI process running' ,
191+ } ) ;
192+ } ) ;
193+
194+ it ( 'should carry the managed wrapper name to a process-only child before a session file exists' , async ( ) => {
195+ const regPath = path . join ( tmpHome , 'agents.json' ) ;
196+ const registry = new AgentRegistry ( regPath ) ;
197+ const namedAdapter = new GeminiCliAdapter ( registry ) ;
198+ const wrapperProc : ProcessInfo = {
199+ pid : 35792 ,
200+ ppid : 33068 ,
201+ command : '/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini' ,
202+ cwd : '/repo' ,
203+ tty : 'ttys002' ,
204+ startTime : new Date ( '2026-06-13T19:15:16Z' ) ,
205+ } ;
206+ const childProc : ProcessInfo = {
207+ pid : 36514 ,
208+ ppid : 35792 ,
209+ command : '/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini' ,
210+ cwd : '/repo' ,
211+ tty : 'ttys002' ,
212+ startTime : new Date ( '2026-06-13T19:15:18Z' ) ,
213+ } ;
214+ registry . register ( {
215+ name : 'cli-mqcqj469' ,
216+ type : 'gemini_cli' ,
217+ pid : wrapperProc . pid ,
218+ tmuxSession : 'cli-mqcqj469' ,
219+ cwd : wrapperProc . cwd ,
220+ startedAt : '2026-06-13T19:15:16.211Z' ,
221+ sessionId : `pid-${ wrapperProc . pid } ` ,
222+ sessionFilePath : '' ,
223+ } ) ;
224+ mockedListAgentProcesses . mockReturnValue ( [ wrapperProc , childProc ] ) ;
225+
226+ const agents = await namedAdapter . detectAgents ( ) ;
227+
228+ expect ( agents ) . toHaveLength ( 1 ) ;
229+ expect ( agents [ 0 ] ) . toMatchObject ( {
230+ name : 'cli-mqcqj469' ,
231+ pid : childProc . pid ,
232+ sessionId : `pid-${ childProc . pid } ` ,
233+ } ) ;
234+ } ) ;
235+
161236 it ( 'should map a process to its matching session file via projectHash' , async ( ) => {
162237 const cwd = '/repo/project-a' ;
163238 const projectHash = hashProjectRoot ( cwd ) ;
@@ -433,6 +508,66 @@ describe('GeminiCliAdapter', () => {
433508
434509 expect ( agents [ 0 ] . sessionId ) . toBe ( 'pid-100' ) ;
435510 } ) ;
511+
512+ it ( 'carries the managed wrapper name to the detected child process' , async ( ) => {
513+ const wrapperProc : ProcessInfo = {
514+ pid : 20339 ,
515+ ppid : 17570 ,
516+ command : '/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini' ,
517+ cwd : '/repo-a' ,
518+ tty : 'ttys002' ,
519+ startTime : new Date ( '2026-06-13T19:00:53Z' ) ,
520+ } ;
521+ const childProc : ProcessInfo = {
522+ pid : 21038 ,
523+ ppid : 20339 ,
524+ command : '/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini' ,
525+ cwd : '/repo-a' ,
526+ tty : 'ttys002' ,
527+ startTime : new Date ( '2026-06-13T19:00:57Z' ) ,
528+ } ;
529+ const now = new Date ( ) . toISOString ( ) ;
530+ sessionFilePath = writeSession ( tmpHome , 'cli-2' , 'session-2026-06-13T19-00-s-cached' , {
531+ sessionId : 's-cached' ,
532+ projectHash : hashProjectRoot ( '/repo-a' ) ,
533+ startTime : now ,
534+ lastUpdated : now ,
535+ directories : [ '/repo-a' ] ,
536+ messages : [
537+ { id : 'm1' , timestamp : now , type : 'user' , content : 'Hello from child process' } ,
538+ ] ,
539+ } ) ;
540+ registerEntry ( {
541+ name : 'cli-mqcq0mg5' ,
542+ pid : wrapperProc . pid ,
543+ tmuxSession : 'cli-mqcq0mg5' ,
544+ sessionId : 's-cached' ,
545+ sessionFilePath,
546+ } ) ;
547+ mockedListAgentProcesses . mockReturnValue ( [ wrapperProc , childProc ] ) ;
548+ mockedMatchProcessesToSessions . mockReturnValue ( [
549+ {
550+ process : childProc ,
551+ session : {
552+ sessionId : 's-cached' ,
553+ filePath : sessionFilePath ,
554+ projectDir : path . dirname ( sessionFilePath ) ,
555+ birthtimeMs : Date . now ( ) ,
556+ resolvedCwd : '/repo-a' ,
557+ } ,
558+ deltaMs : 0 ,
559+ } ,
560+ ] ) ;
561+
562+ const agents = await cachedAdapter . detectAgents ( ) ;
563+
564+ expect ( agents ) . toHaveLength ( 1 ) ;
565+ expect ( agents [ 0 ] ) . toMatchObject ( {
566+ name : 'cli-mqcq0mg5' ,
567+ pid : childProc . pid ,
568+ sessionId : 's-cached' ,
569+ } ) ;
570+ } ) ;
436571 } ) ;
437572
438573 describe ( 'discoverSessions' , ( ) => {
0 commit comments