@ai-devkit/agent-manager 0.18.0 → 0.19.1

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 (45) hide show
  1. package/dist/__tests__/adapters/CopilotAdapter.test.js +127 -2
  2. package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -1
  3. package/dist/__tests__/adapters/GeminiCliAdapter.test.js +144 -2
  4. package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -1
  5. package/dist/__tests__/terminal/TerminalFocusManager.test.js +73 -0
  6. package/dist/__tests__/terminal/TerminalFocusManager.test.js.map +1 -0
  7. package/dist/__tests__/utils/agents.test.js +17 -0
  8. package/dist/__tests__/utils/agents.test.js.map +1 -0
  9. package/dist/__tests__/utils/process.test.js +54 -5
  10. package/dist/__tests__/utils/process.test.js.map +1 -1
  11. package/dist/adapters/AgentAdapter.d.ts +2 -0
  12. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  13. package/dist/adapters/AgentAdapter.js.map +1 -1
  14. package/dist/adapters/CopilotAdapter.d.ts +4 -2
  15. package/dist/adapters/CopilotAdapter.d.ts.map +1 -1
  16. package/dist/adapters/CopilotAdapter.js +23 -12
  17. package/dist/adapters/CopilotAdapter.js.map +1 -1
  18. package/dist/adapters/GeminiCliAdapter.d.ts +1 -0
  19. package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
  20. package/dist/adapters/GeminiCliAdapter.js +36 -11
  21. package/dist/adapters/GeminiCliAdapter.js.map +1 -1
  22. package/dist/terminal/TerminalFocusManager.d.ts +1 -0
  23. package/dist/terminal/TerminalFocusManager.d.ts.map +1 -1
  24. package/dist/terminal/TerminalFocusManager.js +10 -9
  25. package/dist/terminal/TerminalFocusManager.js.map +1 -1
  26. package/dist/utils/agents.d.ts +1 -1
  27. package/dist/utils/agents.d.ts.map +1 -1
  28. package/dist/utils/agents.js +29 -3
  29. package/dist/utils/agents.js.map +1 -1
  30. package/dist/utils/process.d.ts +11 -2
  31. package/dist/utils/process.d.ts.map +1 -1
  32. package/dist/utils/process.js +44 -9
  33. package/dist/utils/process.js.map +1 -1
  34. package/package.json +6 -1
  35. package/src/__tests__/adapters/CopilotAdapter.test.ts +97 -5
  36. package/src/__tests__/adapters/GeminiCliAdapter.test.ts +139 -4
  37. package/src/__tests__/terminal/TerminalFocusManager.test.ts +92 -0
  38. package/src/__tests__/utils/agents.test.ts +17 -0
  39. package/src/__tests__/utils/process.test.ts +31 -7
  40. package/src/adapters/AgentAdapter.ts +3 -0
  41. package/src/adapters/CopilotAdapter.ts +25 -14
  42. package/src/adapters/GeminiCliAdapter.ts +42 -11
  43. package/src/terminal/TerminalFocusManager.ts +11 -3
  44. package/src/utils/agents.ts +22 -2
  45. package/src/utils/process.ts +64 -9
@@ -7,10 +7,15 @@ import { CopilotAdapter } from '../../adapters/CopilotAdapter.js';
7
7
  import { AgentStatus } from '../../adapters/AgentAdapter.js';
8
8
  import { listAgentProcesses, enrichProcesses } from '../../utils/process.js';
9
9
  import { generateAgentName } from '../../utils/matching.js';
10
- vi.mock('../../utils/process.js', ()=>({
10
+ import { AgentRegistry } from '../../utils/AgentRegistry.js';
11
+ vi.mock('../../utils/process.js', async (importOriginal)=>{
12
+ const actual = await importOriginal();
13
+ return {
14
+ ...actual,
11
15
  listAgentProcesses: vi.fn(),
12
16
  enrichProcesses: vi.fn()
13
- }));
17
+ };
18
+ });
14
19
  vi.mock('../../utils/matching.js', ()=>({
15
20
  generateAgentName: vi.fn()
16
21
  }));
@@ -223,6 +228,73 @@ describe('CopilotAdapter', ()=>{
223
228
  summary: 'Copilot process running'
224
229
  });
225
230
  });
