@ai-devkit/agent-manager 0.15.0 → 0.17.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.
- package/.swcrc +27 -0
- package/dist/AgentManager.d.ts +4 -0
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +97 -64
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +564 -0
- package/dist/__tests__/AgentManager.test.js.map +1 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +2157 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js +1354 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/CopilotAdapter.test.js +500 -0
- package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js +1229 -0
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js +626 -0
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/terminal/TmuxManager.test.js +159 -0
- package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -0
- package/dist/__tests__/terminal/TtyWriter.test.js +162 -0
- package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -0
- package/dist/__tests__/utils/AgentRegistry.test.js +282 -0
- package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -0
- package/dist/__tests__/utils/ClaudeSessionParser.test.js +312 -0
- package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -0
- package/dist/__tests__/utils/matching.test.js +324 -0
- package/dist/__tests__/utils/matching.test.js.map +1 -0
- package/dist/__tests__/utils/process.test.js +177 -0
- package/dist/__tests__/utils/process.test.js.map +1 -0
- package/dist/__tests__/utils/session.test.js +158 -0
- package/dist/__tests__/utils/session.test.js.map +1 -0
- package/dist/adapters/AgentAdapter.d.ts +1 -1
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js +8 -7
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.js +66 -77
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +13 -1
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +208 -132
- package/dist/adapters/CodexAdapter.js.map +1 -1
- package/dist/adapters/CopilotAdapter.d.ts +44 -0
- package/dist/adapters/CopilotAdapter.d.ts.map +1 -0
- package/dist/adapters/CopilotAdapter.js +314 -0
- package/dist/adapters/CopilotAdapter.js.map +1 -0
- package/dist/adapters/GeminiCliAdapter.d.ts +6 -1
- package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/adapters/GeminiCliAdapter.js +160 -149
- package/dist/adapters/GeminiCliAdapter.js.map +1 -1
- package/dist/adapters/OpenCodeAdapter.js +60 -69
- package/dist/adapters/OpenCodeAdapter.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/terminal/TerminalFocusManager.js +62 -49
- package/dist/terminal/TerminalFocusManager.js.map +1 -1
- package/dist/terminal/TmuxManager.d.ts +28 -0
- package/dist/terminal/TmuxManager.d.ts.map +1 -0
- package/dist/terminal/TmuxManager.js +137 -0
- package/dist/terminal/TmuxManager.js.map +1 -0
- package/dist/terminal/TtyWriter.js +37 -16
- package/dist/terminal/TtyWriter.js.map +1 -1
- package/dist/terminal/index.js +1 -0
- package/dist/terminal/index.js.map +1 -1
- package/dist/utils/AgentRegistry.d.ts +35 -0
- package/dist/utils/AgentRegistry.d.ts.map +1 -0
- package/dist/utils/AgentRegistry.js +123 -0
- package/dist/utils/AgentRegistry.js.map +1 -0
- package/dist/utils/ClaudeSessionParser.d.ts.map +1 -1
- package/dist/utils/ClaudeSessionParser.js +62 -93
- package/dist/utils/ClaudeSessionParser.js.map +1 -1
- package/dist/utils/agents.d.ts +15 -0
- package/dist/utils/agents.d.ts.map +1 -0
- package/dist/utils/agents.js +41 -0
- package/dist/utils/agents.js.map +1 -0
- package/dist/utils/applescript.js +3 -6
- package/dist/utils/applescript.js.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/matching.js +21 -29
- package/dist/utils/matching.js.map +1 -1
- package/dist/utils/process.js +77 -56
- package/dist/utils/process.js.map +1 -1
- package/dist/utils/session.js +20 -34
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/sortAgents.js +6 -8
- package/dist/utils/sortAgents.js.map +1 -1
- package/package.json +11 -7
- package/src/AgentManager.ts +40 -0
- package/src/__tests__/AgentManager.test.ts +135 -0
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +3 -2
- package/src/__tests__/adapters/CodexAdapter.test.ts +199 -0
- package/src/__tests__/adapters/CopilotAdapter.test.ts +411 -0
- package/src/__tests__/adapters/GeminiCliAdapter.test.ts +185 -1
- package/src/__tests__/terminal/TmuxManager.test.ts +175 -0
- package/src/__tests__/utils/AgentRegistry.test.ts +223 -0
- package/src/__tests__/utils/ClaudeSessionParser.test.ts +71 -0
- package/src/adapters/AgentAdapter.ts +1 -1
- package/src/adapters/CodexAdapter.ts +155 -8
- package/src/adapters/CopilotAdapter.ts +453 -0
- package/src/adapters/GeminiCliAdapter.ts +84 -6
- package/src/adapters/index.ts +1 -0
- package/src/index.ts +7 -0
- package/src/terminal/TmuxManager.ts +118 -0
- package/src/utils/AgentRegistry.ts +139 -0
- package/src/utils/ClaudeSessionParser.ts +21 -10
- package/src/utils/agents.ts +41 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for utils/session.ts
|
|
3
|
+
*/ import * as fs from 'fs';
|
|
4
|
+
import { batchGetSessionFileBirthtimes } from '../../utils/session.js';
|
|
5
|
+
vi.mock('fs', ()=>({
|
|
6
|
+
readdirSync: vi.fn(),
|
|
7
|
+
statSync: vi.fn()
|
|
8
|
+
}));
|
|
9
|
+
const mockedReaddirSync = fs.readdirSync;
|
|
10
|
+
const mockedStatSync = fs.statSync;
|
|
11
|
+
describe('batchGetSessionFileBirthtimes', ()=>{
|
|
12
|
+
beforeEach(()=>{
|
|
13
|
+
mockedReaddirSync.mockReset();
|
|
14
|
+
mockedStatSync.mockReset();
|
|
15
|
+
});
|
|
16
|
+
it('should parse session files correctly', ()=>{
|
|
17
|
+
mockedReaddirSync.mockReturnValue([
|
|
18
|
+
'abc123.jsonl',
|
|
19
|
+
'def456.jsonl'
|
|
20
|
+
]);
|
|
21
|
+
mockedStatSync.mockReturnValueOnce({
|
|
22
|
+
birthtimeMs: 1710800324000
|
|
23
|
+
}).mockReturnValueOnce({
|
|
24
|
+
birthtimeMs: 1710800500000
|
|
25
|
+
});
|
|
26
|
+
const results = batchGetSessionFileBirthtimes([
|
|
27
|
+
'/home/.claude/projects/my-app'
|
|
28
|
+
]);
|
|
29
|
+
expect(results).toHaveLength(2);
|
|
30
|
+
expect(results[0]).toEqual({
|
|
31
|
+
sessionId: 'abc123',
|
|
32
|
+
filePath: '/home/.claude/projects/my-app/abc123.jsonl',
|
|
33
|
+
projectDir: '/home/.claude/projects/my-app',
|
|
34
|
+
birthtimeMs: 1710800324000,
|
|
35
|
+
resolvedCwd: ''
|
|
36
|
+
});
|
|
37
|
+
expect(results[1].sessionId).toBe('def456');
|
|
38
|
+
expect(results[1].birthtimeMs).toBe(1710800500000);
|
|
39
|
+
});
|
|
40
|
+
it('should return empty array for empty dirs list', ()=>{
|
|
41
|
+
expect(batchGetSessionFileBirthtimes([])).toEqual([]);
|
|
42
|
+
expect(mockedReaddirSync).not.toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
it('should return empty array on readdir failure', ()=>{
|
|
45
|
+
mockedReaddirSync.mockImplementation(()=>{
|
|
46
|
+
throw new Error('ENOENT');
|
|
47
|
+
});
|
|
48
|
+
expect(batchGetSessionFileBirthtimes([
|
|
49
|
+
'/some/dir'
|
|
50
|
+
])).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
it('should skip files with invalid birthtime (0 or negative)', ()=>{
|
|
53
|
+
mockedReaddirSync.mockReturnValue([
|
|
54
|
+
'bad.jsonl',
|
|
55
|
+
'negative.jsonl',
|
|
56
|
+
'good.jsonl'
|
|
57
|
+
]);
|
|
58
|
+
mockedStatSync.mockReturnValueOnce({
|
|
59
|
+
birthtimeMs: 0
|
|
60
|
+
}).mockReturnValueOnce({
|
|
61
|
+
birthtimeMs: -1
|
|
62
|
+
}).mockReturnValueOnce({
|
|
63
|
+
birthtimeMs: 1710800324000
|
|
64
|
+
});
|
|
65
|
+
const results = batchGetSessionFileBirthtimes([
|
|
66
|
+
'/dir'
|
|
67
|
+
]);
|
|
68
|
+
expect(results).toHaveLength(1);
|
|
69
|
+
expect(results[0].sessionId).toBe('good');
|
|
70
|
+
});
|
|
71
|
+
it('should skip non-jsonl files', ()=>{
|
|
72
|
+
mockedReaddirSync.mockReturnValue([
|
|
73
|
+
'sessions-index.json',
|
|
74
|
+
'abc123.jsonl'
|
|
75
|
+
]);
|
|
76
|
+
mockedStatSync.mockReturnValueOnce({
|
|
77
|
+
birthtimeMs: 1710800500000
|
|
78
|
+
});
|
|
79
|
+
const results = batchGetSessionFileBirthtimes([
|
|
80
|
+
'/dir'
|
|
81
|
+
]);
|
|
82
|
+
expect(results).toHaveLength(1);
|
|
83
|
+
expect(results[0].sessionId).toBe('abc123');
|
|
84
|
+
});
|
|
85
|
+
it('should handle empty directory', ()=>{
|
|
86
|
+
mockedReaddirSync.mockReturnValue([]);
|
|
87
|
+
expect(batchGetSessionFileBirthtimes([
|
|
88
|
+
'/dir'
|
|
89
|
+
])).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
it('should handle UUID session IDs', ()=>{
|
|
92
|
+
mockedReaddirSync.mockReturnValue([
|
|
93
|
+
'068e7b1f-cff5-4c94-bf69-b9acd32d765c.jsonl'
|
|
94
|
+
]);
|
|
95
|
+
mockedStatSync.mockReturnValueOnce({
|
|
96
|
+
birthtimeMs: 1710800324000
|
|
97
|
+
});
|
|
98
|
+
const results = batchGetSessionFileBirthtimes([
|
|
99
|
+
'/dir'
|
|
100
|
+
]);
|
|
101
|
+
expect(results).toHaveLength(1);
|
|
102
|
+
expect(results[0].sessionId).toBe('068e7b1f-cff5-4c94-bf69-b9acd32d765c');
|
|
103
|
+
});
|
|
104
|
+
it('should leave resolvedCwd empty', ()=>{
|
|
105
|
+
mockedReaddirSync.mockReturnValue([
|
|
106
|
+
'abc.jsonl'
|
|
107
|
+
]);
|
|
108
|
+
mockedStatSync.mockReturnValueOnce({
|
|
109
|
+
birthtimeMs: 1710800324000
|
|
110
|
+
});
|
|
111
|
+
const results = batchGetSessionFileBirthtimes([
|
|
112
|
+
'/dir'
|
|
113
|
+
]);
|
|
114
|
+
expect(results[0].resolvedCwd).toBe('');
|
|
115
|
+
});
|
|
116
|
+
it('should enumerate multiple directories', ()=>{
|
|
117
|
+
mockedReaddirSync.mockReturnValueOnce([
|
|
118
|
+
'sess1.jsonl',
|
|
119
|
+
'sess3.jsonl'
|
|
120
|
+
]).mockReturnValueOnce([
|
|
121
|
+
'sess2.jsonl'
|
|
122
|
+
]);
|
|
123
|
+
mockedStatSync.mockReturnValueOnce({
|
|
124
|
+
birthtimeMs: 1710800324000
|
|
125
|
+
}).mockReturnValueOnce({
|
|
126
|
+
birthtimeMs: 1710800500000
|
|
127
|
+
}).mockReturnValueOnce({
|
|
128
|
+
birthtimeMs: 1710800400000
|
|
129
|
+
});
|
|
130
|
+
const results = batchGetSessionFileBirthtimes([
|
|
131
|
+
'/projects/app-a',
|
|
132
|
+
'/projects/app-b'
|
|
133
|
+
]);
|
|
134
|
+
expect(mockedReaddirSync).toHaveBeenCalledTimes(2);
|
|
135
|
+
expect(results).toHaveLength(3);
|
|
136
|
+
expect(results[0].projectDir).toBe('/projects/app-a');
|
|
137
|
+
expect(results[1].projectDir).toBe('/projects/app-a');
|
|
138
|
+
expect(results[2].projectDir).toBe('/projects/app-b');
|
|
139
|
+
});
|
|
140
|
+
it('should skip files where statSync fails', ()=>{
|
|
141
|
+
mockedReaddirSync.mockReturnValue([
|
|
142
|
+
'good.jsonl',
|
|
143
|
+
'gone.jsonl'
|
|
144
|
+
]);
|
|
145
|
+
mockedStatSync.mockReturnValueOnce({
|
|
146
|
+
birthtimeMs: 1710800324000
|
|
147
|
+
}).mockImplementationOnce(()=>{
|
|
148
|
+
throw new Error('ENOENT');
|
|
149
|
+
});
|
|
150
|
+
const results = batchGetSessionFileBirthtimes([
|
|
151
|
+
'/dir'
|
|
152
|
+
]);
|
|
153
|
+
expect(results).toHaveLength(1);
|
|
154
|
+
expect(results[0].sessionId).toBe('good');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
//# sourceMappingURL=session.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/utils/session.test.ts"],"sourcesContent":["/**\n * Tests for utils/session.ts\n */\n\nimport type { MockedFunction } from 'vitest';\n\nimport * as fs from 'fs';\nimport { batchGetSessionFileBirthtimes } from '../../utils/session.js';\n\nvi.mock('fs', () => ({\n readdirSync: vi.fn(),\n statSync: vi.fn(),\n}));\n\nconst mockedReaddirSync = fs.readdirSync as MockedFunction<typeof fs.readdirSync>;\nconst mockedStatSync = fs.statSync as MockedFunction<typeof fs.statSync>;\n\ndescribe('batchGetSessionFileBirthtimes', () => {\n beforeEach(() => {\n mockedReaddirSync.mockReset();\n mockedStatSync.mockReset();\n });\n\n it('should parse session files correctly', () => {\n mockedReaddirSync.mockReturnValue([\n 'abc123.jsonl',\n 'def456.jsonl',\n ] as unknown as ReturnType<typeof fs.readdirSync>);\n mockedStatSync\n .mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats)\n .mockReturnValueOnce({ birthtimeMs: 1710800500000 } as fs.Stats);\n\n const results = batchGetSessionFileBirthtimes(['/home/.claude/projects/my-app']);\n\n expect(results).toHaveLength(2);\n expect(results[0]).toEqual({\n sessionId: 'abc123',\n filePath: '/home/.claude/projects/my-app/abc123.jsonl',\n projectDir: '/home/.claude/projects/my-app',\n birthtimeMs: 1710800324000,\n resolvedCwd: '',\n });\n expect(results[1].sessionId).toBe('def456');\n expect(results[1].birthtimeMs).toBe(1710800500000);\n });\n\n it('should return empty array for empty dirs list', () => {\n expect(batchGetSessionFileBirthtimes([])).toEqual([]);\n expect(mockedReaddirSync).not.toHaveBeenCalled();\n });\n\n it('should return empty array on readdir failure', () => {\n mockedReaddirSync.mockImplementation(() => {\n throw new Error('ENOENT');\n });\n\n expect(batchGetSessionFileBirthtimes(['/some/dir'])).toEqual([]);\n });\n\n it('should skip files with invalid birthtime (0 or negative)', () => {\n mockedReaddirSync.mockReturnValue([\n 'bad.jsonl',\n 'negative.jsonl',\n 'good.jsonl',\n ] as unknown as ReturnType<typeof fs.readdirSync>);\n mockedStatSync\n .mockReturnValueOnce({ birthtimeMs: 0 } as fs.Stats)\n .mockReturnValueOnce({ birthtimeMs: -1 } as fs.Stats)\n .mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats);\n\n const results = batchGetSessionFileBirthtimes(['/dir']);\n expect(results).toHaveLength(1);\n expect(results[0].sessionId).toBe('good');\n });\n\n it('should skip non-jsonl files', () => {\n mockedReaddirSync.mockReturnValue([\n 'sessions-index.json',\n 'abc123.jsonl',\n ] as unknown as ReturnType<typeof fs.readdirSync>);\n mockedStatSync\n .mockReturnValueOnce({ birthtimeMs: 1710800500000 } as fs.Stats);\n\n const results = batchGetSessionFileBirthtimes(['/dir']);\n expect(results).toHaveLength(1);\n expect(results[0].sessionId).toBe('abc123');\n });\n\n it('should handle empty directory', () => {\n mockedReaddirSync.mockReturnValue([] as unknown as ReturnType<typeof fs.readdirSync>);\n expect(batchGetSessionFileBirthtimes(['/dir'])).toEqual([]);\n });\n\n it('should handle UUID session IDs', () => {\n mockedReaddirSync.mockReturnValue([\n '068e7b1f-cff5-4c94-bf69-b9acd32d765c.jsonl',\n ] as unknown as ReturnType<typeof fs.readdirSync>);\n mockedStatSync\n .mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats);\n\n const results = batchGetSessionFileBirthtimes(['/dir']);\n expect(results).toHaveLength(1);\n expect(results[0].sessionId).toBe('068e7b1f-cff5-4c94-bf69-b9acd32d765c');\n });\n\n it('should leave resolvedCwd empty', () => {\n mockedReaddirSync.mockReturnValue([\n 'abc.jsonl',\n ] as unknown as ReturnType<typeof fs.readdirSync>);\n mockedStatSync\n .mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats);\n\n const results = batchGetSessionFileBirthtimes(['/dir']);\n expect(results[0].resolvedCwd).toBe('');\n });\n\n it('should enumerate multiple directories', () => {\n mockedReaddirSync\n .mockReturnValueOnce([\n 'sess1.jsonl',\n 'sess3.jsonl',\n ] as unknown as ReturnType<typeof fs.readdirSync>)\n .mockReturnValueOnce([\n 'sess2.jsonl',\n ] as unknown as ReturnType<typeof fs.readdirSync>);\n mockedStatSync\n .mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats)\n .mockReturnValueOnce({ birthtimeMs: 1710800500000 } as fs.Stats)\n .mockReturnValueOnce({ birthtimeMs: 1710800400000 } as fs.Stats);\n\n const results = batchGetSessionFileBirthtimes(['/projects/app-a', '/projects/app-b']);\n\n expect(mockedReaddirSync).toHaveBeenCalledTimes(2);\n\n expect(results).toHaveLength(3);\n expect(results[0].projectDir).toBe('/projects/app-a');\n expect(results[1].projectDir).toBe('/projects/app-a');\n expect(results[2].projectDir).toBe('/projects/app-b');\n });\n\n it('should skip files where statSync fails', () => {\n mockedReaddirSync.mockReturnValue([\n 'good.jsonl',\n 'gone.jsonl',\n ] as unknown as ReturnType<typeof fs.readdirSync>);\n mockedStatSync\n .mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats)\n .mockImplementationOnce(() => { throw new Error('ENOENT'); });\n\n const results = batchGetSessionFileBirthtimes(['/dir']);\n expect(results).toHaveLength(1);\n expect(results[0].sessionId).toBe('good');\n });\n});\n"],"names":["fs","batchGetSessionFileBirthtimes","vi","mock","readdirSync","fn","statSync","mockedReaddirSync","mockedStatSync","describe","beforeEach","mockReset","it","mockReturnValue","mockReturnValueOnce","birthtimeMs","results","expect","toHaveLength","toEqual","sessionId","filePath","projectDir","resolvedCwd","toBe","not","toHaveBeenCalled","mockImplementation","Error","toHaveBeenCalledTimes","mockImplementationOnce"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/AgentAdapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"AgentAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/AgentAdapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,CAAC;AAE7F;;GAEG;AACH,oBAAY,WAAW;IACnB,OAAO,YAAY;IACnB,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,OAAO,YAAY;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACtB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IAEb,oBAAoB;IACpB,IAAI,EAAE,SAAS,CAAC;IAEhB,qBAAqB;IACrB,MAAM,EAAE,WAAW,CAAC;IAEpB,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAEhB,iBAAiB;IACjB,GAAG,EAAE,MAAM,CAAC;IAEZ,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAC;IAEpB,mBAAmB;IACnB,SAAS,EAAE,MAAM,CAAC;IAElB,iCAAiC;IACjC,UAAU,EAAE,IAAI,CAAC;IAEjB,6CAA6C;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,iBAAiB;IACjB,GAAG,EAAE,MAAM,CAAC;IAEZ,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAEhB,wBAAwB;IACxB,GAAG,EAAE,MAAM,CAAC;IAEZ,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IAEZ,uDAAuD;IACvD,SAAS,CAAC,EAAE,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC3B,sCAAsC;IACtC,IAAI,EAAE,SAAS,CAAC;IAEhB;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,CAAC;IAEzB,+EAA+E;IAC/E,UAAU,EAAE,IAAI,CAAC;IAEjB,oFAAoF;IACpF,SAAS,EAAE,IAAI,CAAC;IAEhB,oEAAoE;IACpE,eAAe,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAChC;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IACzB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAEzB;;;OAGG;IACH,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAErC;;;;OAIG;IACH,SAAS,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC;IAE7C;;;;;OAKG;IACH,eAAe,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,mBAAmB,EAAE,CAAC;IAEjG;;;;;;;;;OASG;IACH,YAAY,CAAC,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;CACvE"}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent Adapter Interface
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Defines the contract for detecting and managing different types of AI agents.
|
|
5
5
|
* Each adapter is responsible for detecting agents of a specific type (e.g., claude).
|
|
6
|
-
*/
|
|
7
|
-
|
|
6
|
+
*/ /**
|
|
7
|
+
* Type of AI agent
|
|
8
|
+
*/ /**
|
|
8
9
|
* Current status of an agent
|
|
9
|
-
*/
|
|
10
|
-
export var AgentStatus;
|
|
11
|
-
(function (AgentStatus) {
|
|
10
|
+
*/ export var AgentStatus = /*#__PURE__*/ function(AgentStatus) {
|
|
12
11
|
AgentStatus["RUNNING"] = "running";
|
|
13
12
|
AgentStatus["WAITING"] = "waiting";
|
|
14
13
|
AgentStatus["IDLE"] = "idle";
|
|
15
14
|
AgentStatus["UNKNOWN"] = "unknown";
|
|
16
|
-
|
|
15
|
+
return AgentStatus;
|
|
16
|
+
}({});
|
|
17
|
+
|
|
17
18
|
//# sourceMappingURL=AgentAdapter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/AgentAdapter.ts"],"sourcesContent":["/**\n * Agent Adapter Interface\n * \n * Defines the contract for detecting and managing different types of AI agents.\n * Each adapter is responsible for detecting agents of a specific type (e.g., claude).\n */\n\n/**\n * Type of AI agent\n */\nexport type AgentType = 'claude' | 'gemini_cli' | 'codex' | 'opencode' | 'copilot' | 'other';\n\n/**\n * Current status of an agent\n */\nexport enum AgentStatus {\n RUNNING = 'running',\n WAITING = 'waiting',\n IDLE = 'idle',\n UNKNOWN = 'unknown'\n}\n\n/**\n * Information about a detected agent\n */\nexport interface AgentInfo {\n /** Project-based name (e.g., \"ai-devkit\" or \"ai-devkit (merry)\") */\n name: string;\n\n /** Type of agent */\n type: AgentType;\n\n /** Current status */\n status: AgentStatus;\n\n /** Last user prompt from history */\n summary: string;\n\n /** Process ID */\n pid: number;\n\n /** Working directory/project path */\n projectPath: string;\n\n /** Session UUID */\n sessionId: string;\n\n /** Timestamp of last activity */\n lastActive: Date;\n\n /** Path to the session JSONL file on disk */\n sessionFilePath?: string;\n}\n\n/**\n * Information about a running process\n */\nexport interface ProcessInfo {\n /** Process ID */\n pid: number;\n\n /** Process command */\n command: string;\n\n /** Working directory */\n cwd: string;\n\n /** Terminal TTY (e.g., \"ttys030\") */\n tty: string;\n\n /** Process start time, populated by enrichProcesses */\n startTime?: Date;\n}\n\n/**\n * A single message in a conversation\n */\nexport interface ConversationMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n timestamp?: string;\n}\n\n/**\n * A historical session discovered on disk (running or not).\n *\n * Used by `listSessions` to surface enough context for a user to identify\n * a session and resume it via the originating tool's resume command.\n */\nexport interface SessionSummary {\n /** Tool that produced this session */\n type: AgentType;\n\n /**\n * ID accepted by the tool's resume command. Adapters MUST pass this\n * through verbatim — no normalization, no encoding/decoding — so it\n * round-trips into `claude --resume <id>` (and equivalents).\n */\n sessionId: string;\n\n /** Working directory the session was started in (best-known value) */\n cwd: string;\n\n /**\n * Trimmed first user message; empty string if none. Adapters apply\n * the same noise-filter their existing parsers use (skip tool_result\n * blocks, request-interruption notices, system-injected skill\n * content). The CLI table renderer substitutes a placeholder for\n * empty values; JSON output keeps the empty string raw.\n */\n firstUserMessage: string;\n\n /** Last activity timestamp (from session content; falls back to file mtime) */\n lastActive: Date;\n\n /** Session start time (from session content; falls back to file birthtime/mtime) */\n startedAt: Date;\n\n /** Absolute path to the session file on disk (debug/diagnostics) */\n sessionFilePath: string;\n}\n\n/**\n * Filters passed by the CLI to {@link AgentAdapter.listSessions}.\n *\n * The CLI is the source of truth for filter defaults and semantics\n * (e.g. cwd defaults to process.cwd(); --all clears it). Adapters apply\n * the values they receive — they don't invent defaults.\n */\nexport interface ListSessionsOptions {\n /**\n * Filter to sessions whose recorded cwd matches this path using strict\n * equality (no prefix/ancestor matching in v1). Undefined = no cwd\n * filter.\n */\n cwd?: string;\n\n /**\n * Filter to a single tool. Enforced by `AgentManager.listSessions`,\n * which skips adapters whose `type` doesn't match. Adapters MAY\n * ignore this field — by the time their `listSessions` runs, the\n * type filter is already satisfied. Undefined = include every\n * registered adapter.\n */\n type?: AgentType;\n}\n\n/**\n * Agent Adapter Interface\n *\n * Implementations must provide detection logic for a specific agent type.\n */\nexport interface AgentAdapter {\n /** Type of agent this adapter handles */\n readonly type: AgentType;\n\n /**\n * Detect running agents of this type\n * @returns List of detected agents\n */\n detectAgents(): Promise<AgentInfo[]>;\n\n /**\n * Check if this adapter can handle the given process\n * @param processInfo Process information\n * @returns True if this adapter can handle the process\n */\n canHandle(processInfo: ProcessInfo): boolean;\n\n /**\n * Read the full conversation from a session file\n * @param sessionFilePath Path to the session JSONL file\n * @param options.verbose Include tool call/result details\n * @returns Array of conversation messages\n */\n getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[];\n\n /**\n * Enumerate historical sessions for this tool from disk.\n *\n * Applies `opts.cwd` as a strict-equality filter when set. Returns\n * {@link SessionSummary} entries unsorted; sorting and global filters\n * are handled by `AgentManager` and the CLI.\n *\n * @param opts Filter options computed by the CLI\n * @returns Array of sessions discovered on disk\n */\n listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]>;\n}\n"],"names":["AgentStatus"],"mappings":"AAAA;;;;;CAKC,GAED;;CAEC,GAGD;;CAEC,GACD,OAAO,IAAA,AAAKA,qCAAAA;;;;;WAAAA;MAKX"}
|
|
@@ -5,8 +5,7 @@ import { listAgentProcesses, enrichProcesses } from '../utils/process.js';
|
|
|
5
5
|
import { batchGetSessionFileBirthtimes, isDirectory, listJsonl, safeReaddir, safeStat } from '../utils/session.js';
|
|
6
6
|
import { matchProcessesToSessions, generateAgentName } from '../utils/matching.js';
|
|
7
7
|
import { ClaudeSessionParser } from '../utils/ClaudeSessionParser.js';
|
|
8
|
-
/** Maximum allowed delta (ms) between process start time and PID file startedAt. */
|
|
9
|
-
const PID_FILE_STALENESS_MS = 60000;
|
|
8
|
+
/** Maximum allowed delta (ms) between process start time and PID file startedAt. */ const PID_FILE_STALENESS_MS = 60000;
|
|
10
9
|
/**
|
|
11
10
|
* Claude Code Adapter
|
|
12
11
|
*
|
|
@@ -16,13 +15,12 @@ const PID_FILE_STALENESS_MS = 60000;
|
|
|
16
15
|
* 3. Attempting authoritative PID-file matching via ~/.claude/sessions/<pid>.json
|
|
17
16
|
* 4. Falling back to CWD+birthtime heuristic (matchProcessesToSessions) for processes without a PID file
|
|
18
17
|
* 5. Extracting summary from last user message in session JSONL
|
|
19
|
-
*/
|
|
20
|
-
export class ClaudeCodeAdapter {
|
|
18
|
+
*/ export class ClaudeCodeAdapter {
|
|
21
19
|
type = 'claude';
|
|
22
20
|
projectsDir;
|
|
23
21
|
sessionsDir;
|
|
24
22
|
parser;
|
|
25
|
-
constructor()
|
|
23
|
+
constructor(){
|
|
26
24
|
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
27
25
|
this.projectsDir = path.join(homeDir, '.claude', 'projects');
|
|
28
26
|
this.sessionsDir = path.join(homeDir, '.claude', 'sessions');
|
|
@@ -47,43 +45,42 @@ export class ClaudeCodeAdapter {
|
|
|
47
45
|
const { direct: resumeDirect, fallback: noResume } = this.tryResumeMatching(processes);
|
|
48
46
|
// Step 2: try authoritative PID-file matching for the rest
|
|
49
47
|
const { direct: pidDirect, fallback } = this.tryPidFileMatching(noResume);
|
|
50
|
-
const direct = [
|
|
48
|
+
const direct = [
|
|
49
|
+
...resumeDirect,
|
|
50
|
+
...pidDirect
|
|
51
|
+
];
|
|
51
52
|
// Step 3: run legacy CWD+birthtime matching only for processes without a PID file
|
|
52
53
|
const legacySessions = this.discoverSessions(fallback);
|
|
53
|
-
const legacyMatches = fallback.length > 0 && legacySessions.length > 0
|
|
54
|
-
? matchProcessesToSessions(fallback, legacySessions)
|
|
55
|
-
: [];
|
|
54
|
+
const legacyMatches = fallback.length > 0 && legacySessions.length > 0 ? matchProcessesToSessions(fallback, legacySessions) : [];
|
|
56
55
|
const matchedPids = new Set([
|
|
57
|
-
...direct.map((d)
|
|
58
|
-
...legacyMatches.map((m)
|
|
56
|
+
...direct.map((d)=>d.process.pid),
|
|
57
|
+
...legacyMatches.map((m)=>m.process.pid)
|
|
59
58
|
]);
|
|
60
59
|
const agents = [];
|
|
61
60
|
// Build agents from direct (resume + PID-file) matches
|
|
62
|
-
for (const match of direct)
|
|
61
|
+
for (const match of direct){
|
|
63
62
|
const { process: proc, sessionFile } = match;
|
|
64
63
|
const sessionData = this.parser.readSession(sessionFile.filePath, sessionFile.resolvedCwd);
|
|
65
64
|
if (sessionData) {
|
|
66
65
|
agents.push(this.mapSessionToAgent(sessionData, proc, sessionFile, {
|
|
67
66
|
pidStatus: match.pidStatus,
|
|
68
|
-
waitingFor: match.waitingFor
|
|
67
|
+
waitingFor: match.waitingFor
|
|
69
68
|
}));
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
69
|
+
} else {
|
|
72
70
|
matchedPids.delete(proc.pid);
|
|
73
71
|
}
|
|
74
72
|
}
|
|
75
73
|
// Build agents from legacy matches
|
|
76
|
-
for (const match of legacyMatches)
|
|
74
|
+
for (const match of legacyMatches){
|
|
77
75
|
const sessionData = this.parser.readSession(match.session.filePath, match.session.resolvedCwd);
|
|
78
76
|
if (sessionData) {
|
|
79
77
|
agents.push(this.mapSessionToAgent(sessionData, match.process, match.session));
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
78
|
+
} else {
|
|
82
79
|
matchedPids.delete(match.process.pid);
|
|
83
80
|
}
|
|
84
81
|
}
|
|
85
82
|
// Any process with no match (direct or legacy) appears as IDLE
|
|
86
|
-
for (const proc of processes)
|
|
83
|
+
for (const proc of processes){
|
|
87
84
|
if (!matchedPids.has(proc.pid)) {
|
|
88
85
|
agents.push(this.mapProcessOnlyAgent(proc));
|
|
89
86
|
}
|
|
@@ -96,28 +93,24 @@ export class ClaudeCodeAdapter {
|
|
|
96
93
|
* For each unique process CWD, encodes it to derive the expected
|
|
97
94
|
* ~/.claude/projects/<encoded>/ directory, then gets session file birthtimes
|
|
98
95
|
* via a single batched stat call across all directories.
|
|
99
|
-
*/
|
|
100
|
-
discoverSessions(processes) {
|
|
96
|
+
*/ discoverSessions(processes) {
|
|
101
97
|
const dirToCwd = new Map();
|
|
102
|
-
for (const proc of processes)
|
|
103
|
-
if (!proc.cwd)
|
|
104
|
-
continue;
|
|
98
|
+
for (const proc of processes){
|
|
99
|
+
if (!proc.cwd) continue;
|
|
105
100
|
const projectDir = this.getProjectDir(proc.cwd);
|
|
106
|
-
if (dirToCwd.has(projectDir))
|
|
107
|
-
continue;
|
|
101
|
+
if (dirToCwd.has(projectDir)) continue;
|
|
108
102
|
try {
|
|
109
|
-
if (!fs.statSync(projectDir).isDirectory())
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
103
|
+
if (!fs.statSync(projectDir).isDirectory()) continue;
|
|
104
|
+
} catch {
|
|
113
105
|
continue;
|
|
114
106
|
}
|
|
115
107
|
dirToCwd.set(projectDir, proc.cwd);
|
|
116
108
|
}
|
|
117
|
-
if (dirToCwd.size === 0)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
if (dirToCwd.size === 0) return [];
|
|
110
|
+
const files = batchGetSessionFileBirthtimes([
|
|
111
|
+
...dirToCwd.keys()
|
|
112
|
+
]);
|
|
113
|
+
for (const file of files){
|
|
121
114
|
file.resolvedCwd = dirToCwd.get(file.projectDir) || '';
|
|
122
115
|
}
|
|
123
116
|
return files;
|
|
@@ -127,11 +120,10 @@ export class ClaudeCodeAdapter {
|
|
|
127
120
|
* This works for resumed sessions, where the JSONL was created earlier
|
|
128
121
|
* (so its birthtime is far from the process startTime and the legacy
|
|
129
122
|
* matcher can't pair them) and the PID file may also be misaligned.
|
|
130
|
-
*/
|
|
131
|
-
tryResumeMatching(processes) {
|
|
123
|
+
*/ tryResumeMatching(processes) {
|
|
132
124
|
const direct = [];
|
|
133
125
|
const fallback = [];
|
|
134
|
-
for (const proc of processes)
|
|
126
|
+
for (const proc of processes){
|
|
135
127
|
const sessionId = this.extractResumeSessionId(proc.command);
|
|
136
128
|
if (!sessionId || !proc.cwd) {
|
|
137
129
|
fallback.push(proc);
|
|
@@ -156,13 +148,16 @@ export class ClaudeCodeAdapter {
|
|
|
156
148
|
filePath: jsonlPath,
|
|
157
149
|
projectDir,
|
|
158
150
|
birthtimeMs: stat.birthtimeMs,
|
|
159
|
-
resolvedCwd: proc.cwd
|
|
151
|
+
resolvedCwd: proc.cwd
|
|
160
152
|
},
|
|
161
153
|
pidStatus: this.mapPidStatus(pidEntry?.status),
|
|
162
|
-
waitingFor: pidEntry?.waitingFor
|
|
154
|
+
waitingFor: pidEntry?.waitingFor
|
|
163
155
|
});
|
|
164
156
|
}
|
|
165
|
-
return {
|
|
157
|
+
return {
|
|
158
|
+
direct,
|
|
159
|
+
fallback
|
|
160
|
+
};
|
|
166
161
|
}
|
|
167
162
|
extractResumeSessionId(command) {
|
|
168
163
|
const match = command.match(/--resume\s+([0-9a-f-]{36})/i);
|
|
@@ -176,8 +171,7 @@ export class ClaudeCodeAdapter {
|
|
|
176
171
|
* "Stale" means the PID file's startedAt diverges from the process's
|
|
177
172
|
* start time by more than {@link PID_FILE_STALENESS_MS} — typically
|
|
178
173
|
* a previous Claude Code process recycled the same PID without cleanup.
|
|
179
|
-
*/
|
|
180
|
-
readMatchingPidFile(pid, procStartTime) {
|
|
174
|
+
*/ readMatchingPidFile(pid, procStartTime) {
|
|
181
175
|
const pidFilePath = path.join(this.sessionsDir, `${pid}.json`);
|
|
182
176
|
try {
|
|
183
177
|
const entry = JSON.parse(fs.readFileSync(pidFilePath, 'utf-8'));
|
|
@@ -188,8 +182,7 @@ export class ClaudeCodeAdapter {
|
|
|
188
182
|
}
|
|
189
183
|
}
|
|
190
184
|
return entry;
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
185
|
+
} catch {
|
|
193
186
|
return null;
|
|
194
187
|
}
|
|
195
188
|
}
|
|
@@ -198,9 +191,8 @@ export class ClaudeCodeAdapter {
|
|
|
198
191
|
*
|
|
199
192
|
* Returns undefined for missing / unrecognized values so the caller
|
|
200
193
|
* can fall back to JSONL-derived heuristics.
|
|
201
|
-
*/
|
|
202
|
-
|
|
203
|
-
switch (status) {
|
|
194
|
+
*/ mapPidStatus(status) {
|
|
195
|
+
switch(status){
|
|
204
196
|
case 'running':
|
|
205
197
|
return AgentStatus.RUNNING;
|
|
206
198
|
case 'waiting':
|
|
@@ -220,11 +212,10 @@ export class ClaudeCodeAdapter {
|
|
|
220
212
|
*
|
|
221
213
|
* Per-process fallback triggers on: file absent, malformed JSON,
|
|
222
214
|
* stale startedAt (>60s from proc.startTime), or missing JSONL.
|
|
223
|
-
*/
|
|
224
|
-
tryPidFileMatching(processes) {
|
|
215
|
+
*/ tryPidFileMatching(processes) {
|
|
225
216
|
const direct = [];
|
|
226
217
|
const fallback = [];
|
|
227
|
-
for (const proc of processes)
|
|
218
|
+
for (const proc of processes){
|
|
228
219
|
const entry = this.readMatchingPidFile(proc.pid, proc.startTime);
|
|
229
220
|
if (!entry) {
|
|
230
221
|
fallback.push(proc);
|
|
@@ -243,13 +234,16 @@ export class ClaudeCodeAdapter {
|
|
|
243
234
|
filePath: jsonlPath,
|
|
244
235
|
projectDir,
|
|
245
236
|
birthtimeMs: entry.startedAt,
|
|
246
|
-
resolvedCwd: entry.cwd
|
|
237
|
+
resolvedCwd: entry.cwd
|
|
247
238
|
},
|
|
248
239
|
pidStatus: this.mapPidStatus(entry.status),
|
|
249
|
-
waitingFor: entry.waitingFor
|
|
240
|
+
waitingFor: entry.waitingFor
|
|
250
241
|
});
|
|
251
242
|
}
|
|
252
|
-
return {
|
|
243
|
+
return {
|
|
244
|
+
direct,
|
|
245
|
+
fallback
|
|
246
|
+
};
|
|
253
247
|
}
|
|
254
248
|
/**
|
|
255
249
|
* Derive the Claude Code project directory for a given CWD.
|
|
@@ -263,8 +257,7 @@ export class ClaudeCodeAdapter {
|
|
|
263
257
|
* The encoding is lossy — multiple real paths can collide on the
|
|
264
258
|
* same encoded dir. Callers that need to disambiguate must read the
|
|
265
259
|
* `cwd` field inside each session JSONL.
|
|
266
|
-
*/
|
|
267
|
-
getProjectDir(cwd) {
|
|
260
|
+
*/ getProjectDir(cwd) {
|
|
268
261
|
const encoded = cwd.replace(/[^a-zA-Z0-9]/g, '-');
|
|
269
262
|
return path.join(this.projectsDir, encoded);
|
|
270
263
|
}
|
|
@@ -274,9 +267,7 @@ export class ClaudeCodeAdapter {
|
|
|
274
267
|
// event like `permission-mode` or `ai-title`.
|
|
275
268
|
const status = liveInfo?.pidStatus ?? this.parser.determineStatus(session);
|
|
276
269
|
const baseSummary = session.lastUserMessage || 'Session started';
|
|
277
|
-
const summary = status === AgentStatus.WAITING && liveInfo?.waitingFor
|
|
278
|
-
? `${baseSummary} — waiting for ${liveInfo.waitingFor}`
|
|
279
|
-
: baseSummary;
|
|
270
|
+
const summary = status === AgentStatus.WAITING && liveInfo?.waitingFor ? `${baseSummary} — waiting for ${liveInfo.waitingFor}` : baseSummary;
|
|
280
271
|
return {
|
|
281
272
|
name: generateAgentName(processInfo.cwd, processInfo.pid),
|
|
282
273
|
type: this.type,
|
|
@@ -286,7 +277,7 @@ export class ClaudeCodeAdapter {
|
|
|
286
277
|
projectPath: sessionFile.resolvedCwd || processInfo.cwd || '',
|
|
287
278
|
sessionId: sessionFile.sessionId,
|
|
288
279
|
lastActive: session.lastActive,
|
|
289
|
-
sessionFilePath: sessionFile.filePath
|
|
280
|
+
sessionFilePath: sessionFile.filePath
|
|
290
281
|
};
|
|
291
282
|
}
|
|
292
283
|
mapProcessOnlyAgent(processInfo) {
|
|
@@ -298,7 +289,7 @@ export class ClaudeCodeAdapter {
|
|
|
298
289
|
pid: processInfo.pid,
|
|
299
290
|
projectPath: processInfo.cwd || '',
|
|
300
291
|
sessionId: `pid-${processInfo.pid}`,
|
|
301
|
-
lastActive: new Date()
|
|
292
|
+
lastActive: new Date()
|
|
302
293
|
};
|
|
303
294
|
}
|
|
304
295
|
getConversation(sessionFilePath, options) {
|
|
@@ -308,19 +299,16 @@ export class ClaudeCodeAdapter {
|
|
|
308
299
|
const filterCwd = opts?.cwd;
|
|
309
300
|
const candidates = this.discoverSessionFiles();
|
|
310
301
|
const summaries = [];
|
|
311
|
-
for (const { filePath, defaultCwd } of candidates)
|
|
302
|
+
for (const { filePath, defaultCwd } of candidates){
|
|
312
303
|
const session = this.parser.readSession(filePath, defaultCwd);
|
|
313
|
-
if (!session)
|
|
314
|
-
continue;
|
|
304
|
+
if (!session) continue;
|
|
315
305
|
// Drop sessions whose JSONL had no parseable conversation entries.
|
|
316
306
|
// readSession is permissive (returns a shell record even when every
|
|
317
307
|
// line fails to parse); listSessions needs at least one real entry
|
|
318
308
|
// so we don't surface garbage files.
|
|
319
|
-
if (!session.lastEntryType)
|
|
320
|
-
continue;
|
|
309
|
+
if (!session.lastEntryType) continue;
|
|
321
310
|
const recordedCwd = session.lastCwd || defaultCwd;
|
|
322
|
-
if (filterCwd !== undefined && recordedCwd !== filterCwd)
|
|
323
|
-
continue;
|
|
311
|
+
if (filterCwd !== undefined && recordedCwd !== filterCwd) continue;
|
|
324
312
|
const stat = safeStat(filePath);
|
|
325
313
|
summaries.push({
|
|
326
314
|
type: 'claude',
|
|
@@ -329,7 +317,7 @@ export class ClaudeCodeAdapter {
|
|
|
329
317
|
firstUserMessage: session.firstUserMessage || '',
|
|
330
318
|
lastActive: session.lastActive ?? stat?.mtime ?? new Date(),
|
|
331
319
|
startedAt: session.sessionStart ?? stat?.birthtime ?? stat?.mtime ?? new Date(),
|
|
332
|
-
sessionFilePath: filePath
|
|
320
|
+
sessionFilePath: filePath
|
|
333
321
|
});
|
|
334
322
|
}
|
|
335
323
|
return summaries;
|
|
@@ -344,25 +332,26 @@ export class ClaudeCodeAdapter {
|
|
|
344
332
|
* worktrees and similar setups. The cwd filter is applied later
|
|
345
333
|
* against `session.lastCwd` so callers see exactly the sessions whose
|
|
346
334
|
* recorded cwd matches.
|
|
347
|
-
*/
|
|
348
|
-
discoverSessionFiles() {
|
|
335
|
+
*/ discoverSessionFiles() {
|
|
349
336
|
const out = [];
|
|
350
|
-
if (!isDirectory(this.projectsDir))
|
|
351
|
-
|
|
352
|
-
for (const dirName of safeReaddir(this.projectsDir)) {
|
|
337
|
+
if (!isDirectory(this.projectsDir)) return out;
|
|
338
|
+
for (const dirName of safeReaddir(this.projectsDir)){
|
|
353
339
|
const projectDir = path.join(this.projectsDir, dirName);
|
|
354
|
-
if (!isDirectory(projectDir))
|
|
355
|
-
continue;
|
|
340
|
+
if (!isDirectory(projectDir)) continue;
|
|
356
341
|
// Best-effort decode for the rare case session content has no
|
|
357
342
|
// recorded cwd: '-Users-foo-bar' → '/Users/foo/bar'. Lossy for
|
|
358
343
|
// paths containing '-'; session content's lastCwd overrides
|
|
359
344
|
// this when available.
|
|
360
345
|
const decoded = dirName.replace(/-/g, '/');
|
|
361
|
-
for (const name of listJsonl(projectDir))
|
|
362
|
-
out.push({
|
|
346
|
+
for (const name of listJsonl(projectDir)){
|
|
347
|
+
out.push({
|
|
348
|
+
filePath: path.join(projectDir, name),
|
|
349
|
+
defaultCwd: decoded
|
|
350
|
+
});
|
|
363
351
|
}
|
|
364
352
|
}
|
|
365
353
|
return out;
|
|
366
354
|
}
|
|
367
355
|
}
|
|
356
|
+
|
|
368
357
|
//# sourceMappingURL=ClaudeCodeAdapter.js.map
|