@ai-devkit/agent-manager 0.9.0 → 0.10.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/AgentManager.d.ts +7 -0
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +9 -0
- package/dist/AgentManager.js.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.d.ts +2 -44
- package/dist/adapters/ClaudeCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.js +10 -285
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/terminal/TerminalFocusManager.d.ts.map +1 -1
- package/dist/terminal/TerminalFocusManager.js +38 -27
- package/dist/terminal/TerminalFocusManager.js.map +1 -1
- package/dist/utils/ClaudeSessionParser.d.ts +112 -0
- package/dist/utils/ClaudeSessionParser.d.ts.map +1 -0
- package/dist/utils/ClaudeSessionParser.js +334 -0
- package/dist/utils/ClaudeSessionParser.js.map +1 -0
- package/dist/utils/process.d.ts +3 -4
- package/dist/utils/process.d.ts.map +1 -1
- package/dist/utils/process.js +11 -15
- package/dist/utils/process.js.map +1 -1
- package/package.json +1 -1
- package/src/AgentManager.ts +11 -1
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +29 -29
- package/src/__tests__/utils/process.test.ts +27 -27
- package/src/adapters/ClaudeCodeAdapter.ts +17 -365
- package/src/terminal/TerminalFocusManager.ts +38 -26
- package/src/utils/ClaudeSessionParser.ts +383 -0
- package/src/utils/process.ts +21 -24
package/dist/utils/process.d.ts
CHANGED
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
* Process Detection Utilities
|
|
3
3
|
*
|
|
4
4
|
* Shared shell command wrappers for detecting and inspecting running processes.
|
|
5
|
-
* All
|
|
5
|
+
* All execFileSync calls for process data live here — adapters must not call execFileSync directly.
|
|
6
6
|
*/
|
|
7
7
|
import type { ProcessInfo } from '../adapters/AgentAdapter';
|
|
8
8
|
/**
|
|
9
9
|
* List running processes matching an agent executable name.
|
|
10
10
|
*
|
|
11
|
-
* Uses `ps aux
|
|
12
|
-
*
|
|
13
|
-
* `claude-helper`, `vscode-claude-extension`, or the grep process itself).
|
|
11
|
+
* Uses `ps aux` then filters in JS for exact executable basename match.
|
|
12
|
+
* This avoids shell pipelines and string interpolation.
|
|
14
13
|
*
|
|
15
14
|
* Returned ProcessInfo has pid, command, tty populated.
|
|
16
15
|
* cwd and startTime are NOT populated — call enrichProcesses() to fill them.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/utils/process.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/utils/process.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,EAAE,CA6CrE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAuCvE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAkC3E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAavE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAYjD"}
|
package/dist/utils/process.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Process Detection Utilities
|
|
4
4
|
*
|
|
5
5
|
* Shared shell command wrappers for detecting and inspecting running processes.
|
|
6
|
-
* All
|
|
6
|
+
* All execFileSync calls for process data live here — adapters must not call execFileSync directly.
|
|
7
7
|
*/
|
|
8
8
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
9
|
if (k2 === undefined) k2 = k;
|
|
@@ -49,9 +49,8 @@ const child_process_1 = require("child_process");
|
|
|
49
49
|
/**
|
|
50
50
|
* List running processes matching an agent executable name.
|
|
51
51
|
*
|
|
52
|
-
* Uses `ps aux
|
|
53
|
-
*
|
|
54
|
-
* `claude-helper`, `vscode-claude-extension`, or the grep process itself).
|
|
52
|
+
* Uses `ps aux` then filters in JS for exact executable basename match.
|
|
53
|
+
* This avoids shell pipelines and string interpolation.
|
|
55
54
|
*
|
|
56
55
|
* Returned ProcessInfo has pid, command, tty populated.
|
|
57
56
|
* cwd and startTime are NOT populated — call enrichProcesses() to fill them.
|
|
@@ -62,9 +61,8 @@ function listAgentProcesses(namePattern) {
|
|
|
62
61
|
return [];
|
|
63
62
|
}
|
|
64
63
|
try {
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
const output = (0, child_process_1.execSync)(`ps aux | grep -i '${escapedPattern}'`, { encoding: 'utf-8' });
|
|
64
|
+
const output = (0, child_process_1.execFileSync)('ps', ['aux'], { encoding: 'utf-8' });
|
|
65
|
+
const lowerPattern = namePattern.toLowerCase();
|
|
68
66
|
const processes = [];
|
|
69
67
|
for (const line of output.trim().split('\n')) {
|
|
70
68
|
if (!line.trim())
|
|
@@ -77,10 +75,10 @@ function listAgentProcesses(namePattern) {
|
|
|
77
75
|
continue;
|
|
78
76
|
const tty = parts[6];
|
|
79
77
|
const command = parts.slice(10).join(' ');
|
|
80
|
-
//
|
|
78
|
+
// Check that the executable basename matches exactly
|
|
81
79
|
const executable = command.trim().split(/\s+/)[0] || '';
|
|
82
80
|
const base = path.basename(executable).toLowerCase();
|
|
83
|
-
if (base !==
|
|
81
|
+
if (base !== lowerPattern && base !== `${lowerPattern}.exe`) {
|
|
84
82
|
continue;
|
|
85
83
|
}
|
|
86
84
|
const ttyShort = tty.startsWith('/dev/') ? tty.slice(5) : tty;
|
|
@@ -108,7 +106,7 @@ function batchGetProcessCwds(pids) {
|
|
|
108
106
|
if (pids.length === 0)
|
|
109
107
|
return result;
|
|
110
108
|
try {
|
|
111
|
-
const output = (0, child_process_1.
|
|
109
|
+
const output = (0, child_process_1.execFileSync)('lsof', ['-a', '-d', 'cwd', '-Fn', '-p', pids.join(',')], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
112
110
|
// lsof output format: p{PID}\nn{path}\np{PID}\nn{path}...
|
|
113
111
|
let currentPid = null;
|
|
114
112
|
for (const line of output.trim().split('\n')) {
|
|
@@ -125,7 +123,7 @@ function batchGetProcessCwds(pids) {
|
|
|
125
123
|
// Try per-PID fallback with pwdx (Linux)
|
|
126
124
|
for (const pid of pids) {
|
|
127
125
|
try {
|
|
128
|
-
const output = (0, child_process_1.
|
|
126
|
+
const output = (0, child_process_1.execFileSync)('pwdx', [String(pid)], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
129
127
|
const match = output.match(/^\d+:\s*(.+)$/);
|
|
130
128
|
if (match) {
|
|
131
129
|
result.set(pid, match[1].trim());
|
|
@@ -150,7 +148,7 @@ function batchGetProcessStartTimes(pids) {
|
|
|
150
148
|
if (pids.length === 0)
|
|
151
149
|
return result;
|
|
152
150
|
try {
|
|
153
|
-
const output = (0, child_process_1.
|
|
151
|
+
const output = (0, child_process_1.execFileSync)('ps', ['-o', 'pid=,lstart=', '-p', pids.join(',')], { encoding: 'utf-8' });
|
|
154
152
|
for (const rawLine of output.split('\n')) {
|
|
155
153
|
const line = rawLine.trim();
|
|
156
154
|
if (!line)
|
|
@@ -199,9 +197,7 @@ function enrichProcesses(processes) {
|
|
|
199
197
|
*/
|
|
200
198
|
function getProcessTty(pid) {
|
|
201
199
|
try {
|
|
202
|
-
const output = (0, child_process_1.
|
|
203
|
-
encoding: 'utf-8',
|
|
204
|
-
});
|
|
200
|
+
const output = (0, child_process_1.execFileSync)('ps', ['-p', String(pid), '-o', 'tty='], { encoding: 'utf-8' });
|
|
205
201
|
const tty = output.trim();
|
|
206
202
|
return tty.startsWith('/dev/') ? tty.slice(5) : tty;
|
|
207
203
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/utils/process.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/utils/process.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/AgentManager.ts
CHANGED
|
@@ -66,9 +66,19 @@ export class AgentManager {
|
|
|
66
66
|
return Array.from(this.adapters.values());
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Get a registered adapter by type
|
|
71
|
+
*
|
|
72
|
+
* @param type Agent type to look up
|
|
73
|
+
* @returns The adapter, or undefined if not registered
|
|
74
|
+
*/
|
|
75
|
+
getAdapter(type: string): AgentAdapter | undefined {
|
|
76
|
+
return this.adapters.get(type);
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
/**
|
|
70
80
|
* Check if an adapter is registered for a specific type
|
|
71
|
-
*
|
|
81
|
+
*
|
|
72
82
|
* @param type Agent type to check
|
|
73
83
|
* @returns True if adapter is registered
|
|
74
84
|
*/
|
|
@@ -361,7 +361,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
361
361
|
(adapter as any).projectsDir = projectsDir;
|
|
362
362
|
|
|
363
363
|
// Simulate JSONL disappearing between existence check and read
|
|
364
|
-
jest.spyOn(adapter as any, 'readSession').mockReturnValueOnce(null);
|
|
364
|
+
jest.spyOn((adapter as any).parser, 'readSession').mockReturnValueOnce(null);
|
|
365
365
|
|
|
366
366
|
const agents = await adapter.detectAgents();
|
|
367
367
|
|
|
@@ -408,7 +408,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
408
408
|
]);
|
|
409
409
|
|
|
410
410
|
// Simulate JSONL disappearing between match and read
|
|
411
|
-
jest.spyOn(adapter as any, 'readSession').mockReturnValueOnce(null);
|
|
411
|
+
jest.spyOn((adapter as any).parser, 'readSession').mockReturnValueOnce(null);
|
|
412
412
|
|
|
413
413
|
const agents = await adapter.detectAgents();
|
|
414
414
|
|
|
@@ -601,7 +601,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
601
601
|
describe('helper methods', () => {
|
|
602
602
|
describe('determineStatus', () => {
|
|
603
603
|
it('should return "unknown" for sessions with no last entry type', () => {
|
|
604
|
-
const determineStatus = (adapter as any).determineStatus.bind(adapter);
|
|
604
|
+
const determineStatus = (adapter as any).parser.determineStatus.bind((adapter as any).parser);
|
|
605
605
|
|
|
606
606
|
const session = {
|
|
607
607
|
sessionId: 'test',
|
|
@@ -615,7 +615,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
615
615
|
});
|
|
616
616
|
|
|
617
617
|
it('should return "waiting" for assistant entries', () => {
|
|
618
|
-
const determineStatus = (adapter as any).determineStatus.bind(adapter);
|
|
618
|
+
const determineStatus = (adapter as any).parser.determineStatus.bind((adapter as any).parser);
|
|
619
619
|
|
|
620
620
|
const session = {
|
|
621
621
|
sessionId: 'test',
|
|
@@ -630,7 +630,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
630
630
|
});
|
|
631
631
|
|
|
632
632
|
it('should return "waiting" for user interruption', () => {
|
|
633
|
-
const determineStatus = (adapter as any).determineStatus.bind(adapter);
|
|
633
|
+
const determineStatus = (adapter as any).parser.determineStatus.bind((adapter as any).parser);
|
|
634
634
|
|
|
635
635
|
const session = {
|
|
636
636
|
sessionId: 'test',
|
|
@@ -645,7 +645,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
645
645
|
});
|
|
646
646
|
|
|
647
647
|
it('should return "running" for user/progress entries', () => {
|
|
648
|
-
const determineStatus = (adapter as any).determineStatus.bind(adapter);
|
|
648
|
+
const determineStatus = (adapter as any).parser.determineStatus.bind((adapter as any).parser);
|
|
649
649
|
|
|
650
650
|
const session = {
|
|
651
651
|
sessionId: 'test',
|
|
@@ -660,7 +660,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
660
660
|
});
|
|
661
661
|
|
|
662
662
|
it('should not override status based on age (process is running)', () => {
|
|
663
|
-
const determineStatus = (adapter as any).determineStatus.bind(adapter);
|
|
663
|
+
const determineStatus = (adapter as any).parser.determineStatus.bind((adapter as any).parser);
|
|
664
664
|
|
|
665
665
|
const oldDate = new Date(Date.now() - 10 * 60 * 1000);
|
|
666
666
|
const session = {
|
|
@@ -676,7 +676,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
676
676
|
});
|
|
677
677
|
|
|
678
678
|
it('should return "idle" for system entries', () => {
|
|
679
|
-
const determineStatus = (adapter as any).determineStatus.bind(adapter);
|
|
679
|
+
const determineStatus = (adapter as any).parser.determineStatus.bind((adapter as any).parser);
|
|
680
680
|
|
|
681
681
|
const session = {
|
|
682
682
|
sessionId: 'test',
|
|
@@ -691,7 +691,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
691
691
|
});
|
|
692
692
|
|
|
693
693
|
it('should return "running" for thinking entries', () => {
|
|
694
|
-
const determineStatus = (adapter as any).determineStatus.bind(adapter);
|
|
694
|
+
const determineStatus = (adapter as any).parser.determineStatus.bind((adapter as any).parser);
|
|
695
695
|
|
|
696
696
|
const session = {
|
|
697
697
|
sessionId: 'test',
|
|
@@ -706,7 +706,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
706
706
|
});
|
|
707
707
|
|
|
708
708
|
it('should return "running" for progress entries', () => {
|
|
709
|
-
const determineStatus = (adapter as any).determineStatus.bind(adapter);
|
|
709
|
+
const determineStatus = (adapter as any).parser.determineStatus.bind((adapter as any).parser);
|
|
710
710
|
|
|
711
711
|
const session = {
|
|
712
712
|
sessionId: 'test',
|
|
@@ -721,7 +721,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
721
721
|
});
|
|
722
722
|
|
|
723
723
|
it('should return "unknown" for unrecognized entry types', () => {
|
|
724
|
-
const determineStatus = (adapter as any).determineStatus.bind(adapter);
|
|
724
|
+
const determineStatus = (adapter as any).parser.determineStatus.bind((adapter as any).parser);
|
|
725
725
|
|
|
726
726
|
const session = {
|
|
727
727
|
sessionId: 'test',
|
|
@@ -738,12 +738,12 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
738
738
|
|
|
739
739
|
describe('extractUserMessageText', () => {
|
|
740
740
|
it('should extract plain string content', () => {
|
|
741
|
-
const extract = (adapter as any).extractUserMessageText.bind(adapter);
|
|
741
|
+
const extract = (adapter as any).parser['extractUserMessageText'].bind((adapter as any).parser);
|
|
742
742
|
expect(extract('hello world')).toBe('hello world');
|
|
743
743
|
});
|
|
744
744
|
|
|
745
745
|
it('should extract text from array content blocks', () => {
|
|
746
|
-
const extract = (adapter as any).extractUserMessageText.bind(adapter);
|
|
746
|
+
const extract = (adapter as any).parser['extractUserMessageText'].bind((adapter as any).parser);
|
|
747
747
|
|
|
748
748
|
const content = [
|
|
749
749
|
{ type: 'tool_result', content: 'some result' },
|
|
@@ -753,7 +753,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
753
753
|
});
|
|
754
754
|
|
|
755
755
|
it('should return undefined for empty/null content', () => {
|
|
756
|
-
const extract = (adapter as any).extractUserMessageText.bind(adapter);
|
|
756
|
+
const extract = (adapter as any).parser['extractUserMessageText'].bind((adapter as any).parser);
|
|
757
757
|
|
|
758
758
|
expect(extract(undefined)).toBeUndefined();
|
|
759
759
|
expect(extract('')).toBeUndefined();
|
|
@@ -761,35 +761,35 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
761
761
|
});
|
|
762
762
|
|
|
763
763
|
it('should parse command-message tags', () => {
|
|
764
|
-
const extract = (adapter as any).extractUserMessageText.bind(adapter);
|
|
764
|
+
const extract = (adapter as any).parser['extractUserMessageText'].bind((adapter as any).parser);
|
|
765
765
|
|
|
766
766
|
const msg = '<command-message><command-name>commit</command-name><command-args>fix bug</command-args></command-message>';
|
|
767
767
|
expect(extract(msg)).toBe('commit fix bug');
|
|
768
768
|
});
|
|
769
769
|
|
|
770
770
|
it('should parse command-message without args', () => {
|
|
771
|
-
const extract = (adapter as any).extractUserMessageText.bind(adapter);
|
|
771
|
+
const extract = (adapter as any).parser['extractUserMessageText'].bind((adapter as any).parser);
|
|
772
772
|
|
|
773
773
|
const msg = '<command-message><command-name>help</command-name></command-message>';
|
|
774
774
|
expect(extract(msg)).toBe('help');
|
|
775
775
|
});
|
|
776
776
|
|
|
777
777
|
it('should extract ARGUMENTS from skill expansion', () => {
|
|
778
|
-
const extract = (adapter as any).extractUserMessageText.bind(adapter);
|
|
778
|
+
const extract = (adapter as any).parser['extractUserMessageText'].bind((adapter as any).parser);
|
|
779
779
|
|
|
780
780
|
const msg = 'Base directory for this skill: /some/path\n\nSome instructions\n\nARGUMENTS: implement the feature';
|
|
781
781
|
expect(extract(msg)).toBe('implement the feature');
|
|
782
782
|
});
|
|
783
783
|
|
|
784
784
|
it('should return undefined for skill expansion without ARGUMENTS', () => {
|
|
785
|
-
const extract = (adapter as any).extractUserMessageText.bind(adapter);
|
|
785
|
+
const extract = (adapter as any).parser['extractUserMessageText'].bind((adapter as any).parser);
|
|
786
786
|
|
|
787
787
|
const msg = 'Base directory for this skill: /some/path\n\nSome instructions only';
|
|
788
788
|
expect(extract(msg)).toBeUndefined();
|
|
789
789
|
});
|
|
790
790
|
|
|
791
791
|
it('should filter noise messages', () => {
|
|
792
|
-
const extract = (adapter as any).extractUserMessageText.bind(adapter);
|
|
792
|
+
const extract = (adapter as any).parser['extractUserMessageText'].bind((adapter as any).parser);
|
|
793
793
|
|
|
794
794
|
expect(extract('[Request interrupted by user]')).toBeUndefined();
|
|
795
795
|
expect(extract('Tool loaded.')).toBeUndefined();
|
|
@@ -799,7 +799,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
799
799
|
|
|
800
800
|
describe('parseCommandMessage', () => {
|
|
801
801
|
it('should return undefined for malformed command-message', () => {
|
|
802
|
-
const parse = (adapter as any).parseCommandMessage.bind(adapter);
|
|
802
|
+
const parse = (adapter as any).parser['parseCommandMessage'].bind((adapter as any).parser);
|
|
803
803
|
expect(parse('<command-message>no tags</command-message>')).toBeUndefined();
|
|
804
804
|
});
|
|
805
805
|
});
|
|
@@ -977,7 +977,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
977
977
|
|
|
978
978
|
describe('readSession', () => {
|
|
979
979
|
it('should parse session file with timestamps, cwd, and entry type', () => {
|
|
980
|
-
const readSession = (adapter as any).readSession.bind(adapter);
|
|
980
|
+
const readSession = (adapter as any).parser.readSession.bind((adapter as any).parser);
|
|
981
981
|
|
|
982
982
|
const filePath = path.join(tmpDir, 'test-session.jsonl');
|
|
983
983
|
const lines = [
|
|
@@ -999,7 +999,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
999
999
|
});
|
|
1000
1000
|
|
|
1001
1001
|
it('should detect user interruption', () => {
|
|
1002
|
-
const readSession = (adapter as any).readSession.bind(adapter);
|
|
1002
|
+
const readSession = (adapter as any).parser.readSession.bind((adapter as any).parser);
|
|
1003
1003
|
|
|
1004
1004
|
const filePath = path.join(tmpDir, 'interrupted.jsonl');
|
|
1005
1005
|
const lines = [
|
|
@@ -1019,7 +1019,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
1019
1019
|
});
|
|
1020
1020
|
|
|
1021
1021
|
it('should return session with defaults for empty file', () => {
|
|
1022
|
-
const readSession = (adapter as any).readSession.bind(adapter);
|
|
1022
|
+
const readSession = (adapter as any).parser.readSession.bind((adapter as any).parser);
|
|
1023
1023
|
|
|
1024
1024
|
const filePath = path.join(tmpDir, 'empty.jsonl');
|
|
1025
1025
|
fs.writeFileSync(filePath, '');
|
|
@@ -1030,12 +1030,12 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
1030
1030
|
});
|
|
1031
1031
|
|
|
1032
1032
|
it('should return null for non-existent file', () => {
|
|
1033
|
-
const readSession = (adapter as any).readSession.bind(adapter);
|
|
1033
|
+
const readSession = (adapter as any).parser.readSession.bind((adapter as any).parser);
|
|
1034
1034
|
expect(readSession(path.join(tmpDir, 'nonexistent.jsonl'), '/test')).toBeNull();
|
|
1035
1035
|
});
|
|
1036
1036
|
|
|
1037
1037
|
it('should skip metadata entry types for lastEntryType', () => {
|
|
1038
|
-
const readSession = (adapter as any).readSession.bind(adapter);
|
|
1038
|
+
const readSession = (adapter as any).parser.readSession.bind((adapter as any).parser);
|
|
1039
1039
|
|
|
1040
1040
|
const filePath = path.join(tmpDir, 'metadata-test.jsonl');
|
|
1041
1041
|
const lines = [
|
|
@@ -1051,7 +1051,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
1051
1051
|
});
|
|
1052
1052
|
|
|
1053
1053
|
it('should parse snapshot.timestamp from file-history-snapshot first entry', () => {
|
|
1054
|
-
const readSession = (adapter as any).readSession.bind(adapter);
|
|
1054
|
+
const readSession = (adapter as any).parser.readSession.bind((adapter as any).parser);
|
|
1055
1055
|
|
|
1056
1056
|
const filePath = path.join(tmpDir, 'snapshot-ts.jsonl');
|
|
1057
1057
|
const lines = [
|
|
@@ -1070,7 +1070,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
1070
1070
|
});
|
|
1071
1071
|
|
|
1072
1072
|
it('should extract lastUserMessage from session entries', () => {
|
|
1073
|
-
const readSession = (adapter as any).readSession.bind(adapter);
|
|
1073
|
+
const readSession = (adapter as any).parser.readSession.bind((adapter as any).parser);
|
|
1074
1074
|
|
|
1075
1075
|
const filePath = path.join(tmpDir, 'user-msg.jsonl');
|
|
1076
1076
|
const lines = [
|
|
@@ -1086,7 +1086,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
1086
1086
|
});
|
|
1087
1087
|
|
|
1088
1088
|
it('should use lastCwd as projectPath when projectPath is empty', () => {
|
|
1089
|
-
const readSession = (adapter as any).readSession.bind(adapter);
|
|
1089
|
+
const readSession = (adapter as any).parser.readSession.bind((adapter as any).parser);
|
|
1090
1090
|
|
|
1091
1091
|
const filePath = path.join(tmpDir, 'no-project.jsonl');
|
|
1092
1092
|
const lines = [
|
|
@@ -1099,7 +1099,7 @@ describe('ClaudeCodeAdapter', () => {
|
|
|
1099
1099
|
});
|
|
1100
1100
|
|
|
1101
1101
|
it('should handle malformed JSON lines gracefully', () => {
|
|
1102
|
-
const readSession = (adapter as any).readSession.bind(adapter);
|
|
1102
|
+
const readSession = (adapter as any).parser.readSession.bind((adapter as any).parser);
|
|
1103
1103
|
|
|
1104
1104
|
const filePath = path.join(tmpDir, 'malformed.jsonl');
|
|
1105
1105
|
const lines = [
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
|
6
|
-
import {
|
|
6
|
+
import { execFileSync } from 'child_process';
|
|
7
7
|
import {
|
|
8
8
|
listAgentProcesses,
|
|
9
9
|
batchGetProcessCwds,
|
|
@@ -12,18 +12,18 @@ import {
|
|
|
12
12
|
} from '../../utils/process';
|
|
13
13
|
|
|
14
14
|
jest.mock('child_process', () => ({
|
|
15
|
-
|
|
15
|
+
execFileSync: jest.fn(),
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const mockedExecFileSync = execFileSync as jest.MockedFunction<typeof execFileSync>;
|
|
19
19
|
|
|
20
20
|
describe('listAgentProcesses', () => {
|
|
21
21
|
beforeEach(() => {
|
|
22
|
-
|
|
22
|
+
mockedExecFileSync.mockReset();
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
it('should parse ps aux | grep output and post-filter by executable name', () => {
|
|
26
|
-
|
|
26
|
+
mockedExecFileSync.mockReturnValue(
|
|
27
27
|
'user 78070 1.0 0.5 485636016 245952 s018 S+ 11:18PM 1:55.14 claude\n' +
|
|
28
28
|
'user 55106 0.1 0.4 485620368 72496 s015 S+ 9Mar26 8:06.36 claude\n',
|
|
29
29
|
);
|
|
@@ -38,7 +38,7 @@ describe('listAgentProcesses', () => {
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
it('should filter out non-matching executables', () => {
|
|
41
|
-
|
|
41
|
+
mockedExecFileSync.mockReturnValue(
|
|
42
42
|
'user 100 0.0 0.0 0 0 s001 S 1:00PM 0:00 claude\n' +
|
|
43
43
|
'user 200 0.0 0.0 0 0 s002 S 1:00PM 0:00 claude-helper --pid 100\n' +
|
|
44
44
|
'user 300 0.0 0.0 0 0 s003 S 1:00PM 0:00 /usr/bin/claude\n',
|
|
@@ -50,46 +50,46 @@ describe('listAgentProcesses', () => {
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
it('should return empty array on command failure', () => {
|
|
53
|
-
|
|
53
|
+
mockedExecFileSync.mockImplementation(() => { throw new Error('fail'); });
|
|
54
54
|
expect(listAgentProcesses('claude')).toEqual([]);
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
it('should handle empty output', () => {
|
|
58
|
-
|
|
58
|
+
mockedExecFileSync.mockReturnValue('');
|
|
59
59
|
expect(listAgentProcesses('claude')).toEqual([]);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
it('should reject empty pattern', () => {
|
|
63
63
|
expect(listAgentProcesses('')).toEqual([]);
|
|
64
|
-
expect(
|
|
64
|
+
expect(mockedExecFileSync).not.toHaveBeenCalled();
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
it('should reject patterns with shell injection characters', () => {
|
|
68
68
|
expect(listAgentProcesses('claude; rm -rf /')).toEqual([]);
|
|
69
69
|
expect(listAgentProcesses("claude' || true")).toEqual([]);
|
|
70
70
|
expect(listAgentProcesses('$(whoami)')).toEqual([]);
|
|
71
|
-
expect(
|
|
71
|
+
expect(mockedExecFileSync).not.toHaveBeenCalled();
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
it('should accept valid patterns with dashes and underscores', () => {
|
|
75
|
-
|
|
75
|
+
mockedExecFileSync.mockReturnValue('');
|
|
76
76
|
listAgentProcesses('claude-code');
|
|
77
|
-
expect(
|
|
77
|
+
expect(mockedExecFileSync).toHaveBeenCalled();
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
mockedExecFileSync.mockReset();
|
|
80
|
+
mockedExecFileSync.mockReturnValue('');
|
|
81
81
|
listAgentProcesses('my_agent');
|
|
82
|
-
expect(
|
|
82
|
+
expect(mockedExecFileSync).toHaveBeenCalled();
|
|
83
83
|
});
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
describe('batchGetProcessCwds', () => {
|
|
87
87
|
beforeEach(() => {
|
|
88
|
-
|
|
88
|
+
mockedExecFileSync.mockReset();
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
it('should parse batched lsof output', () => {
|
|
92
|
-
|
|
92
|
+
mockedExecFileSync.mockReturnValue(
|
|
93
93
|
'p78070\nn/Users/user/ai-devkit\np55106\nn/Users/user/other-project\n',
|
|
94
94
|
);
|
|
95
95
|
|
|
@@ -104,7 +104,7 @@ describe('batchGetProcessCwds', () => {
|
|
|
104
104
|
|
|
105
105
|
it('should return partial results when lsof succeeds for some PIDs', () => {
|
|
106
106
|
// lsof might not return entries for dead processes
|
|
107
|
-
|
|
107
|
+
mockedExecFileSync.mockReturnValue(
|
|
108
108
|
'p78070\nn/Users/user/ai-devkit\n',
|
|
109
109
|
);
|
|
110
110
|
|
|
@@ -114,7 +114,7 @@ describe('batchGetProcessCwds', () => {
|
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
it('should return empty map on total failure', () => {
|
|
117
|
-
|
|
117
|
+
mockedExecFileSync.mockImplementation(() => { throw new Error('fail'); });
|
|
118
118
|
const cwds = batchGetProcessCwds([78070]);
|
|
119
119
|
// Falls through to pwdx fallback which also fails
|
|
120
120
|
expect(cwds.size).toBe(0);
|
|
@@ -123,11 +123,11 @@ describe('batchGetProcessCwds', () => {
|
|
|
123
123
|
|
|
124
124
|
describe('batchGetProcessStartTimes', () => {
|
|
125
125
|
beforeEach(() => {
|
|
126
|
-
|
|
126
|
+
mockedExecFileSync.mockReset();
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
it('should parse ps lstart output', () => {
|
|
130
|
-
|
|
130
|
+
mockedExecFileSync.mockReturnValue(
|
|
131
131
|
' 78070 Wed Mar 18 23:18:01 2026\n' +
|
|
132
132
|
' 55106 Mon Mar 9 21:41:42 2026\n',
|
|
133
133
|
);
|
|
@@ -143,7 +143,7 @@ describe('batchGetProcessStartTimes', () => {
|
|
|
143
143
|
});
|
|
144
144
|
|
|
145
145
|
it('should skip lines with unparseable dates', () => {
|
|
146
|
-
|
|
146
|
+
mockedExecFileSync.mockReturnValue(
|
|
147
147
|
' 78070 Wed Mar 18 23:18:01 2026\n' +
|
|
148
148
|
' 99999 INVALID_DATE\n',
|
|
149
149
|
);
|
|
@@ -154,20 +154,20 @@ describe('batchGetProcessStartTimes', () => {
|
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
it('should return empty map on failure', () => {
|
|
157
|
-
|
|
157
|
+
mockedExecFileSync.mockImplementation(() => { throw new Error('fail'); });
|
|
158
158
|
expect(batchGetProcessStartTimes([78070])).toEqual(new Map());
|
|
159
159
|
});
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
describe('enrichProcesses', () => {
|
|
163
163
|
beforeEach(() => {
|
|
164
|
-
|
|
164
|
+
mockedExecFileSync.mockReset();
|
|
165
165
|
});
|
|
166
166
|
|
|
167
167
|
it('should populate cwd and startTime on processes', () => {
|
|
168
168
|
// First call: batchGetProcessCwds (lsof)
|
|
169
169
|
// Second call: batchGetProcessStartTimes (ps lstart)
|
|
170
|
-
|
|
170
|
+
mockedExecFileSync
|
|
171
171
|
.mockReturnValueOnce('p100\nn/projects/app\n')
|
|
172
172
|
.mockReturnValueOnce(' 100 Wed Mar 18 23:18:01 2026\n');
|
|
173
173
|
|
|
@@ -182,12 +182,12 @@ describe('enrichProcesses', () => {
|
|
|
182
182
|
|
|
183
183
|
it('should return empty array for empty input', () => {
|
|
184
184
|
expect(enrichProcesses([])).toEqual([]);
|
|
185
|
-
expect(
|
|
185
|
+
expect(mockedExecFileSync).not.toHaveBeenCalled();
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
it('should handle partial failures', () => {
|
|
189
189
|
// lsof succeeds, ps lstart fails
|
|
190
|
-
|
|
190
|
+
mockedExecFileSync
|
|
191
191
|
.mockReturnValueOnce('p100\nn/projects/app\n')
|
|
192
192
|
.mockImplementationOnce(() => { throw new Error('fail'); });
|
|
193
193
|
|