@ai-devkit/agent-manager 0.12.0 → 0.14.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/README.md +32 -0
- package/dist/adapters/AgentAdapter.d.ts +1 -1
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.d.ts +26 -2
- package/dist/adapters/ClaudeCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.js +97 -37
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/adapters/OpenCodeAdapter.d.ts +35 -0
- package/dist/adapters/OpenCodeAdapter.d.ts.map +1 -0
- package/dist/adapters/OpenCodeAdapter.js +329 -0
- package/dist/adapters/OpenCodeAdapter.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 +3 -1
- 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 +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +227 -1
- package/src/__tests__/adapters/OpenCodeAdapter.test.ts +475 -0
- package/src/adapters/AgentAdapter.ts +1 -1
- package/src/adapters/ClaudeCodeAdapter.ts +121 -40
- package/src/adapters/OpenCodeAdapter.ts +340 -0
- package/src/adapters/index.ts +1 -0
- package/src/index.ts +1 -0
|
@@ -408,6 +408,194 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
408
408
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
409
409
|
});
|
|
410
410
|
|
|
411
|
+
it('should prefer PID-file live status over JSONL-derived status and surface waitingFor in summary', async () => {
|
|
412
|
+
const startTime = new Date();
|
|
413
|
+
const processes: ProcessInfo[] = [
|
|
414
|
+
{ pid: 55050, command: 'claude', cwd: '/project/wait', tty: 'ttys001', startTime },
|
|
415
|
+
];
|
|
416
|
+
mockedListAgentProcesses.mockReturnValue(processes);
|
|
417
|
+
mockedEnrichProcesses.mockReturnValue(processes);
|
|
418
|
+
|
|
419
|
+
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'claude-pid-wait-'));
|
|
420
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
421
|
+
const projectsDir = path.join(tmpDir, 'projects');
|
|
422
|
+
const projDir = path.join(projectsDir, '-project-wait');
|
|
423
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
424
|
+
fs.mkdirSync(projDir, { recursive: true });
|
|
425
|
+
|
|
426
|
+
const sessionId = 'wait-session';
|
|
427
|
+
const jsonlPath = path.join(projDir, `${sessionId}.jsonl`);
|
|
428
|
+
// JSONL trails with permission-mode → parser would resolve to UNKNOWN.
|
|
429
|
+
// PID file's live status must win.
|
|
430
|
+
fs.writeFileSync(jsonlPath, [
|
|
431
|
+
JSON.stringify({ type: 'user', timestamp: new Date().toISOString(), cwd: '/project/wait', message: { content: '/reddit-commenter' } }),
|
|
432
|
+
JSON.stringify({ type: 'permission-mode', timestamp: new Date().toISOString(), permissionMode: 'default' }),
|
|
433
|
+
].join('\n'));
|
|
434
|
+
|
|
435
|
+
fs.writeFileSync(
|
|
436
|
+
path.join(sessionsDir, '55050.json'),
|
|
437
|
+
JSON.stringify({
|
|
438
|
+
pid: 55050, sessionId, cwd: '/project/wait',
|
|
439
|
+
startedAt: startTime.getTime(),
|
|
440
|
+
kind: 'interactive', entrypoint: 'cli',
|
|
441
|
+
status: 'waiting', waitingFor: 'approve Read',
|
|
442
|
+
}),
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
(adapter as any).sessionsDir = sessionsDir;
|
|
446
|
+
(adapter as any).projectsDir = projectsDir;
|
|
447
|
+
|
|
448
|
+
const agents = await adapter.detectAgents();
|
|
449
|
+
|
|
450
|
+
expect(agents).toHaveLength(1);
|
|
451
|
+
expect(agents[0].status).toBe(AgentStatus.WAITING);
|
|
452
|
+
expect(agents[0].summary).toContain('/reddit-commenter');
|
|
453
|
+
expect(agents[0].summary).toContain('waiting for approve Read');
|
|
454
|
+
|
|
455
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('should resolve PID-file status: "idle" to AgentStatus.IDLE even when JSONL would say UNKNOWN', async () => {
|
|
459
|
+
const startTime = new Date();
|
|
460
|
+
const processes: ProcessInfo[] = [
|
|
461
|
+
{ pid: 55051, command: 'claude', cwd: '/project/idle', tty: 'ttys001', startTime },
|
|
462
|
+
];
|
|
463
|
+
mockedListAgentProcesses.mockReturnValue(processes);
|
|
464
|
+
mockedEnrichProcesses.mockReturnValue(processes);
|
|
465
|
+
|
|
466
|
+
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'claude-pid-idle-'));
|
|
467
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
468
|
+
const projectsDir = path.join(tmpDir, 'projects');
|
|
469
|
+
const projDir = path.join(projectsDir, '-project-idle');
|
|
470
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
471
|
+
fs.mkdirSync(projDir, { recursive: true });
|
|
472
|
+
|
|
473
|
+
const sessionId = 'idle-session';
|
|
474
|
+
const jsonlPath = path.join(projDir, `${sessionId}.jsonl`);
|
|
475
|
+
fs.writeFileSync(jsonlPath, [
|
|
476
|
+
JSON.stringify({ type: 'user', timestamp: new Date().toISOString(), cwd: '/project/idle', message: { content: 'hello' } }),
|
|
477
|
+
JSON.stringify({ type: 'permission-mode', timestamp: new Date().toISOString(), permissionMode: 'default' }),
|
|
478
|
+
].join('\n'));
|
|
479
|
+
|
|
480
|
+
fs.writeFileSync(
|
|
481
|
+
path.join(sessionsDir, '55051.json'),
|
|
482
|
+
JSON.stringify({
|
|
483
|
+
pid: 55051, sessionId, cwd: '/project/idle',
|
|
484
|
+
startedAt: startTime.getTime(),
|
|
485
|
+
kind: 'interactive', entrypoint: 'cli',
|
|
486
|
+
status: 'idle',
|
|
487
|
+
}),
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
(adapter as any).sessionsDir = sessionsDir;
|
|
491
|
+
(adapter as any).projectsDir = projectsDir;
|
|
492
|
+
|
|
493
|
+
const agents = await adapter.detectAgents();
|
|
494
|
+
|
|
495
|
+
expect(agents).toHaveLength(1);
|
|
496
|
+
expect(agents[0].status).toBe(AgentStatus.IDLE);
|
|
497
|
+
// No waitingFor in PID file → summary is just the last user message
|
|
498
|
+
expect(agents[0].summary).toBe('hello');
|
|
499
|
+
|
|
500
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('should fall back to JSONL-derived status when PID-file status is missing or unrecognized', async () => {
|
|
504
|
+
const startTime = new Date();
|
|
505
|
+
const processes: ProcessInfo[] = [
|
|
506
|
+
{ pid: 55052, command: 'claude', cwd: '/project/legacy', tty: 'ttys001', startTime },
|
|
507
|
+
];
|
|
508
|
+
mockedListAgentProcesses.mockReturnValue(processes);
|
|
509
|
+
mockedEnrichProcesses.mockReturnValue(processes);
|
|
510
|
+
|
|
511
|
+
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'claude-pid-nostat-'));
|
|
512
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
513
|
+
const projectsDir = path.join(tmpDir, 'projects');
|
|
514
|
+
const projDir = path.join(projectsDir, '-project-legacy');
|
|
515
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
516
|
+
fs.mkdirSync(projDir, { recursive: true });
|
|
517
|
+
|
|
518
|
+
const sessionId = 'legacy-session';
|
|
519
|
+
const jsonlPath = path.join(projDir, `${sessionId}.jsonl`);
|
|
520
|
+
// Last entry is assistant → JSONL parser yields WAITING.
|
|
521
|
+
fs.writeFileSync(jsonlPath, [
|
|
522
|
+
JSON.stringify({ type: 'user', timestamp: new Date().toISOString(), cwd: '/project/legacy', message: { content: 'do the thing' } }),
|
|
523
|
+
JSON.stringify({ type: 'assistant', timestamp: new Date().toISOString() }),
|
|
524
|
+
].join('\n'));
|
|
525
|
+
|
|
526
|
+
// PID file with unrecognized status string — adapter must ignore it and use parser
|
|
527
|
+
fs.writeFileSync(
|
|
528
|
+
path.join(sessionsDir, '55052.json'),
|
|
529
|
+
JSON.stringify({
|
|
530
|
+
pid: 55052, sessionId, cwd: '/project/legacy',
|
|
531
|
+
startedAt: startTime.getTime(),
|
|
532
|
+
kind: 'interactive', entrypoint: 'cli',
|
|
533
|
+
status: 'fantastical-future-state',
|
|
534
|
+
}),
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
(adapter as any).sessionsDir = sessionsDir;
|
|
538
|
+
(adapter as any).projectsDir = projectsDir;
|
|
539
|
+
|
|
540
|
+
const agents = await adapter.detectAgents();
|
|
541
|
+
|
|
542
|
+
expect(agents).toHaveLength(1);
|
|
543
|
+
expect(agents[0].status).toBe(AgentStatus.WAITING);
|
|
544
|
+
|
|
545
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('should pick up live status from PID file even when matched via --resume', async () => {
|
|
549
|
+
const sessionId = 'aaaaaaaa-1111-2222-3333-444444444444';
|
|
550
|
+
const startTime = new Date();
|
|
551
|
+
const processes: ProcessInfo[] = [
|
|
552
|
+
{
|
|
553
|
+
pid: 55053,
|
|
554
|
+
command: `claude --resume ${sessionId}`,
|
|
555
|
+
cwd: '/project/resume-wait',
|
|
556
|
+
tty: 'ttys001',
|
|
557
|
+
startTime,
|
|
558
|
+
},
|
|
559
|
+
];
|
|
560
|
+
mockedListAgentProcesses.mockReturnValue(processes);
|
|
561
|
+
mockedEnrichProcesses.mockReturnValue(processes);
|
|
562
|
+
|
|
563
|
+
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'claude-resume-wait-'));
|
|
564
|
+
const sessionsDir = path.join(tmpDir, 'sessions');
|
|
565
|
+
const projectsDir = path.join(tmpDir, 'projects');
|
|
566
|
+
const projDir = path.join(projectsDir, '-project-resume-wait');
|
|
567
|
+
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
568
|
+
fs.mkdirSync(projDir, { recursive: true });
|
|
569
|
+
|
|
570
|
+
const jsonlPath = path.join(projDir, `${sessionId}.jsonl`);
|
|
571
|
+
fs.writeFileSync(jsonlPath, [
|
|
572
|
+
JSON.stringify({ type: 'user', timestamp: new Date().toISOString(), cwd: '/project/resume-wait', message: { content: 'resumed work' } }),
|
|
573
|
+
JSON.stringify({ type: 'permission-mode', timestamp: new Date().toISOString(), permissionMode: 'default' }),
|
|
574
|
+
].join('\n'));
|
|
575
|
+
|
|
576
|
+
fs.writeFileSync(
|
|
577
|
+
path.join(sessionsDir, '55053.json'),
|
|
578
|
+
JSON.stringify({
|
|
579
|
+
pid: 55053, sessionId, cwd: '/project/resume-wait',
|
|
580
|
+
startedAt: startTime.getTime(),
|
|
581
|
+
kind: 'interactive', entrypoint: 'cli',
|
|
582
|
+
status: 'waiting', waitingFor: 'approve Bash',
|
|
583
|
+
}),
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
(adapter as any).sessionsDir = sessionsDir;
|
|
587
|
+
(adapter as any).projectsDir = projectsDir;
|
|
588
|
+
|
|
589
|
+
const agents = await adapter.detectAgents();
|
|
590
|
+
|
|
591
|
+
expect(agents).toHaveLength(1);
|
|
592
|
+
expect(agents[0].status).toBe(AgentStatus.WAITING);
|
|
593
|
+
expect(agents[0].summary).toContain('resumed work');
|
|
594
|
+
expect(agents[0].summary).toContain('waiting for approve Bash');
|
|
595
|
+
|
|
596
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
597
|
+
});
|
|
598
|
+
|
|
411
599
|
it('should fall back to process-only when direct-matched JSONL becomes unreadable', async () => {
|
|
412
600
|
const startTime = new Date();
|
|
413
601
|
const processes: ProcessInfo[] = [
|
|
@@ -914,7 +1102,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
914
1102
|
};
|
|
915
1103
|
|
|
916
1104
|
const writeJsonl = (cwd: string, sessionId: string) => {
|
|
917
|
-
const encoded = cwd.replace(
|
|
1105
|
+
const encoded = cwd.replace(/[^a-zA-Z0-9]/g, '-');
|
|
918
1106
|
const projDir = path.join(projectsDir, encoded);
|
|
919
1107
|
fs.mkdirSync(projDir, { recursive: true });
|
|
920
1108
|
const filePath = path.join(projDir, `${sessionId}.jsonl`);
|
|
@@ -1049,6 +1237,44 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
1049
1237
|
});
|
|
1050
1238
|
});
|
|
1051
1239
|
|
|
1240
|
+
describe('getProjectDir', () => {
|
|
1241
|
+
const encode = (cwd: string) => (adapter as any).getProjectDir(cwd) as string;
|
|
1242
|
+
|
|
1243
|
+
it('should replace path separators with hyphens', () => {
|
|
1244
|
+
const expected = path.join((adapter as any).projectsDir, '-Users-foo-bar');
|
|
1245
|
+
expect(encode('/Users/foo/bar')).toBe(expected);
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
it('should encode underscores as hyphens (matches Claude Code CLI)', () => {
|
|
1249
|
+
const expected = path.join((adapter as any).projectsDir, '-Users-foo-my-project');
|
|
1250
|
+
expect(encode('/Users/foo/my_project')).toBe(expected);
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
it('should encode dots as hyphens', () => {
|
|
1254
|
+
const expected = path.join((adapter as any).projectsDir, '-Users-foo--worktrees-x');
|
|
1255
|
+
expect(encode('/Users/foo/.worktrees/x')).toBe(expected);
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
it('should collide paths that differ only in non-alphanumeric chars', () => {
|
|
1259
|
+
// The encoding is intentionally lossy — callers must
|
|
1260
|
+
// disambiguate via session JSONL contents, not dir name.
|
|
1261
|
+
expect(encode('/a/b_c')).toBe(encode('/a/b-c'));
|
|
1262
|
+
expect(encode('/a/b_c')).toBe(encode('/a/b.c'));
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
it('should resolve to a real session dir when cwd contains underscores', () => {
|
|
1266
|
+
const cwd = '/Users/foo/my_project';
|
|
1267
|
+
const projectsDir = (adapter as any).projectsDir as string;
|
|
1268
|
+
const expectedDir = path.join(projectsDir, '-Users-foo-my-project');
|
|
1269
|
+
fs.mkdirSync(expectedDir, { recursive: true });
|
|
1270
|
+
const sessionFile = path.join(expectedDir, 'session-underscore.jsonl');
|
|
1271
|
+
fs.writeFileSync(sessionFile, '');
|
|
1272
|
+
|
|
1273
|
+
expect(encode(cwd)).toBe(expectedDir);
|
|
1274
|
+
expect(fs.existsSync(path.join(encode(cwd), 'session-underscore.jsonl'))).toBe(true);
|
|
1275
|
+
});
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1052
1278
|
describe('readSession', () => {
|
|
1053
1279
|
it('should parse session file with timestamps, cwd, and entry type', () => {
|
|
1054
1280
|
const readSession = (adapter as any).parser.readSession.bind((adapter as any).parser);
|