231
+ it('suppresses wrapper process-only agents before the session lock exists', async ()=>{
232
+ const processes = [
233
+ {
234
+ pid: 86800,
235
+ command: 'copilot',
236
+ cwd: '/repo',
237
+ tty: 'ttys001',
238
+ ppid: 84174
239
+ },
240
+ {
241
+ pid: 86810,
242
+ command: '/custom/install/copilot',
243
+ cwd: '/repo',
244
+ tty: 'ttys001',
245
+ ppid: 86800
246
+ }
247
+ ];
248
+ mockedListAgentProcesses.mockReturnValue(processes);
249
+ mockedEnrichProcesses.mockReturnValue(processes);
250
+ const agents = await adapter.detectAgents();
251
+ expect(agents).toHaveLength(1);
252
+ expect(agents[0]).toMatchObject({
253
+ pid: 86810,
254
+ sessionId: 'pid-86810',
255
+ summary: 'Copilot process running'
256
+ });
257
+ });
258
+ it('carries the managed wrapper name to a process-only child before the session lock exists', async ()=>{
259
+ const registry = new AgentRegistry(path.join(tmpDir, 'agents.json'));
260
+ adapter = new CopilotAdapter(registry);
261
+ adapter.sessionStateDir = sessionStateDir;
262
+ const processes = [
263
+ {
264
+ pid: 86800,
265
+ command: 'copilot',
266
+ cwd: '/repo',
267
+ tty: 'ttys001',
268
+ ppid: 84174
269
+ },
270
+ {
271
+ pid: 86810,
272
+ command: '/custom/install/copilot',
273
+ cwd: '/repo',
274
+ tty: 'ttys001',
275
+ ppid: 86800
276
+ }
277
+ ];
278
+ registry.register({
279
+ name: 'copilot-started',
280
+ type: 'copilot',
281
+ pid: 86800,
282
+ tmuxSession: 'copilot-started',
283
+ cwd: '/repo',
284
+ startedAt: '2026-06-13T19:15:16.211Z',
285
+ sessionId: 'pid-86800',
286
+ sessionFilePath: ''
287
+ });
288
+ mockedListAgentProcesses.mockReturnValue(processes);
289
+ mockedEnrichProcesses.mockReturnValue(processes);
290
+ const agents = await adapter.detectAgents();
291
+ expect(agents).toHaveLength(1);
292
+ expect(agents[0]).toMatchObject({
293
+ name: 'copilot-started',
294
+ pid: 86810,
295
+ sessionId: 'pid-86810'
296
+ });
297
+ });
226
298
  it('does not add duplicate process-only agent for wrapper process in the same terminal', async ()=>{
227
299
  const processes = [
228
300
  {
@@ -260,6 +332,59 @@ describe('CopilotAdapter', ()=>{
260
332
  sessionId: 'sess-wrapper'
261
333
  });
262
334
  });
335
+ it('carries the managed wrapper name to a lock-backed child process', async ()=>{
336
+ const registry = new AgentRegistry(path.join(tmpDir, 'agents.json'));
337
+ adapter = new CopilotAdapter(registry);
338
+ adapter.sessionStateDir = sessionStateDir;
339
+ const processes = [
340
+ {
341
+ pid: 14095,
342
+ command: 'copilot',
343
+ cwd: '/repo',
344
+ tty: 'ttys001',
345
+ ppid: 84174
346
+ },
347
+ {
348
+ pid: 14096,
349
+ command: '/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot',
350
+ cwd: '/repo',
351
+ tty: 'ttys001',
352
+ ppid: 14095
353
+ }
354
+ ];
355
+ registry.register({
356
+ name: 'copilot-started',
357
+ type: 'copilot',
358
+ pid: 14095,
359
+ tmuxSession: 'copilot-started',
360
+ cwd: '/repo',
361
+ startedAt: '2026-06-13T19:15:16.211Z',
362
+ sessionId: 'pid-14095',
363
+ sessionFilePath: ''
364
+ });
365
+ mockedListAgentProcesses.mockReturnValue(processes);
366
+ mockedEnrichProcesses.mockReturnValue(processes);
367
+ writeSession('sess-wrapper', {
368
+ lockPid: 14096,
369
+ events: [
370
+ sessionStart('sess-wrapper', '/repo', '2026-06-09T09:50:00.000Z'),
371
+ {
372
+ type: 'user.message',
373
+ data: {
374
+ content: 'hello'
375
+ },
376
+ timestamp: new Date().toISOString()
377
+ }
378
+ ]
379
+ });
380
+ const agents = await adapter.detectAgents();
381
+ expect(agents).toHaveLength(1);
382
+ expect(agents[0]).toMatchObject({
383
+ name: 'copilot-started',
384
+ pid: 14096,
385
+ sessionId: 'sess-wrapper'
386
+ });
387
+ });
263
388
  it('uses workspace metadata when events are missing', async ()=>{
264
389
  const processes = [
265
390
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/__tests__/adapters/CopilotAdapter.test.ts"],"sourcesContent":["/**\n * Tests for CopilotAdapter\n */\n\nimport type { MockedFunction } from 'vitest';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { CopilotAdapter } from '../../adapters/CopilotAdapter.js';\nimport type { ProcessInfo } from '../../adapters/AgentAdapter.js';\nimport { AgentStatus } from '../../adapters/AgentAdapter.js';\nimport { listAgentProcesses, enrichProcesses } from '../../utils/process.js';\nimport { generateAgentName } from '../../utils/matching.js';\n\nvi.mock('../../utils/process.js', () => ({\n listAgentProcesses: vi.fn(),\n enrichProcesses: vi.fn(),\n}));\n\nvi.mock('../../utils/matching.js', () => ({\n generateAgentName: vi.fn(),\n}));\n\nconst mockedListAgentProcesses = listAgentProcesses as MockedFunction<typeof listAgentProcesses>;\nconst mockedEnrichProcesses = enrichProcesses as MockedFunction<typeof enrichProcesses>;\nconst mockedGenerateAgentName = generateAgentName as MockedFunction<typeof generateAgentName>;\n\ndescribe('CopilotAdapter', () => {\n let adapter: CopilotAdapter;\n let tmpDir: string;\n let sessionStateDir: string;\n\n beforeEach(() => {\n adapter = new CopilotAdapter();\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'copilot-test-'));\n sessionStateDir = path.join(tmpDir, 'session-state');\n fs.mkdirSync(sessionStateDir, { recursive: true });\n (adapter as any).sessionStateDir = sessionStateDir;\n\n mockedListAgentProcesses.mockReset();\n mockedEnrichProcesses.mockReset();\n mockedGenerateAgentName.mockReset();\n mockedEnrichProcesses.mockImplementation((procs) => procs);\n mockedGenerateAgentName.mockImplementation((cwd, pid) => `${path.basename(cwd) || 'unknown'} (${pid})`);\n });\n\n afterEach(() => {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n function writeSession(\n sessionId: string,\n options: {\n events?: Array<object | string>;\n workspace?: Record<string, string>;\n lockPid?: number | string;\n },\n ): string {\n const sessionDir = path.join(sessionStateDir, sessionId);\n fs.mkdirSync(sessionDir, { recursive: true });\n\n if (options.events) {\n const lines = options.events.map((entry) => (\n typeof entry === 'string' ? entry : JSON.stringify(entry)\n ));\n fs.writeFileSync(path.join(sessionDir, 'events.jsonl'), lines.join('\\n'));\n }\n\n if (options.workspace) {\n const lines = Object.entries(options.workspace).map(([key, value]) => `${key}: ${value}`);\n fs.writeFileSync(path.join(sessionDir, 'workspace.yaml'), lines.join('\\n'));\n }\n\n if (options.lockPid !== undefined) {\n fs.writeFileSync(path.join(sessionDir, `inuse.${options.lockPid}.lock`), '');\n }\n\n return sessionDir;\n }\n\n function sessionStart(sessionId: string, cwd: string, startTime = '2026-06-09T09:50:00.000Z'): object {\n return {\n type: 'session.start',\n data: {\n sessionId,\n startTime,\n context: {\n cwd,\n gitRoot: cwd,\n branch: 'main',\n },\n },\n timestamp: startTime,\n };\n }\n\n describe('initialization', () => {\n it('exposes copilot type', () => {\n expect(adapter.type).toBe('copilot');\n });\n });\n\n describe('canHandle', () => {\n it('returns true for copilot commands', () => {\n expect(adapter.canHandle({ pid: 1, command: 'copilot', cwd: '/repo', tty: 'ttys001' })).toBe(true);\n });\n\n it('returns true for full-path Homebrew copilot commands', () => {\n expect(adapter.canHandle({\n pid: 2,\n command: '/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot',\n cwd: '/repo',\n tty: 'ttys002',\n })).toBe(true);\n });\n\n it('returns true for copilot.exe commands', () => {\n expect(adapter.canHandle({ pid: 3, command: '/usr/bin/copilot.exe', cwd: '/repo', tty: 'ttys003' })).toBe(true);\n });\n\n it('returns false when copilot appears only in an argument', () => {\n expect(adapter.canHandle({\n pid: 4,\n command: 'node /repo/copilot-plugin/index.js',\n cwd: '/repo',\n tty: 'ttys004',\n })).toBe(false);\n });\n });\n\n describe('detectAgents', () => {\n it('returns empty list when no copilot process is running', async () => {\n mockedListAgentProcesses.mockReturnValue([]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toEqual([]);\n expect(mockedListAgentProcesses).toHaveBeenCalledWith('copilot');\n });\n\n it('maps matching inuse lock and events to an active agent', async () => {\n const processes: ProcessInfo[] = [\n { pid: 14096, command: '/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot', cwd: '/repo', tty: 'ttys001' },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession('sess-a', {\n lockPid: 14096,\n events: [\n sessionStart('sess-a', '/repo', '2026-06-09T09:50:00.000Z'),\n { type: 'user.message', data: { content: 'Build the Copilot adapter' }, timestamp: new Date().toISOString() },\n { type: 'assistant.message', data: { content: 'I will inspect the code.' }, timestamp: new Date().toISOString() },\n ],\n workspace: {\n id: 'sess-a',\n cwd: '/fallback',\n name: 'Fallback Name',\n created_at: '2026-06-09T09:49:00.000Z',\n updated_at: '2026-06-09T09:51:00.000Z',\n },\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: 'copilot',\n status: AgentStatus.WAITING,\n pid: 14096,\n projectPath: '/repo',\n sessionId: 'sess-a',\n summary: 'Build the Copilot adapter',\n sessionFilePath: path.join(sessionStateDir, 'sess-a', 'events.jsonl'),\n });\n });\n\n it('ignores invalid and unmatched lock PIDs', async () => {\n const processes: ProcessInfo[] = [\n { pid: 100, command: 'copilot', cwd: '/repo', tty: 'ttys001' },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession('invalid', {\n lockPid: 'not-a-pid',\n events: [sessionStart('invalid', '/invalid')],\n });\n writeSession('unmatched', {\n lockPid: 999999,\n events: [sessionStart('unmatched', '/unmatched')],\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 100,\n sessionId: 'pid-100',\n summary: 'Copilot process running',\n });\n });\n\n it('falls back to process-only agent when no session lock matches', async () => {\n const processes: ProcessInfo[] = [\n { pid: 200, command: 'copilot', cwd: '/repo-b', tty: 'ttys002' },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: 'copilot',\n status: AgentStatus.RUNNING,\n pid: 200,\n projectPath: '/repo-b',\n sessionId: 'pid-200',\n summary: 'Copilot process running',\n });\n });\n\n it('does not add duplicate process-only agent for wrapper process in the same terminal', async () => {\n const processes: ProcessInfo[] = [\n { pid: 14095, command: 'copilot', cwd: '/repo', tty: 'ttys001' },\n { pid: 14096, command: '/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot', cwd: '/repo', tty: 'ttys001' },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession('sess-wrapper', {\n lockPid: 14096,\n events: [\n sessionStart('sess-wrapper', '/repo', '2026-06-09T09:50:00.000Z'),\n { type: 'user.message', data: { content: 'hello' }, timestamp: new Date().toISOString() },\n ],\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 14096,\n sessionId: 'sess-wrapper',\n });\n });\n\n it('uses workspace metadata when events are missing', async () => {\n const processes: ProcessInfo[] = [\n { pid: 300, command: 'copilot', cwd: '/proc-cwd', tty: 'ttys003' },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession('workspace-only', {\n lockPid: 300,\n workspace: {\n id: 'workspace-only',\n cwd: '/workspace-cwd',\n name: 'Workspace Session',\n created_at: '2026-06-09T09:00:00.000Z',\n updated_at: '2026-06-09T09:10:00.000Z',\n },\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 300,\n projectPath: '/workspace-cwd',\n sessionId: 'workspace-only',\n summary: 'Workspace Session',\n });\n });\n });\n\n describe('getConversation', () => {\n it('parses user and assistant message events', () => {\n const sessionDir = writeSession('conv', {\n events: [\n sessionStart('conv', '/repo'),\n { type: 'user.message', data: { content: 'hello' }, timestamp: '2026-06-09T09:50:01.000Z' },\n { type: 'assistant.message', data: { content: 'Hi there' }, timestamp: '2026-06-09T09:50:02.000Z' },\n ],\n });\n\n const messages = adapter.getConversation(path.join(sessionDir, 'events.jsonl'));\n\n expect(messages).toEqual([\n { role: 'user', content: 'hello', timestamp: '2026-06-09T09:50:01.000Z' },\n { role: 'assistant', content: 'Hi there', timestamp: '2026-06-09T09:50:02.000Z' },\n ]);\n });\n\n it('includes system/info/warning messages only in verbose mode', () => {\n const sessionDir = writeSession('verbose', {\n events: [\n { type: 'session.warning', data: { message: 'MCP failed' }, timestamp: '2026-06-09T09:50:01.000Z' },\n { type: 'tool.execution_complete', data: { result: { content: 'Tool result' } }, timestamp: '2026-06-09T09:50:02.000Z' },\n ],\n });\n const filePath = path.join(sessionDir, 'events.jsonl');\n\n expect(adapter.getConversation(filePath)).toEqual([]);\n expect(adapter.getConversation(filePath, { verbose: true })).toEqual([\n { role: 'system', content: 'MCP failed', timestamp: '2026-06-09T09:50:01.000Z' },\n { role: 'system', content: 'Tool result', timestamp: '2026-06-09T09:50:02.000Z' },\n ]);\n });\n\n it('skips malformed JSONL lines and missing text', () => {\n const sessionDir = writeSession('malformed', {\n events: [\n 'not json',\n { type: 'user.message', data: { content: 'valid' }, timestamp: '2026-06-09T09:50:01.000Z' },\n { type: 'assistant.message', data: {}, timestamp: '2026-06-09T09:50:02.000Z' },\n ],\n });\n\n const messages = adapter.getConversation(path.join(sessionDir, 'events.jsonl'));\n\n expect(messages).toEqual([\n { role: 'user', content: 'valid', timestamp: '2026-06-09T09:50:01.000Z' },\n ]);\n });\n\n it('returns empty array for missing file', () => {\n expect(adapter.getConversation(path.join(tmpDir, 'missing.jsonl'))).toEqual([]);\n });\n });\n\n describe('listSessions', () => {\n it('returns historical sessions without active locks', async () => {\n writeSession('history-a', {\n events: [\n sessionStart('history-a', '/repo-a', '2026-06-09T09:00:00.000Z'),\n { type: 'user.message', data: { content: 'first user' }, timestamp: '2026-06-09T09:00:01.000Z' },\n { type: 'assistant.message', data: { content: 'answer' }, timestamp: '2026-06-09T09:00:02.000Z' },\n ],\n });\n writeSession('history-b', {\n events: [\n sessionStart('history-b', '/repo-b', '2026-06-09T10:00:00.000Z'),\n { type: 'user.message', data: { content: 'other user' }, timestamp: '2026-06-09T10:00:01.000Z' },\n ],\n });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toHaveLength(2);\n const byId = Object.fromEntries(sessions.map((session) => [session.sessionId, session]));\n expect(byId['history-a']).toMatchObject({\n type: 'copilot',\n cwd: '/repo-a',\n firstUserMessage: 'first user',\n sessionFilePath: path.join(sessionStateDir, 'history-a', 'events.jsonl'),\n });\n expect(byId['history-b']).toMatchObject({\n type: 'copilot',\n cwd: '/repo-b',\n firstUserMessage: 'other user',\n });\n });\n\n it('applies strict cwd filter', async () => {\n writeSession('keep', { events: [sessionStart('keep', '/repo')] });\n writeSession('drop', { events: [sessionStart('drop', '/other')] });\n\n const sessions = await adapter.listSessions({ cwd: '/repo' });\n\n expect(sessions).toHaveLength(1);\n expect(sessions[0].sessionId).toBe('keep');\n });\n\n it('uses workspace fallback for historical sessions without events', async () => {\n writeSession('workspace-history', {\n workspace: {\n id: 'workspace-history',\n cwd: '/workspace-repo',\n name: 'Workspace History',\n created_at: '2026-06-09T08:00:00.000Z',\n updated_at: '2026-06-09T08:10:00.000Z',\n },\n });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toHaveLength(1);\n expect(sessions[0]).toMatchObject({\n type: 'copilot',\n sessionId: 'workspace-history',\n cwd: '/workspace-repo',\n firstUserMessage: '',\n sessionFilePath: path.join(sessionStateDir, 'workspace-history', 'events.jsonl'),\n });\n });\n\n it('skips session directories without events or workspace metadata', async () => {\n fs.mkdirSync(path.join(sessionStateDir, 'empty'), { recursive: true });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toEqual([]);\n });\n\n it('returns empty when session-state directory does not exist', async () => {\n (adapter as any).sessionStateDir = path.join(tmpDir, 'missing');\n\n await expect(adapter.listSessions()).resolves.toEqual([]);\n });\n });\n});\n"],"names":["fs","os","path","CopilotAdapter","AgentStatus","listAgentProcesses","enrichProcesses","generateAgentName","vi","mock","fn","mockedListAgentProcesses","mockedEnrichProcesses","mockedGenerateAgentName","describe","adapter","tmpDir","sessionStateDir","beforeEach","mkdtempSync","join","tmpdir","mkdirSync","recursive","mockReset","mockImplementation","procs","cwd","pid","basename","afterEach","rmSync","force","writeSession","sessionId","options","sessionDir","events","lines","map","entry","JSON","stringify","writeFileSync","workspace","Object","entries","key","value","lockPid","undefined","sessionStart","startTime","type","data","context","gitRoot","branch","timestamp","it","expect","toBe","canHandle","command","tty","mockReturnValue","agents","detectAgents","toEqual","toHaveBeenCalledWith","processes","content","Date","toISOString","id","name","created_at","updated_at","toHaveLength","toMatchObject","status","WAITING","projectPath","summary","sessionFilePath","RUNNING","messages","getConversation","role","message","result","filePath","verbose","sessions","listSessions","byId","fromEntries","session","firstUserMessage","resolves"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/__tests__/adapters/CopilotAdapter.test.ts"],"sourcesContent":["/**\n * Tests for CopilotAdapter\n */\n\nimport type { MockedFunction } from 'vitest';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { CopilotAdapter } from '../../adapters/CopilotAdapter.js';\nimport type { ProcessInfo } from '../../adapters/AgentAdapter.js';\nimport { AgentStatus } from '../../adapters/AgentAdapter.js';\nimport { listAgentProcesses, enrichProcesses } from '../../utils/process.js';\nimport { generateAgentName } from '../../utils/matching.js';\nimport { AgentRegistry } from '../../utils/AgentRegistry.js';\n\nvi.mock('../../utils/process.js', async (importOriginal) => {\n const actual = await importOriginal() as typeof import('../../utils/process.js');\n return {\n ...actual,\n listAgentProcesses: vi.fn(),\n enrichProcesses: vi.fn(),\n };\n});\n\nvi.mock('../../utils/matching.js', () => ({\n generateAgentName: vi.fn(),\n}));\n\nconst mockedListAgentProcesses = listAgentProcesses as MockedFunction<typeof listAgentProcesses>;\nconst mockedEnrichProcesses = enrichProcesses as MockedFunction<typeof enrichProcesses>;\nconst mockedGenerateAgentName = generateAgentName as MockedFunction<typeof generateAgentName>;\n\ndescribe('CopilotAdapter', () => {\n let adapter: CopilotAdapter;\n let tmpDir: string;\n let sessionStateDir: string;\n\n beforeEach(() => {\n adapter = new CopilotAdapter();\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'copilot-test-'));\n sessionStateDir = path.join(tmpDir, 'session-state');\n fs.mkdirSync(sessionStateDir, { recursive: true });\n (adapter as any).sessionStateDir = sessionStateDir;\n\n mockedListAgentProcesses.mockReset();\n mockedEnrichProcesses.mockReset();\n mockedGenerateAgentName.mockReset();\n mockedEnrichProcesses.mockImplementation((procs) => procs);\n mockedGenerateAgentName.mockImplementation((cwd, pid) => `${path.basename(cwd) || 'unknown'} (${pid})`);\n });\n\n afterEach(() => {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n function writeSession(\n sessionId: string,\n options: {\n events?: Array<object | string>;\n workspace?: Record<string, string>;\n lockPid?: number | string;\n },\n ): string {\n const sessionDir = path.join(sessionStateDir, sessionId);\n fs.mkdirSync(sessionDir, { recursive: true });\n\n if (options.events) {\n const lines = options.events.map((entry) => (\n typeof entry === 'string' ? entry : JSON.stringify(entry)\n ));\n fs.writeFileSync(path.join(sessionDir, 'events.jsonl'), lines.join('\\n'));\n }\n\n if (options.workspace) {\n const lines = Object.entries(options.workspace).map(([key, value]) => `${key}: ${value}`);\n fs.writeFileSync(path.join(sessionDir, 'workspace.yaml'), lines.join('\\n'));\n }\n\n if (options.lockPid !== undefined) {\n fs.writeFileSync(path.join(sessionDir, `inuse.${options.lockPid}.lock`), '');\n }\n\n return sessionDir;\n }\n\n function sessionStart(sessionId: string, cwd: string, startTime = '2026-06-09T09:50:00.000Z'): object {\n return {\n type: 'session.start',\n data: {\n sessionId,\n startTime,\n context: {\n cwd,\n gitRoot: cwd,\n branch: 'main',\n },\n },\n timestamp: startTime,\n };\n }\n\n describe('initialization', () => {\n it('exposes copilot type', () => {\n expect(adapter.type).toBe('copilot');\n });\n });\n\n describe('canHandle', () => {\n it('returns true for copilot commands', () => {\n expect(adapter.canHandle({ pid: 1, command: 'copilot', cwd: '/repo', tty: 'ttys001' })).toBe(true);\n });\n\n it('returns true for full-path Homebrew copilot commands', () => {\n expect(adapter.canHandle({\n pid: 2,\n command: '/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot',\n cwd: '/repo',\n tty: 'ttys002',\n })).toBe(true);\n });\n\n it('returns true for copilot.exe commands', () => {\n expect(adapter.canHandle({ pid: 3, command: '/usr/bin/copilot.exe', cwd: '/repo', tty: 'ttys003' })).toBe(true);\n });\n\n it('returns false when copilot appears only in an argument', () => {\n expect(adapter.canHandle({\n pid: 4,\n command: 'node /repo/copilot-plugin/index.js',\n cwd: '/repo',\n tty: 'ttys004',\n })).toBe(false);\n });\n });\n\n describe('detectAgents', () => {\n it('returns empty list when no copilot process is running', async () => {\n mockedListAgentProcesses.mockReturnValue([]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toEqual([]);\n expect(mockedListAgentProcesses).toHaveBeenCalledWith('copilot');\n });\n\n it('maps matching inuse lock and events to an active agent', async () => {\n const processes: ProcessInfo[] = [\n { pid: 14096, command: '/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot', cwd: '/repo', tty: 'ttys001' },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession('sess-a', {\n lockPid: 14096,\n events: [\n sessionStart('sess-a', '/repo', '2026-06-09T09:50:00.000Z'),\n { type: 'user.message', data: { content: 'Build the Copilot adapter' }, timestamp: new Date().toISOString() },\n { type: 'assistant.message', data: { content: 'I will inspect the code.' }, timestamp: new Date().toISOString() },\n ],\n workspace: {\n id: 'sess-a',\n cwd: '/fallback',\n name: 'Fallback Name',\n created_at: '2026-06-09T09:49:00.000Z',\n updated_at: '2026-06-09T09:51:00.000Z',\n },\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: 'copilot',\n status: AgentStatus.WAITING,\n pid: 14096,\n projectPath: '/repo',\n sessionId: 'sess-a',\n summary: 'Build the Copilot adapter',\n sessionFilePath: path.join(sessionStateDir, 'sess-a', 'events.jsonl'),\n });\n });\n\n it('ignores invalid and unmatched lock PIDs', async () => {\n const processes: ProcessInfo[] = [\n { pid: 100, command: 'copilot', cwd: '/repo', tty: 'ttys001' },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession('invalid', {\n lockPid: 'not-a-pid',\n events: [sessionStart('invalid', '/invalid')],\n });\n writeSession('unmatched', {\n lockPid: 999999,\n events: [sessionStart('unmatched', '/unmatched')],\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 100,\n sessionId: 'pid-100',\n summary: 'Copilot process running',\n });\n });\n\n it('falls back to process-only agent when no session lock matches', async () => {\n const processes: ProcessInfo[] = [\n { pid: 200, command: 'copilot', cwd: '/repo-b', tty: 'ttys002' },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: 'copilot',\n status: AgentStatus.RUNNING,\n pid: 200,\n projectPath: '/repo-b',\n sessionId: 'pid-200',\n summary: 'Copilot process running',\n });\n });\n\n it('suppresses wrapper process-only agents before the session lock exists', async () => {\n const processes: ProcessInfo[] = [\n { pid: 86800, command: 'copilot', cwd: '/repo', tty: 'ttys001', ppid: 84174 },\n { pid: 86810, command: '/custom/install/copilot', cwd: '/repo', tty: 'ttys001', ppid: 86800 },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 86810,\n sessionId: 'pid-86810',\n summary: 'Copilot process running',\n });\n });\n\n it('carries the managed wrapper name to a process-only child before the session lock exists', async () => {\n const registry = new AgentRegistry(path.join(tmpDir, 'agents.json'));\n adapter = new CopilotAdapter(registry);\n (adapter as any).sessionStateDir = sessionStateDir;\n const processes: ProcessInfo[] = [\n { pid: 86800, command: 'copilot', cwd: '/repo', tty: 'ttys001', ppid: 84174 },\n { pid: 86810, command: '/custom/install/copilot', cwd: '/repo', tty: 'ttys001', ppid: 86800 },\n ];\n registry.register({\n name: 'copilot-started',\n type: 'copilot',\n pid: 86800,\n tmuxSession: 'copilot-started',\n cwd: '/repo',\n startedAt: '2026-06-13T19:15:16.211Z',\n sessionId: 'pid-86800',\n sessionFilePath: '',\n });\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n name: 'copilot-started',\n pid: 86810,\n sessionId: 'pid-86810',\n });\n });\n\n it('does not add duplicate process-only agent for wrapper process in the same terminal', async () => {\n const processes: ProcessInfo[] = [\n { pid: 14095, command: 'copilot', cwd: '/repo', tty: 'ttys001' },\n { pid: 14096, command: '/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot', cwd: '/repo', tty: 'ttys001' },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession('sess-wrapper', {\n lockPid: 14096,\n events: [\n sessionStart('sess-wrapper', '/repo', '2026-06-09T09:50:00.000Z'),\n { type: 'user.message', data: { content: 'hello' }, timestamp: new Date().toISOString() },\n ],\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 14096,\n sessionId: 'sess-wrapper',\n });\n });\n\n it('carries the managed wrapper name to a lock-backed child process', async () => {\n const registry = new AgentRegistry(path.join(tmpDir, 'agents.json'));\n adapter = new CopilotAdapter(registry);\n (adapter as any).sessionStateDir = sessionStateDir;\n const processes: ProcessInfo[] = [\n { pid: 14095, command: 'copilot', cwd: '/repo', tty: 'ttys001', ppid: 84174 },\n { pid: 14096, command: '/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot', cwd: '/repo', tty: 'ttys001', ppid: 14095 },\n ];\n registry.register({\n name: 'copilot-started',\n type: 'copilot',\n pid: 14095,\n tmuxSession: 'copilot-started',\n cwd: '/repo',\n startedAt: '2026-06-13T19:15:16.211Z',\n sessionId: 'pid-14095',\n sessionFilePath: '',\n });\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession('sess-wrapper', {\n lockPid: 14096,\n events: [\n sessionStart('sess-wrapper', '/repo', '2026-06-09T09:50:00.000Z'),\n { type: 'user.message', data: { content: 'hello' }, timestamp: new Date().toISOString() },\n ],\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n name: 'copilot-started',\n pid: 14096,\n sessionId: 'sess-wrapper',\n });\n });\n\n it('uses workspace metadata when events are missing', async () => {\n const processes: ProcessInfo[] = [\n { pid: 300, command: 'copilot', cwd: '/proc-cwd', tty: 'ttys003' },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession('workspace-only', {\n lockPid: 300,\n workspace: {\n id: 'workspace-only',\n cwd: '/workspace-cwd',\n name: 'Workspace Session',\n created_at: '2026-06-09T09:00:00.000Z',\n updated_at: '2026-06-09T09:10:00.000Z',\n },\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 300,\n projectPath: '/workspace-cwd',\n sessionId: 'workspace-only',\n summary: 'Workspace Session',\n });\n });\n });\n\n describe('getConversation', () => {\n it('parses user and assistant message events', () => {\n const sessionDir = writeSession('conv', {\n events: [\n sessionStart('conv', '/repo'),\n { type: 'user.message', data: { content: 'hello' }, timestamp: '2026-06-09T09:50:01.000Z' },\n { type: 'assistant.message', data: { content: 'Hi there' }, timestamp: '2026-06-09T09:50:02.000Z' },\n ],\n });\n\n const messages = adapter.getConversation(path.join(sessionDir, 'events.jsonl'));\n\n expect(messages).toEqual([\n { role: 'user', content: 'hello', timestamp: '2026-06-09T09:50:01.000Z' },\n { role: 'assistant', content: 'Hi there', timestamp: '2026-06-09T09:50:02.000Z' },\n ]);\n });\n\n it('includes system/info/warning messages only in verbose mode', () => {\n const sessionDir = writeSession('verbose', {\n events: [\n { type: 'session.warning', data: { message: 'MCP failed' }, timestamp: '2026-06-09T09:50:01.000Z' },\n { type: 'tool.execution_complete', data: { result: { content: 'Tool result' } }, timestamp: '2026-06-09T09:50:02.000Z' },\n ],\n });\n const filePath = path.join(sessionDir, 'events.jsonl');\n\n expect(adapter.getConversation(filePath)).toEqual([]);\n expect(adapter.getConversation(filePath, { verbose: true })).toEqual([\n { role: 'system', content: 'MCP failed', timestamp: '2026-06-09T09:50:01.000Z' },\n { role: 'system', content: 'Tool result', timestamp: '2026-06-09T09:50:02.000Z' },\n ]);\n });\n\n it('skips malformed JSONL lines and missing text', () => {\n const sessionDir = writeSession('malformed', {\n events: [\n 'not json',\n { type: 'user.message', data: { content: 'valid' }, timestamp: '2026-06-09T09:50:01.000Z' },\n { type: 'assistant.message', data: {}, timestamp: '2026-06-09T09:50:02.000Z' },\n ],\n });\n\n const messages = adapter.getConversation(path.join(sessionDir, 'events.jsonl'));\n\n expect(messages).toEqual([\n { role: 'user', content: 'valid', timestamp: '2026-06-09T09:50:01.000Z' },\n ]);\n });\n\n it('returns empty array for missing file', () => {\n expect(adapter.getConversation(path.join(tmpDir, 'missing.jsonl'))).toEqual([]);\n });\n });\n\n describe('listSessions', () => {\n it('returns historical sessions without active locks', async () => {\n writeSession('history-a', {\n events: [\n sessionStart('history-a', '/repo-a', '2026-06-09T09:00:00.000Z'),\n { type: 'user.message', data: { content: 'first user' }, timestamp: '2026-06-09T09:00:01.000Z' },\n { type: 'assistant.message', data: { content: 'answer' }, timestamp: '2026-06-09T09:00:02.000Z' },\n ],\n });\n writeSession('history-b', {\n events: [\n sessionStart('history-b', '/repo-b', '2026-06-09T10:00:00.000Z'),\n { type: 'user.message', data: { content: 'other user' }, timestamp: '2026-06-09T10:00:01.000Z' },\n ],\n });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toHaveLength(2);\n const byId = Object.fromEntries(sessions.map((session) => [session.sessionId, session]));\n expect(byId['history-a']).toMatchObject({\n type: 'copilot',\n cwd: '/repo-a',\n firstUserMessage: 'first user',\n sessionFilePath: path.join(sessionStateDir, 'history-a', 'events.jsonl'),\n });\n expect(byId['history-b']).toMatchObject({\n type: 'copilot',\n cwd: '/repo-b',\n firstUserMessage: 'other user',\n });\n });\n\n it('applies strict cwd filter', async () => {\n writeSession('keep', { events: [sessionStart('keep', '/repo')] });\n writeSession('drop', { events: [sessionStart('drop', '/other')] });\n\n const sessions = await adapter.listSessions({ cwd: '/repo' });\n\n expect(sessions).toHaveLength(1);\n expect(sessions[0].sessionId).toBe('keep');\n });\n\n it('uses workspace fallback for historical sessions without events', async () => {\n writeSession('workspace-history', {\n workspace: {\n id: 'workspace-history',\n cwd: '/workspace-repo',\n name: 'Workspace History',\n created_at: '2026-06-09T08:00:00.000Z',\n updated_at: '2026-06-09T08:10:00.000Z',\n },\n });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toHaveLength(1);\n expect(sessions[0]).toMatchObject({\n type: 'copilot',\n sessionId: 'workspace-history',\n cwd: '/workspace-repo',\n firstUserMessage: '',\n sessionFilePath: path.join(sessionStateDir, 'workspace-history', 'events.jsonl'),\n });\n });\n\n it('skips session directories without events or workspace metadata', async () => {\n fs.mkdirSync(path.join(sessionStateDir, 'empty'), { recursive: true });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toEqual([]);\n });\n\n it('returns empty when session-state directory does not exist', async () => {\n (adapter as any).sessionStateDir = path.join(tmpDir, 'missing');\n\n await expect(adapter.listSessions()).resolves.toEqual([]);\n });\n });\n});\n"],"names":["fs","os","path","CopilotAdapter","AgentStatus","listAgentProcesses","enrichProcesses","generateAgentName","AgentRegistry","vi","mock","importOriginal","actual","fn","mockedListAgentProcesses","mockedEnrichProcesses","mockedGenerateAgentName","describe","adapter","tmpDir","sessionStateDir","beforeEach","mkdtempSync","join","tmpdir","mkdirSync","recursive","mockReset","mockImplementation","procs","cwd","pid","basename","afterEach","rmSync","force","writeSession","sessionId","options","sessionDir","events","lines","map","entry","JSON","stringify","writeFileSync","workspace","Object","entries","key","value","lockPid","undefined","sessionStart","startTime","type","data","context","gitRoot","branch","timestamp","it","expect","toBe","canHandle","command","tty","mockReturnValue","agents","detectAgents","toEqual","toHaveBeenCalledWith","processes","content","Date","toISOString","id","name","created_at","updated_at","toHaveLength","toMatchObject","status","WAITING","projectPath","summary","sessionFilePath","RUNNING","ppid","registry","register","tmuxSession","startedAt","messages","getConversation","role","message","result","filePath","verbose","sessions","listSessions","byId","fromEntries","session","firstUserMessage","resolves"],"mappings":"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"}
@@ -9,10 +9,14 @@ import { AgentRegistry } from '../../utils/AgentRegistry.js';
9
9
  import { listAgentProcesses, enrichProcesses } from '../../utils/process.js';
10
10
  import { matchProcessesToSessions, generateAgentName } from '../../utils/matching.js';
11
11
  import * as crypto from 'crypto';
12
- vi.mock('../../utils/process.js', ()=>({
12
+ vi.mock('../../utils/process.js', async (importOriginal)=>{
13
+ const actual = await importOriginal();
14
+ return {
15
+ ...actual,
13
16
  listAgentProcesses: vi.fn(),
14
17
  enrichProcesses: vi.fn()
15
- }));
18
+ };
19
+ });
16
20
  vi.mock('../../utils/matching.js', ()=>({
17
21
  matchProcessesToSessions: vi.fn(),
18
22
  generateAgentName: vi.fn()
@@ -150,6 +154,77 @@ describe('GeminiCliAdapter', ()=>{
150
154
  sessionId: 'pid-1234'
151
155
  });
152
156
  });
157
+ it('should suppress Gemini wrapper process-only agents before a session file exists', async ()=>{
158
+ const wrapperProc = {
159
+ pid: 20452,
160
+ ppid: 17530,
161
+ command: '/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini',
162
+ cwd: '/repo',
163
+ tty: 'ttys007',
164
+ startTime: new Date('2026-06-13T08:25:21Z')
165
+ };
166
+ const childProc = {
167
+ pid: 21373,
168
+ ppid: 20452,
169
+ command: '/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini',
170
+ cwd: '/repo',
171
+ tty: 'ttys007',
172
+ startTime: new Date('2026-06-13T08:25:26Z')
173
+ };
174
+ mockedListAgentProcesses.mockReturnValue([
175
+ wrapperProc,
176
+ childProc
177
+ ]);
178
+ const agents = await adapter.detectAgents();
179
+ expect(agents).toHaveLength(1);
180
+ expect(agents[0]).toMatchObject({
181
+ pid: 21373,
182
+ sessionId: 'pid-21373',
183
+ summary: 'Gemini CLI process running'
184
+ });
185
+ });
186
+ it('should carry the managed wrapper name to a process-only child before a session file exists', async ()=>{
187
+ const regPath = path.join(tmpHome, 'agents.json');
188
+ const registry = new AgentRegistry(regPath);
189
+ const namedAdapter = new GeminiCliAdapter(registry);
190
+ const wrapperProc = {
191
+ pid: 35792,
192
+ ppid: 33068,
193
+ command: '/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini',
194
+ cwd: '/repo',
195
+ tty: 'ttys002',
196
+ startTime: new Date('2026-06-13T19:15:16Z')
197
+ };
198
+ const childProc = {
199
+ pid: 36514,
200
+ ppid: 35792,
201
+ command: '/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini',
202
+ cwd: '/repo',
203
+ tty: 'ttys002',
204
+ startTime: new Date('2026-06-13T19:15:18Z')
205
+ };
206
+ registry.register({
207
+ name: 'cli-mqcqj469',
208
+ type: 'gemini_cli',
209
+ pid: wrapperProc.pid,
210
+ tmuxSession: 'cli-mqcqj469',
211
+ cwd: wrapperProc.cwd,
212
+ startedAt: '2026-06-13T19:15:16.211Z',
213
+ sessionId: `pid-${wrapperProc.pid}`,
214
+ sessionFilePath: ''
215
+ });
216
+ mockedListAgentProcesses.mockReturnValue([
217
+ wrapperProc,
218
+ childProc
219
+ ]);
220
+ const agents = await namedAdapter.detectAgents();
221
+ expect(agents).toHaveLength(1);
222
+ expect(agents[0]).toMatchObject({
223
+ name: 'cli-mqcqj469',
224
+ pid: childProc.pid,
225
+ sessionId: `pid-${childProc.pid}`
226
+ });
227
+ });
153
228
  it('should map a process to its matching session file via projectHash', async ()=>{
154
229
  const cwd = '/repo/project-a';
155
230
  const projectHash = hashProjectRoot(cwd);
@@ -433,6 +508,73 @@ describe('GeminiCliAdapter', ()=>{
433
508
  const agents = await cachedAdapter.detectAgents();
434
509
  expect(agents[0].sessionId).toBe('pid-100');
435
510
  });
511
+ it('carries the managed wrapper name to the detected child process', async ()=>{
512
+ const wrapperProc = {
513
+ pid: 20339,
514
+ ppid: 17570,
515
+ command: '/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini',
516
+ cwd: '/repo-a',
517
+ tty: 'ttys002',
518
+ startTime: new Date('2026-06-13T19:00:53Z')
519
+ };
520
+ const childProc = {
521
+ pid: 21038,
522
+ ppid: 20339,
523
+ command: '/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini',
524
+ cwd: '/repo-a',
525
+ tty: 'ttys002',
526
+ startTime: new Date('2026-06-13T19:00:57Z')
527
+ };
528
+ const now = new Date().toISOString();
529
+ sessionFilePath = writeSession(tmpHome, 'cli-2', 'session-2026-06-13T19-00-s-cached', {
530
+ sessionId: 's-cached',
531
+ projectHash: hashProjectRoot('/repo-a'),
532
+ startTime: now,
533
+ lastUpdated: now,
534
+ directories: [
535
+ '/repo-a'
536
+ ],
537
+ messages: [
538
+ {
539
+ id: 'm1',
540
+ timestamp: now,
541
+ type: 'user',
542
+ content: 'Hello from child process'
543
+ }
544
+ ]
545
+ });
546
+ registerEntry({
547
+ name: 'cli-mqcq0mg5',
548
+ pid: wrapperProc.pid,
549
+ tmuxSession: 'cli-mqcq0mg5',
550
+ sessionId: 's-cached',
551
+ sessionFilePath
552
+ });
553
+ mockedListAgentProcesses.mockReturnValue([
554
+ wrapperProc,
555
+ childProc
556
+ ]);
557
+ mockedMatchProcessesToSessions.mockReturnValue([
558
+ {
559
+ process: childProc,
560
+ session: {
561
+ sessionId: 's-cached',
562
+ filePath: sessionFilePath,
563
+ projectDir: path.dirname(sessionFilePath),
564
+ birthtimeMs: Date.now(),
565
+ resolvedCwd: '/repo-a'
566
+ },
567
+ deltaMs: 0
568
+ }
569
+ ]);
570
+ const agents = await cachedAdapter.detectAgents();
571
+ expect(agents).toHaveLength(1);
572
+ expect(agents[0]).toMatchObject({
573
+ name: 'cli-mqcq0mg5',
574
+ pid: childProc.pid,
575
+ sessionId: 's-cached'
576
+ });
577
+ });
436
578
  });
437
579
  describe('discoverSessions', ()=>{
438
580
  it('should return empty when ~/.gemini/tmp does not exist', ()=>{