@ai-devkit/agent-manager 0.9.0 → 0.11.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 +21 -1
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +47 -0
- package/dist/AgentManager.js.map +1 -1
- package/dist/adapters/AgentAdapter.d.ts +66 -0
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.d.ts +14 -43
- package/dist/adapters/ClaudeCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.js +60 -275
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +14 -1
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +105 -6
- package/dist/adapters/CodexAdapter.js.map +1 -1
- package/dist/adapters/GeminiCliAdapter.d.ts +9 -1
- package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/adapters/GeminiCliAdapter.js +77 -6
- package/dist/adapters/GeminiCliAdapter.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/terminal/TerminalFocusManager.d.ts.map +1 -1
- package/dist/terminal/TerminalFocusManager.js +36 -27
- package/dist/terminal/TerminalFocusManager.js.map +1 -1
- package/dist/terminal/TtyWriter.d.ts.map +1 -1
- package/dist/terminal/TtyWriter.js +3 -12
- package/dist/terminal/TtyWriter.js.map +1 -1
- package/dist/utils/ClaudeSessionParser.d.ts +114 -0
- package/dist/utils/ClaudeSessionParser.d.ts.map +1 -0
- package/dist/utils/ClaudeSessionParser.js +377 -0
- package/dist/utils/ClaudeSessionParser.js.map +1 -0
- package/dist/utils/applescript.d.ts +6 -0
- package/dist/utils/applescript.d.ts.map +1 -0
- package/dist/utils/applescript.js +14 -0
- package/dist/utils/applescript.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/dist/utils/session.d.ts +34 -5
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +90 -44
- package/dist/utils/session.js.map +1 -1
- package/package.json +1 -1
- package/src/AgentManager.ts +66 -4
- package/src/__tests__/AgentManager.test.ts +134 -2
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +188 -32
- package/src/__tests__/adapters/CodexAdapter.test.ts +123 -3
- package/src/__tests__/adapters/GeminiCliAdapter.test.ts +133 -0
- package/src/__tests__/utils/ClaudeSessionParser.test.ts +195 -0
- package/src/__tests__/utils/process.test.ts +27 -27
- package/src/__tests__/utils/session.test.ts +79 -43
- package/src/adapters/AgentAdapter.ts +76 -0
- package/src/adapters/ClaudeCodeAdapter.ts +82 -356
- package/src/adapters/CodexAdapter.ts +126 -8
- package/src/adapters/GeminiCliAdapter.ts +102 -7
- package/src/index.ts +9 -1
- package/src/terminal/TerminalFocusManager.ts +35 -26
- package/src/terminal/TtyWriter.ts +1 -11
- package/src/utils/ClaudeSessionParser.ts +437 -0
- package/src/utils/applescript.ts +10 -0
- package/src/utils/process.ts +21 -24
- package/src/utils/session.ts +86 -45
|
@@ -3,25 +3,31 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
|
6
|
-
import
|
|
6
|
+
import * as fs from 'fs';
|
|
7
7
|
import { batchGetSessionFileBirthtimes } from '../../utils/session';
|
|
8
8
|
|
|
9
|
-
jest.mock('
|
|
10
|
-
|
|
9
|
+
jest.mock('fs', () => ({
|
|
10
|
+
readdirSync: jest.fn(),
|
|
11
|
+
statSync: jest.fn(),
|
|
11
12
|
}));
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
+
const mockedReaddirSync = fs.readdirSync as jest.MockedFunction<typeof fs.readdirSync>;
|
|
15
|
+
const mockedStatSync = fs.statSync as jest.MockedFunction<typeof fs.statSync>;
|
|
14
16
|
|
|
15
17
|
describe('batchGetSessionFileBirthtimes', () => {
|
|
16
18
|
beforeEach(() => {
|
|
17
|
-
|
|
19
|
+
mockedReaddirSync.mockReset();
|
|
20
|
+
mockedStatSync.mockReset();
|
|
18
21
|
});
|
|
19
22
|
|
|
20
|
-
it('should parse
|
|
21
|
-
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
);
|
|
23
|
+
it('should parse session files correctly', () => {
|
|
24
|
+
mockedReaddirSync.mockReturnValue([
|
|
25
|
+
'abc123.jsonl',
|
|
26
|
+
'def456.jsonl',
|
|
27
|
+
] as unknown as ReturnType<typeof fs.readdirSync>);
|
|
28
|
+
mockedStatSync
|
|
29
|
+
.mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats)
|
|
30
|
+
.mockReturnValueOnce({ birthtimeMs: 1710800500000 } as fs.Stats);
|
|
25
31
|
|
|
26
32
|
const results = batchGetSessionFileBirthtimes(['/home/.claude/projects/my-app']);
|
|
27
33
|
|
|
@@ -39,49 +45,57 @@ describe('batchGetSessionFileBirthtimes', () => {
|
|
|
39
45
|
|
|
40
46
|
it('should return empty array for empty dirs list', () => {
|
|
41
47
|
expect(batchGetSessionFileBirthtimes([])).toEqual([]);
|
|
42
|
-
expect(
|
|
48
|
+
expect(mockedReaddirSync).not.toHaveBeenCalled();
|
|
43
49
|
});
|
|
44
50
|
|
|
45
|
-
it('should return empty array on
|
|
46
|
-
|
|
47
|
-
throw new Error('
|
|
51
|
+
it('should return empty array on readdir failure', () => {
|
|
52
|
+
mockedReaddirSync.mockImplementation(() => {
|
|
53
|
+
throw new Error('ENOENT');
|
|
48
54
|
});
|
|
49
55
|
|
|
50
56
|
expect(batchGetSessionFileBirthtimes(['/some/dir'])).toEqual([]);
|
|
51
57
|
});
|
|
52
58
|
|
|
53
|
-
it('should skip
|
|
54
|
-
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
);
|
|
59
|
+
it('should skip files with invalid birthtime (0 or negative)', () => {
|
|
60
|
+
mockedReaddirSync.mockReturnValue([
|
|
61
|
+
'bad.jsonl',
|
|
62
|
+
'negative.jsonl',
|
|
63
|
+
'good.jsonl',
|
|
64
|
+
] as unknown as ReturnType<typeof fs.readdirSync>);
|
|
65
|
+
mockedStatSync
|
|
66
|
+
.mockReturnValueOnce({ birthtimeMs: 0 } as fs.Stats)
|
|
67
|
+
.mockReturnValueOnce({ birthtimeMs: -1 } as fs.Stats)
|
|
68
|
+
.mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats);
|
|
59
69
|
|
|
60
70
|
const results = batchGetSessionFileBirthtimes(['/dir']);
|
|
61
71
|
expect(results).toHaveLength(1);
|
|
62
72
|
expect(results[0].sessionId).toBe('good');
|
|
63
73
|
});
|
|
64
74
|
|
|
65
|
-
it('should skip non-jsonl files
|
|
66
|
-
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
);
|
|
75
|
+
it('should skip non-jsonl files', () => {
|
|
76
|
+
mockedReaddirSync.mockReturnValue([
|
|
77
|
+
'sessions-index.json',
|
|
78
|
+
'abc123.jsonl',
|
|
79
|
+
] as unknown as ReturnType<typeof fs.readdirSync>);
|
|
80
|
+
mockedStatSync
|
|
81
|
+
.mockReturnValueOnce({ birthtimeMs: 1710800500000 } as fs.Stats);
|
|
70
82
|
|
|
71
83
|
const results = batchGetSessionFileBirthtimes(['/dir']);
|
|
72
84
|
expect(results).toHaveLength(1);
|
|
73
85
|
expect(results[0].sessionId).toBe('abc123');
|
|
74
86
|
});
|
|
75
87
|
|
|
76
|
-
it('should handle empty
|
|
77
|
-
|
|
88
|
+
it('should handle empty directory', () => {
|
|
89
|
+
mockedReaddirSync.mockReturnValue([] as unknown as ReturnType<typeof fs.readdirSync>);
|
|
78
90
|
expect(batchGetSessionFileBirthtimes(['/dir'])).toEqual([]);
|
|
79
91
|
});
|
|
80
92
|
|
|
81
93
|
it('should handle UUID session IDs', () => {
|
|
82
|
-
|
|
83
|
-
'
|
|
84
|
-
);
|
|
94
|
+
mockedReaddirSync.mockReturnValue([
|
|
95
|
+
'068e7b1f-cff5-4c94-bf69-b9acd32d765c.jsonl',
|
|
96
|
+
] as unknown as ReturnType<typeof fs.readdirSync>);
|
|
97
|
+
mockedStatSync
|
|
98
|
+
.mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats);
|
|
85
99
|
|
|
86
100
|
const results = batchGetSessionFileBirthtimes(['/dir']);
|
|
87
101
|
expect(results).toHaveLength(1);
|
|
@@ -89,29 +103,51 @@ describe('batchGetSessionFileBirthtimes', () => {
|
|
|
89
103
|
});
|
|
90
104
|
|
|
91
105
|
it('should leave resolvedCwd empty', () => {
|
|
92
|
-
|
|
106
|
+
mockedReaddirSync.mockReturnValue([
|
|
107
|
+
'abc.jsonl',
|
|
108
|
+
] as unknown as ReturnType<typeof fs.readdirSync>);
|
|
109
|
+
mockedStatSync
|
|
110
|
+
.mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats);
|
|
93
111
|
|
|
94
112
|
const results = batchGetSessionFileBirthtimes(['/dir']);
|
|
95
113
|
expect(results[0].resolvedCwd).toBe('');
|
|
96
114
|
});
|
|
97
115
|
|
|
98
|
-
it('should
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
116
|
+
it('should enumerate multiple directories', () => {
|
|
117
|
+
mockedReaddirSync
|
|
118
|
+
.mockReturnValueOnce([
|
|
119
|
+
'sess1.jsonl',
|
|
120
|
+
'sess3.jsonl',
|
|
121
|
+
] as unknown as ReturnType<typeof fs.readdirSync>)
|
|
122
|
+
.mockReturnValueOnce([
|
|
123
|
+
'sess2.jsonl',
|
|
124
|
+
] as unknown as ReturnType<typeof fs.readdirSync>);
|
|
125
|
+
mockedStatSync
|
|
126
|
+
.mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats)
|
|
127
|
+
.mockReturnValueOnce({ birthtimeMs: 1710800500000 } as fs.Stats)
|
|
128
|
+
.mockReturnValueOnce({ birthtimeMs: 1710800400000 } as fs.Stats);
|
|
104
129
|
|
|
105
130
|
const results = batchGetSessionFileBirthtimes(['/projects/app-a', '/projects/app-b']);
|
|
106
131
|
|
|
107
|
-
expect(
|
|
108
|
-
const cmd = mockedExecSync.mock.calls[0][0] as string;
|
|
109
|
-
expect(cmd).toContain('"/projects/app-a"/*.jsonl');
|
|
110
|
-
expect(cmd).toContain('"/projects/app-b"/*.jsonl');
|
|
132
|
+
expect(mockedReaddirSync).toHaveBeenCalledTimes(2);
|
|
111
133
|
|
|
112
134
|
expect(results).toHaveLength(3);
|
|
113
135
|
expect(results[0].projectDir).toBe('/projects/app-a');
|
|
114
|
-
expect(results[1].projectDir).toBe('/projects/app-
|
|
115
|
-
expect(results[2].projectDir).toBe('/projects/app-
|
|
136
|
+
expect(results[1].projectDir).toBe('/projects/app-a');
|
|
137
|
+
expect(results[2].projectDir).toBe('/projects/app-b');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should skip files where statSync fails', () => {
|
|
141
|
+
mockedReaddirSync.mockReturnValue([
|
|
142
|
+
'good.jsonl',
|
|
143
|
+
'gone.jsonl',
|
|
144
|
+
] as unknown as ReturnType<typeof fs.readdirSync>);
|
|
145
|
+
mockedStatSync
|
|
146
|
+
.mockReturnValueOnce({ birthtimeMs: 1710800324000 } as fs.Stats)
|
|
147
|
+
.mockImplementationOnce(() => { throw new Error('ENOENT'); });
|
|
148
|
+
|
|
149
|
+
const results = batchGetSessionFileBirthtimes(['/dir']);
|
|
150
|
+
expect(results).toHaveLength(1);
|
|
151
|
+
expect(results[0].sessionId).toBe('good');
|
|
116
152
|
});
|
|
117
153
|
});
|
|
@@ -81,6 +81,70 @@ export interface ConversationMessage {
|
|
|
81
81
|
timestamp?: string;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* A historical session discovered on disk (running or not).
|
|
86
|
+
*
|
|
87
|
+
* Used by `listSessions` to surface enough context for a user to identify
|
|
88
|
+
* a session and resume it via the originating tool's resume command.
|
|
89
|
+
*/
|
|
90
|
+
export interface SessionSummary {
|
|
91
|
+
/** Tool that produced this session */
|
|
92
|
+
type: AgentType;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* ID accepted by the tool's resume command. Adapters MUST pass this
|
|
96
|
+
* through verbatim — no normalization, no encoding/decoding — so it
|
|
97
|
+
* round-trips into `claude --resume <id>` (and equivalents).
|
|
98
|
+
*/
|
|
99
|
+
sessionId: string;
|
|
100
|
+
|
|
101
|
+
/** Working directory the session was started in (best-known value) */
|
|
102
|
+
cwd: string;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Trimmed first user message; empty string if none. Adapters apply
|
|
106
|
+
* the same noise-filter their existing parsers use (skip tool_result
|
|
107
|
+
* blocks, request-interruption notices, system-injected skill
|
|
108
|
+
* content). The CLI table renderer substitutes a placeholder for
|
|
109
|
+
* empty values; JSON output keeps the empty string raw.
|
|
110
|
+
*/
|
|
111
|
+
firstUserMessage: string;
|
|
112
|
+
|
|
113
|
+
/** Last activity timestamp (from session content; falls back to file mtime) */
|
|
114
|
+
lastActive: Date;
|
|
115
|
+
|
|
116
|
+
/** Session start time (from session content; falls back to file birthtime/mtime) */
|
|
117
|
+
startedAt: Date;
|
|
118
|
+
|
|
119
|
+
/** Absolute path to the session file on disk (debug/diagnostics) */
|
|
120
|
+
sessionFilePath: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Filters passed by the CLI to {@link AgentAdapter.listSessions}.
|
|
125
|
+
*
|
|
126
|
+
* The CLI is the source of truth for filter defaults and semantics
|
|
127
|
+
* (e.g. cwd defaults to process.cwd(); --all clears it). Adapters apply
|
|
128
|
+
* the values they receive — they don't invent defaults.
|
|
129
|
+
*/
|
|
130
|
+
export interface ListSessionsOptions {
|
|
131
|
+
/**
|
|
132
|
+
* Filter to sessions whose recorded cwd matches this path using strict
|
|
133
|
+
* equality (no prefix/ancestor matching in v1). Undefined = no cwd
|
|
134
|
+
* filter.
|
|
135
|
+
*/
|
|
136
|
+
cwd?: string;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Filter to a single tool. Enforced by `AgentManager.listSessions`,
|
|
140
|
+
* which skips adapters whose `type` doesn't match. Adapters MAY
|
|
141
|
+
* ignore this field — by the time their `listSessions` runs, the
|
|
142
|
+
* type filter is already satisfied. Undefined = include every
|
|
143
|
+
* registered adapter.
|
|
144
|
+
*/
|
|
145
|
+
type?: AgentType;
|
|
146
|
+
}
|
|
147
|
+
|
|
84
148
|
/**
|
|
85
149
|
* Agent Adapter Interface
|
|
86
150
|
*
|
|
@@ -110,4 +174,16 @@ export interface AgentAdapter {
|
|
|
110
174
|
* @returns Array of conversation messages
|
|
111
175
|
*/
|
|
112
176
|
getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[];
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Enumerate historical sessions for this tool from disk.
|
|
180
|
+
*
|
|
181
|
+
* Applies `opts.cwd` as a strict-equality filter when set. Returns
|
|
182
|
+
* {@link SessionSummary} entries unsorted; sorting and global filters
|
|
183
|
+
* are handled by `AgentManager` and the CLI.
|
|
184
|
+
*
|
|
185
|
+
* @param opts Filter options computed by the CLI
|
|
186
|
+
* @returns Array of sessions discovered on disk
|
|
187
|
+
*/
|
|
188
|
+
listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]>;
|
|
113
189
|
}
|