@ai-devkit/agent-manager 0.17.0 → 0.19.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/dist/__tests__/adapters/CodexAdapter.test.js +80 -15
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/PiAdapter.test.js +590 -0
- package/dist/__tests__/adapters/PiAdapter.test.js.map +1 -0
- package/dist/__tests__/terminal/TerminalFocusManager.test.js +73 -0
- package/dist/__tests__/terminal/TerminalFocusManager.test.js.map +1 -0
- package/dist/__tests__/utils/agents.test.js +17 -0
- package/dist/__tests__/utils/agents.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.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +4 -0
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +38 -1
- package/dist/adapters/CodexAdapter.js.map +1 -1
- package/dist/adapters/PiAdapter.d.ts +62 -0
- package/dist/adapters/PiAdapter.d.ts.map +1 -0
- package/dist/adapters/PiAdapter.js +450 -0
- package/dist/adapters/PiAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/terminal/TerminalFocusManager.d.ts +1 -0
- package/dist/terminal/TerminalFocusManager.d.ts.map +1 -1
- package/dist/terminal/TerminalFocusManager.js +10 -9
- package/dist/terminal/TerminalFocusManager.js.map +1 -1
- package/dist/utils/agents.d.ts +1 -1
- package/dist/utils/agents.d.ts.map +1 -1
- package/dist/utils/agents.js +29 -3
- package/dist/utils/agents.js.map +1 -1
- package/package.json +6 -1
- package/src/__tests__/adapters/CodexAdapter.test.ts +64 -13
- package/src/__tests__/adapters/PiAdapter.test.ts +435 -0
- package/src/__tests__/terminal/TerminalFocusManager.test.ts +92 -0
- package/src/__tests__/utils/agents.test.ts +17 -0
- package/src/adapters/AgentAdapter.ts +1 -1
- package/src/adapters/CodexAdapter.ts +51 -1
- package/src/adapters/PiAdapter.ts +597 -0
- package/src/adapters/index.ts +1 -0
- package/src/index.ts +1 -0
- package/src/terminal/TerminalFocusManager.ts +11 -3
- package/src/utils/agents.ts +22 -2
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for PiAdapter
|
|
3
|
+
*/ import * as fs from 'fs';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { PiAdapter } from '../../adapters/PiAdapter.js';
|
|
7
|
+
import { AgentStatus } from '../../adapters/AgentAdapter.js';
|
|
8
|
+
import { AgentRegistry } from '../../utils/AgentRegistry.js';
|
|
9
|
+
import { listAgentProcesses, enrichProcesses } from '../../utils/process.js';
|
|
10
|
+
import { matchProcessesToSessions, generateAgentName } from '../../utils/matching.js';
|
|
11
|
+
vi.mock('../../utils/process.js', ()=>({
|
|
12
|
+
listAgentProcesses: vi.fn(),
|
|
13
|
+
enrichProcesses: vi.fn()
|
|
14
|
+
}));
|
|
15
|
+
vi.mock('../../utils/matching.js', ()=>({
|
|
16
|
+
matchProcessesToSessions: vi.fn(),
|
|
17
|
+
generateAgentName: vi.fn()
|
|
18
|
+
}));
|
|
19
|
+
const mockedListAgentProcesses = listAgentProcesses;
|
|
20
|
+
const mockedEnrichProcesses = enrichProcesses;
|
|
21
|
+
const mockedMatchProcessesToSessions = matchProcessesToSessions;
|
|
22
|
+
const mockedGenerateAgentName = generateAgentName;
|
|
23
|
+
describe('PiAdapter', ()=>{
|
|
24
|
+
let adapter;
|
|
25
|
+
let tmpHome;
|
|
26
|
+
let sessionsDir;
|
|
27
|
+
beforeEach(()=>{
|
|
28
|
+
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'pi-adapter-test-'));
|
|
29
|
+
process.env.HOME = tmpHome;
|
|
30
|
+
sessionsDir = path.join(tmpHome, '.pi', 'agent', 'sessions');
|
|
31
|
+
fs.mkdirSync(sessionsDir, {
|
|
32
|
+
recursive: true
|
|
33
|
+
});
|
|
34
|
+
adapter = new PiAdapter(new AgentRegistry(path.join(tmpHome, 'agents.json')));
|
|
35
|
+
mockedListAgentProcesses.mockReset();
|
|
36
|
+
mockedEnrichProcesses.mockReset();
|
|
37
|
+
mockedMatchProcessesToSessions.mockReset();
|
|
38
|
+
mockedGenerateAgentName.mockReset();
|
|
39
|
+
mockedEnrichProcesses.mockImplementation((procs)=>procs);
|
|
40
|
+
mockedMatchProcessesToSessions.mockReturnValue([]);
|
|
41
|
+
mockedGenerateAgentName.mockImplementation((cwd, pid)=>{
|
|
42
|
+
const folder = path.basename(cwd) || 'unknown';
|
|
43
|
+
return `${folder} (${pid})`;
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
afterEach(()=>{
|
|
47
|
+
fs.rmSync(tmpHome, {
|
|
48
|
+
recursive: true,
|
|
49
|
+
force: true
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
it('exposes pi type', ()=>{
|
|
53
|
+
expect(adapter.type).toBe('pi');
|
|
54
|
+
});
|
|
55
|
+
it('identifies Pi commands without matching unrelated paths', ()=>{
|
|
56
|
+
expect(adapter.canHandle({
|
|
57
|
+
pid: 1,
|
|
58
|
+
command: 'pi',
|
|
59
|
+
cwd: '/repo',
|
|
60
|
+
tty: 'ttys001'
|
|
61
|
+
})).toBe(true);
|
|
62
|
+
expect(adapter.canHandle({
|
|
63
|
+
pid: 2,
|
|
64
|
+
command: '/usr/local/bin/PI --model x',
|
|
65
|
+
cwd: '/repo',
|
|
66
|
+
tty: 'ttys002'
|
|
67
|
+
})).toBe(true);
|
|
68
|
+
expect(adapter.canHandle({
|
|
69
|
+
pid: 3,
|
|
70
|
+
command: 'node /opt/pi/bin/pi.js',
|
|
71
|
+
cwd: '/repo',
|
|
72
|
+
tty: 'ttys003'
|
|
73
|
+
})).toBe(true);
|
|
74
|
+
expect(adapter.canHandle({
|
|
75
|
+
pid: 4,
|
|
76
|
+
command: 'node /repo/feature-pi-adapter/script.js',
|
|
77
|
+
cwd: '/repo',
|
|
78
|
+
tty: 'ttys004'
|
|
79
|
+
})).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
it('maps a running Pi process to the tracker session for its PID', async ()=>{
|
|
82
|
+
const cwd = '/repo/project-a';
|
|
83
|
+
const proc = makeProcess({
|
|
84
|
+
pid: 101,
|
|
85
|
+
cwd
|
|
86
|
+
});
|
|
87
|
+
const sessionFile = writePiSession(cwd, [
|
|
88
|
+
{
|
|
89
|
+
type: 'session_meta',
|
|
90
|
+
timestamp: '2026-06-10T08:58:20.754Z',
|
|
91
|
+
sessionId: 'sess-101',
|
|
92
|
+
cwd
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
role: 'user',
|
|
96
|
+
timestamp: '2026-06-10T08:58:21.000Z',
|
|
97
|
+
content: 'implement Pi adapter'
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
role: 'assistant',
|
|
101
|
+
timestamp: new Date().toISOString(),
|
|
102
|
+
content: 'working on it'
|
|
103
|
+
}
|
|
104
|
+
]);
|
|
105
|
+
fs.writeFileSync(path.join(tmpHome, '.pi', 'agent', 'sessions.json'), JSON.stringify({
|
|
106
|
+
101: sessionFile
|
|
107
|
+
}));
|
|
108
|
+
mockedListAgentProcesses.mockReturnValue([
|
|
109
|
+
proc
|
|
110
|
+
]);
|
|
111
|
+
const agents = await adapter.detectAgents();
|
|
112
|
+
expect(agents).toHaveLength(1);
|
|
113
|
+
expect(agents[0]).toMatchObject({
|
|
114
|
+
type: 'pi',
|
|
115
|
+
pid: 101,
|
|
116
|
+
projectPath: cwd,
|
|
117
|
+
sessionId: 'sess-101',
|
|
118
|
+
summary: 'implement Pi adapter',
|
|
119
|
+
status: AgentStatus.WAITING,
|
|
120
|
+
sessionFilePath: sessionFile
|
|
121
|
+
});
|
|
122
|
+
expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
it('truncates long user prompts in detected agent summaries', async ()=>{
|
|
125
|
+
const cwd = '/repo/project-long-summary';
|
|
126
|
+
const proc = makeProcess({
|
|
127
|
+
pid: 112,
|
|
128
|
+
cwd
|
|
129
|
+
});
|
|
130
|
+
const longPrompt = 'x'.repeat(140);
|
|
131
|
+
const sessionFile = writePiSession(cwd, [
|
|
132
|
+
{
|
|
133
|
+
type: 'session',
|
|
134
|
+
timestamp: '2026-06-10T08:58:20.754Z',
|
|
135
|
+
id: 'sess-long',
|
|
136
|
+
cwd
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
role: 'user',
|
|
140
|
+
timestamp: '2026-06-10T08:58:21.000Z',
|
|
141
|
+
content: longPrompt
|
|
142
|
+
}
|
|
143
|
+
]);
|
|
144
|
+
fs.writeFileSync(path.join(tmpHome, '.pi', 'agent', 'sessions.json'), JSON.stringify({
|
|
145
|
+
112: sessionFile
|
|
146
|
+
}));
|
|
147
|
+
mockedListAgentProcesses.mockReturnValue([
|
|
148
|
+
proc
|
|
149
|
+
]);
|
|
150
|
+
const agents = await adapter.detectAgents();
|
|
151
|
+
expect(agents[0].summary).toHaveLength(120);
|
|
152
|
+
expect(agents[0].summary.endsWith('...')).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
it('uses the filename session id fallback and reports running when the latest message is from the user', async ()=>{
|
|
155
|
+
const cwd = '/repo/project-filename-fallback';
|
|
156
|
+
const proc = makeProcess({
|
|
157
|
+
pid: 113,
|
|
158
|
+
cwd
|
|
159
|
+
});
|
|
160
|
+
const sessionFile = writePiSessionWithFileName(cwd, 'plain-session.jsonl', [
|
|
161
|
+
{
|
|
162
|
+
role: 'user',
|
|
163
|
+
timestamp: new Date().toISOString(),
|
|
164
|
+
content: 'still working'
|
|
165
|
+
}
|
|
166
|
+
]);
|
|
167
|
+
fs.writeFileSync(path.join(tmpHome, '.pi', 'agent', 'sessions.json'), JSON.stringify({
|
|
168
|
+
113: sessionFile
|
|
169
|
+
}));
|
|
170
|
+
mockedListAgentProcesses.mockReturnValue([
|
|
171
|
+
proc
|
|
172
|
+
]);
|
|
173
|
+
const agents = await adapter.detectAgents();
|
|
174
|
+
expect(agents[0]).toMatchObject({
|
|
175
|
+
sessionId: 'plain-session',
|
|
176
|
+
summary: 'still working',
|
|
177
|
+
status: AgentStatus.RUNNING
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
it('falls back to legacy matching when sessions.json is missing', async ()=>{
|
|
181
|
+
const cwd = '/repo/project-b';
|
|
182
|
+
const proc = makeProcess({
|
|
183
|
+
pid: 202,
|
|
184
|
+
cwd
|
|
185
|
+
});
|
|
186
|
+
const sessionFile = writePiSession(cwd, [
|
|
187
|
+
{
|
|
188
|
+
timestamp: '2026-06-10T08:58:20.754Z',
|
|
189
|
+
sessionId: 'sess-202'
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
role: 'user',
|
|
193
|
+
timestamp: '2026-06-10T08:58:21.000Z',
|
|
194
|
+
content: 'fallback matching please'
|
|
195
|
+
}
|
|
196
|
+
]);
|
|
197
|
+
mockedListAgentProcesses.mockReturnValue([
|
|
198
|
+
proc
|
|
199
|
+
]);
|
|
200
|
+
mockedMatchProcessesToSessions.mockReturnValue([
|
|
201
|
+
{
|
|
202
|
+
process: proc,
|
|
203
|
+
session: {
|
|
204
|
+
sessionId: 'sess-202',
|
|
205
|
+
filePath: sessionFile,
|
|
206
|
+
projectDir: path.dirname(sessionFile),
|
|
207
|
+
birthtimeMs: Date.now(),
|
|
208
|
+
resolvedCwd: cwd
|
|
209
|
+
},
|
|
210
|
+
deltaMs: 0
|
|
211
|
+
}
|
|
212
|
+
]);
|
|
213
|
+
const agents = await adapter.detectAgents();
|
|
214
|
+
expect(agents).toHaveLength(1);
|
|
215
|
+
expect(agents[0]).toMatchObject({
|
|
216
|
+
type: 'pi',
|
|
217
|
+
pid: 202,
|
|
218
|
+
projectPath: cwd,
|
|
219
|
+
sessionId: 'sess-202',
|
|
220
|
+
summary: 'fallback matching please'
|
|
221
|
+
});
|
|
222
|
+
expect(mockedMatchProcessesToSessions).toHaveBeenCalledWith([
|
|
223
|
+
proc
|
|
224
|
+
], expect.arrayContaining([
|
|
225
|
+
expect.objectContaining({
|
|
226
|
+
filePath: sessionFile,
|
|
227
|
+
resolvedCwd: cwd
|
|
228
|
+
})
|
|
229
|
+
]));
|
|
230
|
+
});
|
|
231
|
+
it('ignores malformed tracker metadata and still falls back to legacy matching', async ()=>{
|
|
232
|
+
const cwd = '/repo/project-c';
|
|
233
|
+
const proc = makeProcess({
|
|
234
|
+
pid: 303,
|
|
235
|
+
cwd
|
|
236
|
+
});
|
|
237
|
+
const sessionFile = writePiSession(cwd, [
|
|
238
|
+
{
|
|
239
|
+
timestamp: '2026-06-10T08:58:20.754Z',
|
|
240
|
+
sessionId: 'sess-303',
|
|
241
|
+
cwd
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
role: 'user',
|
|
245
|
+
timestamp: '2026-06-10T08:58:21.000Z',
|
|
246
|
+
content: 'recover from bad tracker'
|
|
247
|
+
}
|
|
248
|
+
]);
|
|
249
|
+
fs.writeFileSync(path.join(tmpHome, '.pi', 'agent', 'sessions.json'), '{bad json');
|
|
250
|
+
mockedListAgentProcesses.mockReturnValue([
|
|
251
|
+
proc
|
|
252
|
+
]);
|
|
253
|
+
mockedMatchProcessesToSessions.mockReturnValue([
|
|
254
|
+
{
|
|
255
|
+
process: proc,
|
|
256
|
+
session: {
|
|
257
|
+
sessionId: 'sess-303',
|
|
258
|
+
filePath: sessionFile,
|
|
259
|
+
projectDir: path.dirname(sessionFile),
|
|
260
|
+
birthtimeMs: Date.now(),
|
|
261
|
+
resolvedCwd: cwd
|
|
262
|
+
},
|
|
263
|
+
deltaMs: 0
|
|
264
|
+
}
|
|
265
|
+
]);
|
|
266
|
+
const agents = await adapter.detectAgents();
|
|
267
|
+
expect(agents).toHaveLength(1);
|
|
268
|
+
expect(agents[0].sessionId).toBe('sess-303');
|
|
269
|
+
});
|
|
270
|
+
it('falls back to legacy matching when a trusted tracker session is unparseable', async ()=>{
|
|
271
|
+
const cwd = '/repo/project-bad-tracker-session';
|
|
272
|
+
const proc = makeProcess({
|
|
273
|
+
pid: 304,
|
|
274
|
+
cwd
|
|
275
|
+
});
|
|
276
|
+
const badSessionFile = writePiSessionWithFileName(cwd, 'bad.jsonl', [
|
|
277
|
+
'{not json'
|
|
278
|
+
]);
|
|
279
|
+
const fallbackSessionFile = writePiSession(cwd, [
|
|
280
|
+
{
|
|
281
|
+
timestamp: '2026-06-10T08:58:20.754Z',
|
|
282
|
+
sessionId: 'sess-304',
|
|
283
|
+
cwd
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
role: 'user',
|
|
287
|
+
timestamp: '2026-06-10T08:58:21.000Z',
|
|
288
|
+
content: 'fallback after bad tracker session'
|
|
289
|
+
}
|
|
290
|
+
]);
|
|
291
|
+
fs.writeFileSync(path.join(tmpHome, '.pi', 'agent', 'sessions.json'), JSON.stringify({
|
|
292
|
+
304: badSessionFile
|
|
293
|
+
}));
|
|
294
|
+
mockedListAgentProcesses.mockReturnValue([
|
|
295
|
+
proc
|
|
296
|
+
]);
|
|
297
|
+
mockedMatchProcessesToSessions.mockReturnValue([
|
|
298
|
+
{
|
|
299
|
+
process: proc,
|
|
300
|
+
session: {
|
|
301
|
+
sessionId: 'sess-304',
|
|
302
|
+
filePath: fallbackSessionFile,
|
|
303
|
+
projectDir: path.dirname(fallbackSessionFile),
|
|
304
|
+
birthtimeMs: Date.now(),
|
|
305
|
+
resolvedCwd: cwd
|
|
306
|
+
},
|
|
307
|
+
deltaMs: 0
|
|
308
|
+
}
|
|
309
|
+
]);
|
|
310
|
+
const agents = await adapter.detectAgents();
|
|
311
|
+
expect(agents).toHaveLength(1);
|
|
312
|
+
expect(agents[0]).toMatchObject({
|
|
313
|
+
sessionId: 'sess-304',
|
|
314
|
+
summary: 'fallback after bad tracker session',
|
|
315
|
+
sessionFilePath: fallbackSessionFile
|
|
316
|
+
});
|
|
317
|
+
expect(mockedMatchProcessesToSessions).toHaveBeenCalled();
|
|
318
|
+
});
|
|
319
|
+
it('does not trust tracker paths outside the Pi sessions directory', async ()=>{
|
|
320
|
+
const cwd = '/repo/project-d';
|
|
321
|
+
const proc = makeProcess({
|
|
322
|
+
pid: 404,
|
|
323
|
+
cwd
|
|
324
|
+
});
|
|
325
|
+
const outside = path.join(tmpHome, 'outside.jsonl');
|
|
326
|
+
fs.writeFileSync(outside, JSON.stringify({
|
|
327
|
+
role: 'user',
|
|
328
|
+
content: 'nope'
|
|
329
|
+
}));
|
|
330
|
+
fs.writeFileSync(path.join(tmpHome, '.pi', 'agent', 'sessions.json'), JSON.stringify({
|
|
331
|
+
404: outside
|
|
332
|
+
}));
|
|
333
|
+
mockedListAgentProcesses.mockReturnValue([
|
|
334
|
+
proc
|
|
335
|
+
]);
|
|
336
|
+
const agents = await adapter.detectAgents();
|
|
337
|
+
expect(agents).toHaveLength(1);
|
|
338
|
+
expect(agents[0]).toMatchObject({
|
|
339
|
+
type: 'pi',
|
|
340
|
+
pid: 404,
|
|
341
|
+
sessionId: 'pid-404',
|
|
342
|
+
summary: 'Pi process running'
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
it('returns a process-only agent when no session can be matched', async ()=>{
|
|
346
|
+
const proc = makeProcess({
|
|
347
|
+
pid: 505,
|
|
348
|
+
cwd: '/repo/project-e'
|
|
349
|
+
});
|
|
350
|
+
mockedListAgentProcesses.mockReturnValue([
|
|
351
|
+
proc
|
|
352
|
+
]);
|
|
353
|
+
const agents = await adapter.detectAgents();
|
|
354
|
+
expect(agents).toHaveLength(1);
|
|
355
|
+
expect(agents[0]).toMatchObject({
|
|
356
|
+
type: 'pi',
|
|
357
|
+
status: AgentStatus.RUNNING,
|
|
358
|
+
pid: 505,
|
|
359
|
+
projectPath: '/repo/project-e',
|
|
360
|
+
sessionId: 'pid-505',
|
|
361
|
+
summary: 'Pi process running'
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
it('reads user and assistant conversation messages from JSONL', ()=>{
|
|
365
|
+
const cwd = '/repo/project-f';
|
|
366
|
+
const sessionFile = writePiSession(cwd, [
|
|
367
|
+
{
|
|
368
|
+
role: 'system',
|
|
369
|
+
timestamp: '2026-06-10T08:58:20.000Z',
|
|
370
|
+
content: 'hidden'
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
role: 'user',
|
|
374
|
+
timestamp: '2026-06-10T08:58:21.000Z',
|
|
375
|
+
content: 'hello pi'
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
type: 'assistant',
|
|
379
|
+
timestamp: '2026-06-10T08:58:22.000Z',
|
|
380
|
+
message: {
|
|
381
|
+
content: 'hello human'
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
'{not json'
|
|
385
|
+
]);
|
|
386
|
+
expect(adapter.getConversation(sessionFile)).toEqual([
|
|
387
|
+
{
|
|
388
|
+
role: 'user',
|
|
389
|
+
content: 'hello pi',
|
|
390
|
+
timestamp: '2026-06-10T08:58:21.000Z'
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
role: 'assistant',
|
|
394
|
+
content: 'hello human',
|
|
395
|
+
timestamp: '2026-06-10T08:58:22.000Z'
|
|
396
|
+
}
|
|
397
|
+
]);
|
|
398
|
+
});
|
|
399
|
+
it('includes system entries only in verbose conversation mode', ()=>{
|
|
400
|
+
const cwd = '/repo/project-verbose';
|
|
401
|
+
const sessionFile = writePiSession(cwd, [
|
|
402
|
+
{
|
|
403
|
+
role: 'system',
|
|
404
|
+
timestamp: '2026-06-10T08:58:20.000Z',
|
|
405
|
+
content: 'model changed'
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
role: 'user',
|
|
409
|
+
timestamp: '2026-06-10T08:58:21.000Z',
|
|
410
|
+
content: 'visible'
|
|
411
|
+
}
|
|
412
|
+
]);
|
|
413
|
+
expect(adapter.getConversation(sessionFile)).toEqual([
|
|
414
|
+
{
|
|
415
|
+
role: 'user',
|
|
416
|
+
content: 'visible',
|
|
417
|
+
timestamp: '2026-06-10T08:58:21.000Z'
|
|
418
|
+
}
|
|
419
|
+
]);
|
|
420
|
+
expect(adapter.getConversation(sessionFile, {
|
|
421
|
+
verbose: true
|
|
422
|
+
})).toEqual([
|
|
423
|
+
{
|
|
424
|
+
role: 'system',
|
|
425
|
+
content: 'model changed',
|
|
426
|
+
timestamp: '2026-06-10T08:58:20.000Z'
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
role: 'user',
|
|
430
|
+
content: 'visible',
|
|
431
|
+
timestamp: '2026-06-10T08:58:21.000Z'
|
|
432
|
+
}
|
|
433
|
+
]);
|
|
434
|
+
});
|
|
435
|
+
it('reads real Pi message entries with nested role and text parts', async ()=>{
|
|
436
|
+
const cwd = '/repo/project-real';
|
|
437
|
+
const proc = makeProcess({
|
|
438
|
+
pid: 606,
|
|
439
|
+
cwd
|
|
440
|
+
});
|
|
441
|
+
const sessionFile = writePiSession(cwd, [
|
|
442
|
+
{
|
|
443
|
+
type: 'session',
|
|
444
|
+
version: 3,
|
|
445
|
+
id: 'sess-real',
|
|
446
|
+
timestamp: '2026-06-10T13:27:17.581Z',
|
|
447
|
+
cwd
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
type: 'model_change',
|
|
451
|
+
id: 'model-1',
|
|
452
|
+
timestamp: '2026-06-10T13:27:17.655Z',
|
|
453
|
+
modelId: 'claude-sonnet-4-6'
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
type: 'message',
|
|
457
|
+
id: 'msg-user',
|
|
458
|
+
timestamp: '2026-06-10T13:27:37.975Z',
|
|
459
|
+
message: {
|
|
460
|
+
role: 'user',
|
|
461
|
+
content: [
|
|
462
|
+
{
|
|
463
|
+
type: 'text',
|
|
464
|
+
text: 'hello'
|
|
465
|
+
}
|
|
466
|
+
],
|
|
467
|
+
timestamp: 1781098057974
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
type: 'message',
|
|
472
|
+
id: 'msg-assistant',
|
|
473
|
+
timestamp: '2026-06-10T13:27:40.161Z',
|
|
474
|
+
message: {
|
|
475
|
+
role: 'assistant',
|
|
476
|
+
content: [
|
|
477
|
+
{
|
|
478
|
+
type: 'text',
|
|
479
|
+
text: 'Hello! How can I help you today?'
|
|
480
|
+
}
|
|
481
|
+
],
|
|
482
|
+
provider: 'anthropic',
|
|
483
|
+
model: 'claude-sonnet-4-6',
|
|
484
|
+
timestamp: 1781098058012
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
]);
|
|
488
|
+
fs.writeFileSync(path.join(tmpHome, '.pi', 'agent', 'sessions.json'), JSON.stringify({
|
|
489
|
+
606: sessionFile
|
|
490
|
+
}));
|
|
491
|
+
mockedListAgentProcesses.mockReturnValue([
|
|
492
|
+
proc
|
|
493
|
+
]);
|
|
494
|
+
expect(adapter.getConversation(sessionFile)).toEqual([
|
|
495
|
+
{
|
|
496
|
+
role: 'user',
|
|
497
|
+
content: 'hello',
|
|
498
|
+
timestamp: '2026-06-10T13:27:37.975Z'
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
role: 'assistant',
|
|
502
|
+
content: 'Hello! How can I help you today?',
|
|
503
|
+
timestamp: '2026-06-10T13:27:40.161Z'
|
|
504
|
+
}
|
|
505
|
+
]);
|
|
506
|
+
const agents = await adapter.detectAgents();
|
|
507
|
+
expect(agents[0]).toMatchObject({
|
|
508
|
+
sessionId: 'sess-real',
|
|
509
|
+
summary: 'hello',
|
|
510
|
+
lastActive: new Date('2026-06-10T13:27:40.161Z')
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
it('lists historical sessions and applies cwd filtering', async ()=>{
|
|
514
|
+
const matchingCwd = '/repo/project-g';
|
|
515
|
+
const otherCwd = '/repo/project-h';
|
|
516
|
+
const matchingSession = writePiSession(matchingCwd, [
|
|
517
|
+
{
|
|
518
|
+
timestamp: '2026-06-10T08:58:20.754Z',
|
|
519
|
+
sessionId: 'sess-g',
|
|
520
|
+
cwd: matchingCwd
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
role: 'user',
|
|
524
|
+
timestamp: '2026-06-10T08:58:21.000Z',
|
|
525
|
+
content: 'first matching message'
|
|
526
|
+
}
|
|
527
|
+
]);
|
|
528
|
+
writePiSession(otherCwd, [
|
|
529
|
+
{
|
|
530
|
+
timestamp: '2026-06-10T08:58:20.754Z',
|
|
531
|
+
sessionId: 'sess-h',
|
|
532
|
+
cwd: otherCwd
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
role: 'user',
|
|
536
|
+
timestamp: '2026-06-10T08:58:21.000Z',
|
|
537
|
+
content: 'other message'
|
|
538
|
+
}
|
|
539
|
+
]);
|
|
540
|
+
const sessions = await adapter.listSessions({
|
|
541
|
+
cwd: matchingCwd
|
|
542
|
+
});
|
|
543
|
+
expect(sessions).toEqual([
|
|
544
|
+
expect.objectContaining({
|
|
545
|
+
type: 'pi',
|
|
546
|
+
sessionId: 'sess-g',
|
|
547
|
+
cwd: matchingCwd,
|
|
548
|
+
firstUserMessage: 'first matching message',
|
|
549
|
+
sessionFilePath: matchingSession
|
|
550
|
+
})
|
|
551
|
+
]);
|
|
552
|
+
});
|
|
553
|
+
function makeProcess(overrides) {
|
|
554
|
+
return {
|
|
555
|
+
pid: 1,
|
|
556
|
+
command: 'pi',
|
|
557
|
+
cwd: '/repo',
|
|
558
|
+
tty: 'ttys001',
|
|
559
|
+
startTime: new Date('2026-06-10T08:58:20.000Z'),
|
|
560
|
+
...overrides
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function writePiSession(cwd, entries) {
|
|
564
|
+
const projectDir = path.join(sessionsDir, encodeProjectDir(cwd));
|
|
565
|
+
fs.mkdirSync(projectDir, {
|
|
566
|
+
recursive: true
|
|
567
|
+
});
|
|
568
|
+
const sessionId = entries.map((entry)=>typeof entry === 'string' ? undefined : entry.sessionId).find((value)=>typeof value === 'string') ?? cryptoRandomSessionId();
|
|
569
|
+
const filePath = path.join(projectDir, `2026-06-10T08-58-20-754Z_${sessionId}.jsonl`);
|
|
570
|
+
fs.writeFileSync(filePath, entries.map((entry)=>typeof entry === 'string' ? entry : JSON.stringify(entry)).join('\n'));
|
|
571
|
+
return filePath;
|
|
572
|
+
}
|
|
573
|
+
function writePiSessionWithFileName(cwd, fileName, entries) {
|
|
574
|
+
const projectDir = path.join(sessionsDir, encodeProjectDir(cwd));
|
|
575
|
+
fs.mkdirSync(projectDir, {
|
|
576
|
+
recursive: true
|
|
577
|
+
});
|
|
578
|
+
const filePath = path.join(projectDir, fileName);
|
|
579
|
+
fs.writeFileSync(filePath, entries.map((entry)=>typeof entry === 'string' ? entry : JSON.stringify(entry)).join('\n'));
|
|
580
|
+
return filePath;
|
|
581
|
+
}
|
|
582
|
+
function encodeProjectDir(cwd) {
|
|
583
|
+
return cwd.replace(/\//g, '-').replace(/^-?/, '--') + '--';
|
|
584
|
+
}
|
|
585
|
+
function cryptoRandomSessionId() {
|
|
586
|
+
return `019eb0c1-06d2-71ed-90ee-${Math.random().toString(16).slice(2, 14).padEnd(12, '0')}`;
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
//# sourceMappingURL=PiAdapter.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/adapters/PiAdapter.test.ts"],"sourcesContent":["/**\n * Tests for PiAdapter\n */\n\nimport type { MockedFunction } from 'vitest';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\n\nimport { PiAdapter } from '../../adapters/PiAdapter.js';\nimport type { ProcessInfo } from '../../adapters/AgentAdapter.js';\nimport { AgentStatus } from '../../adapters/AgentAdapter.js';\nimport { AgentRegistry } from '../../utils/AgentRegistry.js';\nimport { listAgentProcesses, enrichProcesses } from '../../utils/process.js';\nimport { matchProcessesToSessions, 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 matchProcessesToSessions: vi.fn(),\n generateAgentName: vi.fn(),\n}));\n\nconst mockedListAgentProcesses = listAgentProcesses as MockedFunction<typeof listAgentProcesses>;\nconst mockedEnrichProcesses = enrichProcesses as MockedFunction<typeof enrichProcesses>;\nconst mockedMatchProcessesToSessions = matchProcessesToSessions as MockedFunction<typeof matchProcessesToSessions>;\nconst mockedGenerateAgentName = generateAgentName as MockedFunction<typeof generateAgentName>;\n\ndescribe('PiAdapter', () => {\n let adapter: PiAdapter;\n let tmpHome: string;\n let sessionsDir: string;\n\n beforeEach(() => {\n tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'pi-adapter-test-'));\n process.env.HOME = tmpHome;\n sessionsDir = path.join(tmpHome, '.pi', 'agent', 'sessions');\n fs.mkdirSync(sessionsDir, { recursive: true });\n\n adapter = new PiAdapter(new AgentRegistry(path.join(tmpHome, 'agents.json')));\n mockedListAgentProcesses.mockReset();\n mockedEnrichProcesses.mockReset();\n mockedMatchProcessesToSessions.mockReset();\n mockedGenerateAgentName.mockReset();\n\n mockedEnrichProcesses.mockImplementation((procs) => procs);\n mockedMatchProcessesToSessions.mockReturnValue([]);\n mockedGenerateAgentName.mockImplementation((cwd: string, pid: number) => {\n const folder = path.basename(cwd) || 'unknown';\n return `${folder} (${pid})`;\n });\n });\n\n afterEach(() => {\n fs.rmSync(tmpHome, { recursive: true, force: true });\n });\n\n it('exposes pi type', () => {\n expect(adapter.type).toBe('pi');\n });\n\n it('identifies Pi commands without matching unrelated paths', () => {\n expect(adapter.canHandle({ pid: 1, command: 'pi', cwd: '/repo', tty: 'ttys001' })).toBe(true);\n expect(adapter.canHandle({ pid: 2, command: '/usr/local/bin/PI --model x', cwd: '/repo', tty: 'ttys002' })).toBe(true);\n expect(adapter.canHandle({ pid: 3, command: 'node /opt/pi/bin/pi.js', cwd: '/repo', tty: 'ttys003' })).toBe(true);\n expect(adapter.canHandle({ pid: 4, command: 'node /repo/feature-pi-adapter/script.js', cwd: '/repo', tty: 'ttys004' })).toBe(false);\n });\n\n it('maps a running Pi process to the tracker session for its PID', async () => {\n const cwd = '/repo/project-a';\n const proc = makeProcess({ pid: 101, cwd });\n const sessionFile = writePiSession(cwd, [\n { type: 'session_meta', timestamp: '2026-06-10T08:58:20.754Z', sessionId: 'sess-101', cwd },\n { role: 'user', timestamp: '2026-06-10T08:58:21.000Z', content: 'implement Pi adapter' },\n { role: 'assistant', timestamp: new Date().toISOString(), content: 'working on it' },\n ]);\n fs.writeFileSync(\n path.join(tmpHome, '.pi', 'agent', 'sessions.json'),\n JSON.stringify({ 101: sessionFile }),\n );\n mockedListAgentProcesses.mockReturnValue([proc]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: 'pi',\n pid: 101,\n projectPath: cwd,\n sessionId: 'sess-101',\n summary: 'implement Pi adapter',\n status: AgentStatus.WAITING,\n sessionFilePath: sessionFile,\n });\n expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();\n });\n\n it('truncates long user prompts in detected agent summaries', async () => {\n const cwd = '/repo/project-long-summary';\n const proc = makeProcess({ pid: 112, cwd });\n const longPrompt = 'x'.repeat(140);\n const sessionFile = writePiSession(cwd, [\n { type: 'session', timestamp: '2026-06-10T08:58:20.754Z', id: 'sess-long', cwd },\n { role: 'user', timestamp: '2026-06-10T08:58:21.000Z', content: longPrompt },\n ]);\n fs.writeFileSync(\n path.join(tmpHome, '.pi', 'agent', 'sessions.json'),\n JSON.stringify({ 112: sessionFile }),\n );\n mockedListAgentProcesses.mockReturnValue([proc]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents[0].summary).toHaveLength(120);\n expect(agents[0].summary.endsWith('...')).toBe(true);\n });\n\n it('uses the filename session id fallback and reports running when the latest message is from the user', async () => {\n const cwd = '/repo/project-filename-fallback';\n const proc = makeProcess({ pid: 113, cwd });\n const sessionFile = writePiSessionWithFileName(cwd, 'plain-session.jsonl', [\n { role: 'user', timestamp: new Date().toISOString(), content: 'still working' },\n ]);\n fs.writeFileSync(\n path.join(tmpHome, '.pi', 'agent', 'sessions.json'),\n JSON.stringify({ 113: sessionFile }),\n );\n mockedListAgentProcesses.mockReturnValue([proc]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents[0]).toMatchObject({\n sessionId: 'plain-session',\n summary: 'still working',\n status: AgentStatus.RUNNING,\n });\n });\n\n it('falls back to legacy matching when sessions.json is missing', async () => {\n const cwd = '/repo/project-b';\n const proc = makeProcess({ pid: 202, cwd });\n const sessionFile = writePiSession(cwd, [\n { timestamp: '2026-06-10T08:58:20.754Z', sessionId: 'sess-202' },\n { role: 'user', timestamp: '2026-06-10T08:58:21.000Z', content: 'fallback matching please' },\n ]);\n mockedListAgentProcesses.mockReturnValue([proc]);\n mockedMatchProcessesToSessions.mockReturnValue([\n {\n process: proc,\n session: {\n sessionId: 'sess-202',\n filePath: sessionFile,\n projectDir: path.dirname(sessionFile),\n birthtimeMs: Date.now(),\n resolvedCwd: cwd,\n },\n deltaMs: 0,\n },\n ]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: 'pi',\n pid: 202,\n projectPath: cwd,\n sessionId: 'sess-202',\n summary: 'fallback matching please',\n });\n expect(mockedMatchProcessesToSessions).toHaveBeenCalledWith(\n [proc],\n expect.arrayContaining([\n expect.objectContaining({ filePath: sessionFile, resolvedCwd: cwd }),\n ]),\n );\n });\n\n it('ignores malformed tracker metadata and still falls back to legacy matching', async () => {\n const cwd = '/repo/project-c';\n const proc = makeProcess({ pid: 303, cwd });\n const sessionFile = writePiSession(cwd, [\n { timestamp: '2026-06-10T08:58:20.754Z', sessionId: 'sess-303', cwd },\n { role: 'user', timestamp: '2026-06-10T08:58:21.000Z', content: 'recover from bad tracker' },\n ]);\n fs.writeFileSync(path.join(tmpHome, '.pi', 'agent', 'sessions.json'), '{bad json');\n mockedListAgentProcesses.mockReturnValue([proc]);\n mockedMatchProcessesToSessions.mockReturnValue([\n {\n process: proc,\n session: {\n sessionId: 'sess-303',\n filePath: sessionFile,\n projectDir: path.dirname(sessionFile),\n birthtimeMs: Date.now(),\n resolvedCwd: cwd,\n },\n deltaMs: 0,\n },\n ]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0].sessionId).toBe('sess-303');\n });\n\n it('falls back to legacy matching when a trusted tracker session is unparseable', async () => {\n const cwd = '/repo/project-bad-tracker-session';\n const proc = makeProcess({ pid: 304, cwd });\n const badSessionFile = writePiSessionWithFileName(cwd, 'bad.jsonl', ['{not json']);\n const fallbackSessionFile = writePiSession(cwd, [\n { timestamp: '2026-06-10T08:58:20.754Z', sessionId: 'sess-304', cwd },\n { role: 'user', timestamp: '2026-06-10T08:58:21.000Z', content: 'fallback after bad tracker session' },\n ]);\n fs.writeFileSync(\n path.join(tmpHome, '.pi', 'agent', 'sessions.json'),\n JSON.stringify({ 304: badSessionFile }),\n );\n mockedListAgentProcesses.mockReturnValue([proc]);\n mockedMatchProcessesToSessions.mockReturnValue([\n {\n process: proc,\n session: {\n sessionId: 'sess-304',\n filePath: fallbackSessionFile,\n projectDir: path.dirname(fallbackSessionFile),\n birthtimeMs: Date.now(),\n resolvedCwd: cwd,\n },\n deltaMs: 0,\n },\n ]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n sessionId: 'sess-304',\n summary: 'fallback after bad tracker session',\n sessionFilePath: fallbackSessionFile,\n });\n expect(mockedMatchProcessesToSessions).toHaveBeenCalled();\n });\n\n it('does not trust tracker paths outside the Pi sessions directory', async () => {\n const cwd = '/repo/project-d';\n const proc = makeProcess({ pid: 404, cwd });\n const outside = path.join(tmpHome, 'outside.jsonl');\n fs.writeFileSync(outside, JSON.stringify({ role: 'user', content: 'nope' }));\n fs.writeFileSync(\n path.join(tmpHome, '.pi', 'agent', 'sessions.json'),\n JSON.stringify({ 404: outside }),\n );\n mockedListAgentProcesses.mockReturnValue([proc]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: 'pi',\n pid: 404,\n sessionId: 'pid-404',\n summary: 'Pi process running',\n });\n });\n\n it('returns a process-only agent when no session can be matched', async () => {\n const proc = makeProcess({ pid: 505, cwd: '/repo/project-e' });\n mockedListAgentProcesses.mockReturnValue([proc]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: 'pi',\n status: AgentStatus.RUNNING,\n pid: 505,\n projectPath: '/repo/project-e',\n sessionId: 'pid-505',\n summary: 'Pi process running',\n });\n });\n\n it('reads user and assistant conversation messages from JSONL', () => {\n const cwd = '/repo/project-f';\n const sessionFile = writePiSession(cwd, [\n { role: 'system', timestamp: '2026-06-10T08:58:20.000Z', content: 'hidden' },\n { role: 'user', timestamp: '2026-06-10T08:58:21.000Z', content: 'hello pi' },\n { type: 'assistant', timestamp: '2026-06-10T08:58:22.000Z', message: { content: 'hello human' } },\n '{not json',\n ]);\n\n expect(adapter.getConversation(sessionFile)).toEqual([\n { role: 'user', content: 'hello pi', timestamp: '2026-06-10T08:58:21.000Z' },\n { role: 'assistant', content: 'hello human', timestamp: '2026-06-10T08:58:22.000Z' },\n ]);\n });\n\n it('includes system entries only in verbose conversation mode', () => {\n const cwd = '/repo/project-verbose';\n const sessionFile = writePiSession(cwd, [\n { role: 'system', timestamp: '2026-06-10T08:58:20.000Z', content: 'model changed' },\n { role: 'user', timestamp: '2026-06-10T08:58:21.000Z', content: 'visible' },\n ]);\n\n expect(adapter.getConversation(sessionFile)).toEqual([\n { role: 'user', content: 'visible', timestamp: '2026-06-10T08:58:21.000Z' },\n ]);\n expect(adapter.getConversation(sessionFile, { verbose: true })).toEqual([\n { role: 'system', content: 'model changed', timestamp: '2026-06-10T08:58:20.000Z' },\n { role: 'user', content: 'visible', timestamp: '2026-06-10T08:58:21.000Z' },\n ]);\n });\n\n it('reads real Pi message entries with nested role and text parts', async () => {\n const cwd = '/repo/project-real';\n const proc = makeProcess({ pid: 606, cwd });\n const sessionFile = writePiSession(cwd, [\n { type: 'session', version: 3, id: 'sess-real', timestamp: '2026-06-10T13:27:17.581Z', cwd },\n { type: 'model_change', id: 'model-1', timestamp: '2026-06-10T13:27:17.655Z', modelId: 'claude-sonnet-4-6' },\n {\n type: 'message',\n id: 'msg-user',\n timestamp: '2026-06-10T13:27:37.975Z',\n message: {\n role: 'user',\n content: [{ type: 'text', text: 'hello' }],\n timestamp: 1781098057974,\n },\n },\n {\n type: 'message',\n id: 'msg-assistant',\n timestamp: '2026-06-10T13:27:40.161Z',\n message: {\n role: 'assistant',\n content: [{ type: 'text', text: 'Hello! How can I help you today?' }],\n provider: 'anthropic',\n model: 'claude-sonnet-4-6',\n timestamp: 1781098058012,\n },\n },\n ]);\n fs.writeFileSync(\n path.join(tmpHome, '.pi', 'agent', 'sessions.json'),\n JSON.stringify({ 606: sessionFile }),\n );\n mockedListAgentProcesses.mockReturnValue([proc]);\n\n expect(adapter.getConversation(sessionFile)).toEqual([\n { role: 'user', content: 'hello', timestamp: '2026-06-10T13:27:37.975Z' },\n { role: 'assistant', content: 'Hello! How can I help you today?', timestamp: '2026-06-10T13:27:40.161Z' },\n ]);\n\n const agents = await adapter.detectAgents();\n expect(agents[0]).toMatchObject({\n sessionId: 'sess-real',\n summary: 'hello',\n lastActive: new Date('2026-06-10T13:27:40.161Z'),\n });\n });\n\n it('lists historical sessions and applies cwd filtering', async () => {\n const matchingCwd = '/repo/project-g';\n const otherCwd = '/repo/project-h';\n const matchingSession = writePiSession(matchingCwd, [\n { timestamp: '2026-06-10T08:58:20.754Z', sessionId: 'sess-g', cwd: matchingCwd },\n { role: 'user', timestamp: '2026-06-10T08:58:21.000Z', content: 'first matching message' },\n ]);\n writePiSession(otherCwd, [\n { timestamp: '2026-06-10T08:58:20.754Z', sessionId: 'sess-h', cwd: otherCwd },\n { role: 'user', timestamp: '2026-06-10T08:58:21.000Z', content: 'other message' },\n ]);\n\n const sessions = await adapter.listSessions({ cwd: matchingCwd });\n\n expect(sessions).toEqual([\n expect.objectContaining({\n type: 'pi',\n sessionId: 'sess-g',\n cwd: matchingCwd,\n firstUserMessage: 'first matching message',\n sessionFilePath: matchingSession,\n }),\n ]);\n });\n\n function makeProcess(overrides: Partial<ProcessInfo>): ProcessInfo {\n return {\n pid: 1,\n command: 'pi',\n cwd: '/repo',\n tty: 'ttys001',\n startTime: new Date('2026-06-10T08:58:20.000Z'),\n ...overrides,\n };\n }\n\n function writePiSession(cwd: string, entries: Array<Record<string, unknown> | string>): string {\n const projectDir = path.join(sessionsDir, encodeProjectDir(cwd));\n fs.mkdirSync(projectDir, { recursive: true });\n const sessionId = entries\n .map((entry) => typeof entry === 'string' ? undefined : entry.sessionId)\n .find((value): value is string => typeof value === 'string') ?? cryptoRandomSessionId();\n const filePath = path.join(projectDir, `2026-06-10T08-58-20-754Z_${sessionId}.jsonl`);\n fs.writeFileSync(\n filePath,\n entries.map((entry) => typeof entry === 'string' ? entry : JSON.stringify(entry)).join('\\n'),\n );\n return filePath;\n }\n\n function writePiSessionWithFileName(cwd: string, fileName: string, entries: Array<Record<string, unknown> | string>): string {\n const projectDir = path.join(sessionsDir, encodeProjectDir(cwd));\n fs.mkdirSync(projectDir, { recursive: true });\n const filePath = path.join(projectDir, fileName);\n fs.writeFileSync(\n filePath,\n entries.map((entry) => typeof entry === 'string' ? entry : JSON.stringify(entry)).join('\\n'),\n );\n return filePath;\n }\n\n function encodeProjectDir(cwd: string): string {\n return cwd.replace(/\\//g, '-').replace(/^-?/, '--') + '--';\n }\n\n function cryptoRandomSessionId(): string {\n return `019eb0c1-06d2-71ed-90ee-${Math.random().toString(16).slice(2, 14).padEnd(12, '0')}`;\n }\n});\n"],"names":["fs","os","path","PiAdapter","AgentStatus","AgentRegistry","listAgentProcesses","enrichProcesses","matchProcessesToSessions","generateAgentName","vi","mock","fn","mockedListAgentProcesses","mockedEnrichProcesses","mockedMatchProcessesToSessions","mockedGenerateAgentName","describe","adapter","tmpHome","sessionsDir","beforeEach","mkdtempSync","join","tmpdir","process","env","HOME","mkdirSync","recursive","mockReset","mockImplementation","procs","mockReturnValue","cwd","pid","folder","basename","afterEach","rmSync","force","it","expect","type","toBe","canHandle","command","tty","proc","makeProcess","sessionFile","writePiSession","timestamp","sessionId","role","content","Date","toISOString","writeFileSync","JSON","stringify","agents","detectAgents","toHaveLength","toMatchObject","projectPath","summary","status","WAITING","sessionFilePath","not","toHaveBeenCalled","longPrompt","repeat","id","endsWith","writePiSessionWithFileName","RUNNING","session","filePath","projectDir","dirname","birthtimeMs","now","resolvedCwd","deltaMs","toHaveBeenCalledWith","arrayContaining","objectContaining","badSessionFile","fallbackSessionFile","outside","message","getConversation","toEqual","verbose","version","modelId","text","provider","model","lastActive","matchingCwd","otherCwd","matchingSession","sessions","listSessions","firstUserMessage","overrides","startTime","entries","encodeProjectDir","map","entry","undefined","find","value","cryptoRandomSessionId","fileName","replace","Math","random","toString","slice","padEnd"],"mappings":"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"}
|