@ai-devkit/agent-manager 0.26.4 → 0.28.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 +12 -4
- package/dist/AgentManager.d.ts +5 -0
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +21 -1
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +112 -0
- package/dist/__tests__/AgentManager.test.js.map +1 -1
- package/dist/__tests__/adapters/CodexAdapter.test.js +199 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
- package/dist/__tests__/database/DurableAgentsDatabase.test.js +100 -0
- package/dist/__tests__/database/DurableAgentsDatabase.test.js.map +1 -0
- package/dist/__tests__/print/ClaudePrintAgent.integration.test.js +7 -7
- package/dist/__tests__/print/ClaudePrintAgent.integration.test.js.map +1 -1
- package/dist/__tests__/print/ClaudePrintAgentService.test.js +7 -7
- package/dist/__tests__/print/ClaudePrintAgentService.test.js.map +1 -1
- package/dist/__tests__/print/ClaudePrintRunner.test.js +1 -1
- package/dist/__tests__/print/ClaudePrintRunner.test.js.map +1 -1
- package/dist/__tests__/print/DurableAgent.test.js +17 -0
- package/dist/__tests__/print/DurableAgent.test.js.map +1 -0
- package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js +186 -0
- package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js.map +1 -0
- package/dist/__tests__/print/{PrintAgentStore.test.js → DurableAgentRepository.test.js} +63 -110
- package/dist/__tests__/print/DurableAgentRepository.test.js.map +1 -0
- package/dist/__tests__/utils/AgentRegistry.test.js +67 -0
- package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -1
- package/dist/adapters/AgentAdapter.d.ts +2 -0
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +1 -0
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +16 -6
- package/dist/adapters/CodexAdapter.js.map +1 -1
- package/dist/database/connection.d.ts +1 -0
- package/dist/database/connection.d.ts.map +1 -1
- package/dist/database/connection.js +20 -5
- package/dist/database/connection.js.map +1 -1
- package/dist/database/migrations/002_pins.sql +1 -0
- package/dist/database/migrations/003_durable_agents.sql +43 -0
- package/dist/durable/ClaudeCliProbe.d.ts.map +1 -0
- package/dist/{print → durable}/ClaudeCliProbe.js +1 -1
- package/dist/durable/ClaudeCliProbe.js.map +1 -0
- package/dist/{print → durable}/ClaudePrintAgentService.d.ts +11 -11
- package/dist/durable/ClaudePrintAgentService.d.ts.map +1 -0
- package/dist/{print → durable}/ClaudePrintAgentService.js +12 -12
- package/dist/durable/ClaudePrintAgentService.js.map +1 -0
- package/dist/{print → durable}/ClaudePrintRunner.d.ts +3 -3
- package/dist/durable/ClaudePrintRunner.d.ts.map +1 -0
- package/dist/{print → durable}/ClaudePrintRunner.js +2 -2
- package/dist/durable/ClaudePrintRunner.js.map +1 -0
- package/dist/durable/DurableAgent.d.ts +61 -0
- package/dist/durable/DurableAgent.d.ts.map +1 -0
- package/dist/durable/DurableAgent.js +46 -0
- package/dist/durable/DurableAgent.js.map +1 -0
- package/dist/durable/DurableAgentRepository.d.ts +60 -0
- package/dist/durable/DurableAgentRepository.d.ts.map +1 -0
- package/dist/durable/DurableAgentRepository.js +325 -0
- package/dist/durable/DurableAgentRepository.js.map +1 -0
- package/dist/index.d.ts +12 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/utils/AgentRegistry.d.ts +5 -0
- package/dist/utils/AgentRegistry.d.ts.map +1 -1
- package/dist/utils/AgentRegistry.js +19 -2
- package/dist/utils/AgentRegistry.js.map +1 -1
- package/package.json +1 -1
- package/src/AgentManager.ts +21 -0
- package/src/__tests__/AgentManager.test.ts +117 -0
- package/src/__tests__/adapters/CodexAdapter.test.ts +132 -0
- package/src/__tests__/database/DurableAgentsDatabase.test.ts +77 -0
- package/src/__tests__/print/ClaudePrintAgent.integration.test.ts +6 -6
- package/src/__tests__/print/ClaudePrintAgentService.test.ts +7 -7
- package/src/__tests__/print/ClaudePrintRunner.test.ts +3 -3
- package/src/__tests__/print/{PrintAgent.test.ts → DurableAgent.test.ts} +6 -6
- package/src/__tests__/print/DurableAgentRepository.sqlite.test.ts +105 -0
- package/src/__tests__/print/DurableAgentRepository.test.ts +163 -0
- package/src/__tests__/utils/AgentRegistry.test.ts +68 -0
- package/src/adapters/AgentAdapter.ts +3 -0
- package/src/adapters/CodexAdapter.ts +14 -11
- package/src/database/connection.ts +18 -5
- package/src/database/migrations/002_pins.sql +1 -0
- package/src/database/migrations/003_durable_agents.sql +43 -0
- package/src/{print → durable}/ClaudeCliProbe.ts +1 -1
- package/src/{print → durable}/ClaudePrintAgentService.ts +21 -21
- package/src/{print → durable}/ClaudePrintRunner.ts +4 -4
- package/src/durable/DurableAgent.ts +91 -0
- package/src/durable/DurableAgentRepository.ts +307 -0
- package/src/index.ts +27 -26
- package/src/utils/AgentRegistry.ts +21 -0
- package/dist/__tests__/print/PrintAgent.test.js +0 -17
- package/dist/__tests__/print/PrintAgent.test.js.map +0 -1
- package/dist/__tests__/print/PrintAgentStore.test.js.map +0 -1
- package/dist/print/ClaudeCliProbe.d.ts.map +0 -1
- package/dist/print/ClaudeCliProbe.js.map +0 -1
- package/dist/print/ClaudePrintAgentService.d.ts.map +0 -1
- package/dist/print/ClaudePrintAgentService.js.map +0 -1
- package/dist/print/ClaudePrintRunner.d.ts.map +0 -1
- package/dist/print/ClaudePrintRunner.js.map +0 -1
- package/dist/print/PrintAgent.d.ts +0 -57
- package/dist/print/PrintAgent.d.ts.map +0 -1
- package/dist/print/PrintAgent.js +0 -42
- package/dist/print/PrintAgent.js.map +0 -1
- package/dist/print/PrintAgentStore.d.ts +0 -69
- package/dist/print/PrintAgentStore.d.ts.map +0 -1
- package/dist/print/PrintAgentStore.js +0 -484
- package/dist/print/PrintAgentStore.js.map +0 -1
- package/src/__tests__/print/PrintAgentStore.test.ts +0 -192
- package/src/print/PrintAgent.ts +0 -86
- package/src/print/PrintAgentStore.ts +0 -503
- /package/dist/{print → durable}/ClaudeCliProbe.d.ts +0 -0
|
@@ -11,36 +11,36 @@ afterEach(()=>{
|
|
|
11
11
|
});
|
|
12
12
|
async function loadStore() {
|
|
13
13
|
const api = await import('../../index.js');
|
|
14
|
-
expect(api).toHaveProperty('
|
|
15
|
-
return api.
|
|
14
|
+
expect(api).toHaveProperty('DurableAgentRepository');
|
|
15
|
+
return api.DurableAgentRepository;
|
|
16
16
|
}
|
|
17
17
|
function fixture() {
|
|
18
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), '
|
|
18
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-store-'));
|
|
19
19
|
tempDirs.push(root);
|
|
20
20
|
const cwd = path.join(root, 'project');
|
|
21
21
|
fs.mkdirSync(cwd);
|
|
22
22
|
return {
|
|
23
23
|
root,
|
|
24
24
|
cwd,
|
|
25
|
-
|
|
25
|
+
dbPath: path.join(root, 'state', 'agents.db')
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
describe('
|
|
28
|
+
describe('DurableAgentRepository create/list/resolve', ()=>{
|
|
29
29
|
it('creates distinct durable identities with a canonical cwd and lists them', async ()=>{
|
|
30
|
-
const
|
|
31
|
-
const { cwd,
|
|
32
|
-
const
|
|
33
|
-
|
|
30
|
+
const DurableAgentRepository = await loadStore();
|
|
31
|
+
const { cwd, dbPath } = fixture();
|
|
32
|
+
const repository = new DurableAgentRepository({
|
|
33
|
+
dbPath,
|
|
34
34
|
now: ()=>new Date('2026-08-07T09:00:00Z')
|
|
35
35
|
});
|
|
36
|
-
const agent = await
|
|
36
|
+
const agent = await repository.create({
|
|
37
37
|
name: 'reviewer',
|
|
38
38
|
cwd
|
|
39
39
|
});
|
|
40
40
|
expect(agent).toMatchObject({
|
|
41
41
|
name: 'reviewer',
|
|
42
42
|
provider: 'claude',
|
|
43
|
-
mode: '
|
|
43
|
+
mode: 'durable',
|
|
44
44
|
cwd: fs.realpathSync(cwd),
|
|
45
45
|
state: 'ready',
|
|
46
46
|
sessionHealth: 'uninitialized',
|
|
@@ -49,96 +49,53 @@ describe('PrintAgentStore create/list/resolve', ()=>{
|
|
|
49
49
|
expect(agent.id).toMatch(/^[0-9a-f-]{36}$/);
|
|
50
50
|
expect(agent.providerSessionId).toMatch(/^[0-9a-f-]{36}$/);
|
|
51
51
|
expect(agent.id).not.toBe(agent.providerSessionId);
|
|
52
|
-
expect(await
|
|
52
|
+
expect(await repository.list()).toEqual([
|
|
53
53
|
agent
|
|
54
54
|
]);
|
|
55
|
-
expect(fs.
|
|
55
|
+
expect(fs.existsSync(dbPath)).toBe(true);
|
|
56
56
|
});
|
|
57
57
|
it('resolves exact ids and names and rejects duplicate names', async ()=>{
|
|
58
|
-
const
|
|
59
|
-
const { cwd,
|
|
60
|
-
const
|
|
61
|
-
|
|
58
|
+
const DurableAgentRepository = await loadStore();
|
|
59
|
+
const { cwd, dbPath } = fixture();
|
|
60
|
+
const repository = new DurableAgentRepository({
|
|
61
|
+
dbPath
|
|
62
62
|
});
|
|
63
|
-
const agent = await
|
|
63
|
+
const agent = await repository.create({
|
|
64
64
|
name: 'Reviewer',
|
|
65
65
|
cwd
|
|
66
66
|
});
|
|
67
|
-
expect(await
|
|
67
|
+
expect(await repository.resolve(agent.id)).toMatchObject({
|
|
68
68
|
id: agent.id
|
|
69
69
|
});
|
|
70
|
-
expect(await
|
|
70
|
+
expect(await repository.resolve('reviewer')).toMatchObject({
|
|
71
71
|
id: agent.id
|
|
72
72
|
});
|
|
73
|
-
expect(await
|
|
74
|
-
await expect(
|
|
73
|
+
expect(await repository.resolve('view')).toBeNull();
|
|
74
|
+
await expect(repository.create({
|
|
75
75
|
name: 'reviewer',
|
|
76
76
|
cwd
|
|
77
77
|
})).rejects.toMatchObject({
|
|
78
|
-
code: '
|
|
78
|
+
code: 'DURABLE_AGENT_NAME_CONFLICT'
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
|
-
it('rejects missing cwd
|
|
82
|
-
const
|
|
83
|
-
const { root,
|
|
84
|
-
const
|
|
85
|
-
|
|
81
|
+
it('rejects a missing cwd', async ()=>{
|
|
82
|
+
const DurableAgentRepository = await loadStore();
|
|
83
|
+
const { root, dbPath } = fixture();
|
|
84
|
+
const repository = new DurableAgentRepository({
|
|
85
|
+
dbPath
|
|
86
86
|
});
|
|
87
|
-
await expect(
|
|
87
|
+
await expect(repository.create({
|
|
88
88
|
name: 'missing',
|
|
89
89
|
cwd: path.join(root, 'missing')
|
|
90
90
|
})).rejects.toMatchObject({
|
|
91
|
-
code: '
|
|
92
|
-
});
|
|
93
|
-
fs.mkdirSync(path.dirname(filePath), {
|
|
94
|
-
recursive: true
|
|
95
|
-
});
|
|
96
|
-
fs.writeFileSync(filePath, '{bad json', {
|
|
97
|
-
mode: 0o600
|
|
98
|
-
});
|
|
99
|
-
await expect(store.list()).rejects.toMatchObject({
|
|
100
|
-
code: 'PRINT_AGENT_STORE'
|
|
101
|
-
});
|
|
102
|
-
fs.rmSync(filePath);
|
|
103
|
-
const target = path.join(root, 'target.json');
|
|
104
|
-
fs.writeFileSync(target, JSON.stringify({
|
|
105
|
-
version: 1,
|
|
106
|
-
agents: []
|
|
107
|
-
}));
|
|
108
|
-
fs.symlinkSync(target, filePath);
|
|
109
|
-
await expect(store.create({
|
|
110
|
-
name: 'unsafe',
|
|
111
|
-
cwd
|
|
112
|
-
})).rejects.toMatchObject({
|
|
113
|
-
code: 'PRINT_AGENT_STORE'
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
it('recovers an abandoned old mutation lock after a crash', async ()=>{
|
|
117
|
-
const PrintAgentStore = await loadStore();
|
|
118
|
-
const { cwd, filePath } = fixture();
|
|
119
|
-
fs.mkdirSync(path.dirname(filePath), {
|
|
120
|
-
recursive: true
|
|
121
|
-
});
|
|
122
|
-
const lockPath = `${filePath}.lock`;
|
|
123
|
-
fs.mkdirSync(lockPath);
|
|
124
|
-
const old = new Date(Date.now() - 60_000);
|
|
125
|
-
fs.utimesSync(lockPath, old, old);
|
|
126
|
-
const store = new PrintAgentStore({
|
|
127
|
-
filePath,
|
|
128
|
-
mutationLockStaleMs: 10
|
|
129
|
-
});
|
|
130
|
-
await expect(store.create({
|
|
131
|
-
name: 'recovered',
|
|
132
|
-
cwd
|
|
133
|
-
})).resolves.toMatchObject({
|
|
134
|
-
name: 'recovered'
|
|
91
|
+
code: 'DURABLE_AGENT_REPOSITORY'
|
|
135
92
|
});
|
|
136
93
|
});
|
|
137
94
|
});
|
|
138
|
-
describe('
|
|
95
|
+
describe('DurableAgentRepository run ownership', ()=>{
|
|
139
96
|
it('fails fast when another exact owner is live and completes only for its token', async ()=>{
|
|
140
|
-
const
|
|
141
|
-
const { cwd,
|
|
97
|
+
const DurableAgentRepository = await loadStore();
|
|
98
|
+
const { cwd, dbPath } = fixture();
|
|
142
99
|
const live = new Map([
|
|
143
100
|
[
|
|
144
101
|
process.pid,
|
|
@@ -154,27 +111,27 @@ describe('PrintAgentStore run ownership', ()=>{
|
|
|
154
111
|
} : null;
|
|
155
112
|
}
|
|
156
113
|
};
|
|
157
|
-
const
|
|
158
|
-
|
|
114
|
+
const repository = new DurableAgentRepository({
|
|
115
|
+
dbPath,
|
|
159
116
|
processInspector
|
|
160
117
|
});
|
|
161
|
-
const agent = await
|
|
118
|
+
const agent = await repository.create({
|
|
162
119
|
name: 'runner',
|
|
163
120
|
cwd
|
|
164
121
|
});
|
|
165
|
-
const acquired = await
|
|
166
|
-
await expect(
|
|
167
|
-
code: '
|
|
122
|
+
const acquired = await repository.acquireRun(agent.id);
|
|
123
|
+
await expect(repository.acquireRun(agent.id)).rejects.toMatchObject({
|
|
124
|
+
code: 'DURABLE_AGENT_BUSY'
|
|
168
125
|
});
|
|
169
|
-
await expect(
|
|
126
|
+
await expect(repository.completeRun(agent.id, 'wrong-token', {
|
|
170
127
|
status: 'succeeded',
|
|
171
128
|
exitCode: 0,
|
|
172
129
|
summary: 'done',
|
|
173
130
|
sessionHealth: 'healthy'
|
|
174
131
|
})).rejects.toMatchObject({
|
|
175
|
-
code: '
|
|
132
|
+
code: 'DURABLE_AGENT_REPOSITORY'
|
|
176
133
|
});
|
|
177
|
-
const completed = await
|
|
134
|
+
const completed = await repository.completeRun(agent.id, acquired.token, {
|
|
178
135
|
status: 'succeeded',
|
|
179
136
|
exitCode: 0,
|
|
180
137
|
summary: 'done',
|
|
@@ -187,8 +144,8 @@ describe('PrintAgentStore run ownership', ()=>{
|
|
|
187
144
|
});
|
|
188
145
|
});
|
|
189
146
|
it('retains busy for a live provider then recovers a dead run without signaling it', async ()=>{
|
|
190
|
-
const
|
|
191
|
-
const { cwd,
|
|
147
|
+
const DurableAgentRepository = await loadStore();
|
|
148
|
+
const { cwd, dbPath } = fixture();
|
|
192
149
|
const live = new Map([
|
|
193
150
|
[
|
|
194
151
|
process.pid,
|
|
@@ -208,8 +165,8 @@ describe('PrintAgentStore run ownership', ()=>{
|
|
|
208
165
|
} : null;
|
|
209
166
|
}
|
|
210
167
|
};
|
|
211
|
-
const first = new
|
|
212
|
-
|
|
168
|
+
const first = new DurableAgentRepository({
|
|
169
|
+
dbPath,
|
|
213
170
|
processInspector
|
|
214
171
|
});
|
|
215
172
|
const agent = await first.create({
|
|
@@ -223,7 +180,7 @@ describe('PrintAgentStore run ownership', ()=>{
|
|
|
223
180
|
});
|
|
224
181
|
live.delete(process.pid);
|
|
225
182
|
await expect(first.acquireRun(agent.id)).rejects.toMatchObject({
|
|
226
|
-
code: '
|
|
183
|
+
code: 'DURABLE_AGENT_BUSY'
|
|
227
184
|
});
|
|
228
185
|
live.delete(4242);
|
|
229
186
|
live.set(process.pid, 'replacement-owner-start');
|
|
@@ -241,17 +198,17 @@ describe('PrintAgentStore run ownership', ()=>{
|
|
|
241
198
|
sessionHealth: 'unknown'
|
|
242
199
|
});
|
|
243
200
|
});
|
|
244
|
-
it('reconciles an
|
|
245
|
-
const
|
|
246
|
-
const {
|
|
201
|
+
it('reconciles an interrupted run to degraded during list', async ()=>{
|
|
202
|
+
const DurableAgentRepository = await loadStore();
|
|
203
|
+
const { cwd, dbPath } = fixture();
|
|
247
204
|
const live = new Map([
|
|
248
205
|
[
|
|
249
206
|
process.pid,
|
|
250
207
|
'owner-start'
|
|
251
208
|
]
|
|
252
209
|
]);
|
|
253
|
-
const
|
|
254
|
-
|
|
210
|
+
const repository = new DurableAgentRepository({
|
|
211
|
+
dbPath,
|
|
255
212
|
incompleteLockGraceMs: 10,
|
|
256
213
|
processInspector: {
|
|
257
214
|
getIdentity: (pid)=>{
|
|
@@ -263,17 +220,13 @@ describe('PrintAgentStore run ownership', ()=>{
|
|
|
263
220
|
}
|
|
264
221
|
}
|
|
265
222
|
});
|
|
266
|
-
const agent = await
|
|
223
|
+
const agent = await repository.create({
|
|
267
224
|
name: 'crashed',
|
|
268
225
|
cwd
|
|
269
226
|
});
|
|
270
|
-
await
|
|
271
|
-
const lockPath = path.join(root, 'state', 'print-agent-locks', `${agent.id}.lock`);
|
|
272
|
-
fs.unlinkSync(path.join(lockPath, 'owner.json'));
|
|
273
|
-
const old = new Date(Date.now() - 1000);
|
|
274
|
-
fs.utimesSync(lockPath, old, old);
|
|
227
|
+
await repository.acquireRun(agent.id);
|
|
275
228
|
live.clear();
|
|
276
|
-
const listed = await
|
|
229
|
+
const listed = await repository.list();
|
|
277
230
|
expect(listed[0]).toMatchObject({
|
|
278
231
|
state: 'degraded',
|
|
279
232
|
sessionHealth: 'unknown',
|
|
@@ -284,12 +237,12 @@ describe('PrintAgentStore run ownership', ()=>{
|
|
|
284
237
|
});
|
|
285
238
|
});
|
|
286
239
|
it('rejects send acquisition when the bound cwd is replaced by a symlink', async ()=>{
|
|
287
|
-
const
|
|
288
|
-
const { root, cwd,
|
|
289
|
-
const
|
|
290
|
-
|
|
240
|
+
const DurableAgentRepository = await loadStore();
|
|
241
|
+
const { root, cwd, dbPath } = fixture();
|
|
242
|
+
const repository = new DurableAgentRepository({
|
|
243
|
+
dbPath
|
|
291
244
|
});
|
|
292
|
-
const agent = await
|
|
245
|
+
const agent = await repository.create({
|
|
293
246
|
name: 'bound',
|
|
294
247
|
cwd
|
|
295
248
|
});
|
|
@@ -298,10 +251,10 @@ describe('PrintAgentStore run ownership', ()=>{
|
|
|
298
251
|
fs.renameSync(cwd, moved);
|
|
299
252
|
fs.mkdirSync(other);
|
|
300
253
|
fs.symlinkSync(other, cwd);
|
|
301
|
-
await expect(
|
|
302
|
-
code: '
|
|
254
|
+
await expect(repository.acquireRun(agent.id)).rejects.toMatchObject({
|
|
255
|
+
code: 'DURABLE_AGENT_REPOSITORY'
|
|
303
256
|
});
|
|
304
257
|
});
|
|
305
258
|
});
|
|
306
259
|
|
|
307
|
-
//# sourceMappingURL=
|
|
260
|
+
//# sourceMappingURL=DurableAgentRepository.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/print/DurableAgentRepository.test.ts"],"sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport { afterEach, describe, expect, it } from 'vitest';\n\nconst tempDirs: string[] = [];\n\nafterEach(() => {\n for (const dir of tempDirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true });\n});\n\nasync function loadStore(): Promise<any> {\n const api = await import('../../index.js') as Record<string, unknown>;\n expect(api).toHaveProperty('DurableAgentRepository');\n return api.DurableAgentRepository;\n}\n\nfunction fixture(): { root: string; cwd: string; dbPath: string } {\n const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-store-'));\n tempDirs.push(root);\n const cwd = path.join(root, 'project');\n fs.mkdirSync(cwd);\n return { root, cwd, dbPath: path.join(root, 'state', 'agents.db') };\n}\n\ndescribe('DurableAgentRepository create/list/resolve', () => {\n it('creates distinct durable identities with a canonical cwd and lists them', async () => {\n const DurableAgentRepository = await loadStore();\n const { cwd, dbPath } = fixture();\n const repository = new DurableAgentRepository({ dbPath, now: () => new Date('2026-08-07T09:00:00Z') });\n\n const agent = await repository.create({ name: 'reviewer', cwd });\n\n expect(agent).toMatchObject({\n name: 'reviewer',\n provider: 'claude',\n mode: 'durable',\n cwd: fs.realpathSync(cwd),\n state: 'ready',\n sessionHealth: 'uninitialized',\n activeRun: null,\n });\n expect(agent.id).toMatch(/^[0-9a-f-]{36}$/);\n expect(agent.providerSessionId).toMatch(/^[0-9a-f-]{36}$/);\n expect(agent.id).not.toBe(agent.providerSessionId);\n expect(await repository.list()).toEqual([agent]);\n expect(fs.existsSync(dbPath)).toBe(true);\n });\n\n it('resolves exact ids and names and rejects duplicate names', async () => {\n const DurableAgentRepository = await loadStore();\n const { cwd, dbPath } = fixture();\n const repository = new DurableAgentRepository({ dbPath });\n const agent = await repository.create({ name: 'Reviewer', cwd });\n\n expect(await repository.resolve(agent.id)).toMatchObject({ id: agent.id });\n expect(await repository.resolve('reviewer')).toMatchObject({ id: agent.id });\n expect(await repository.resolve('view')).toBeNull();\n await expect(repository.create({ name: 'reviewer', cwd })).rejects.toMatchObject({\n code: 'DURABLE_AGENT_NAME_CONFLICT',\n });\n });\n\n it('rejects a missing cwd', async () => {\n const DurableAgentRepository = await loadStore();\n const { root, dbPath } = fixture();\n const repository = new DurableAgentRepository({ dbPath });\n\n await expect(repository.create({ name: 'missing', cwd: path.join(root, 'missing') }))\n .rejects.toMatchObject({ code: 'DURABLE_AGENT_REPOSITORY' });\n });\n});\n\ndescribe('DurableAgentRepository run ownership', () => {\n it('fails fast when another exact owner is live and completes only for its token', async () => {\n const DurableAgentRepository = await loadStore();\n const { cwd, dbPath } = fixture();\n const live = new Map<number, string>([[process.pid, 'owner-start']]);\n const processInspector = { getIdentity: (pid: number) => {\n const startedAt = live.get(pid);\n return startedAt ? { pid, startedAt } : null;\n } };\n const repository = new DurableAgentRepository({ dbPath, processInspector });\n const agent = await repository.create({ name: 'runner', cwd });\n\n const acquired = await repository.acquireRun(agent.id);\n await expect(repository.acquireRun(agent.id)).rejects.toMatchObject({ code: 'DURABLE_AGENT_BUSY' });\n await expect(repository.completeRun(agent.id, 'wrong-token', {\n status: 'succeeded', exitCode: 0, summary: 'done', sessionHealth: 'healthy',\n })).rejects.toMatchObject({ code: 'DURABLE_AGENT_REPOSITORY' });\n\n const completed = await repository.completeRun(agent.id, acquired.token, {\n status: 'succeeded', exitCode: 0, summary: 'done', sessionHealth: 'healthy',\n });\n expect(completed).toMatchObject({ state: 'ready', sessionHealth: 'healthy', activeRun: null });\n });\n\n it('retains busy for a live provider then recovers a dead run without signaling it', async () => {\n const DurableAgentRepository = await loadStore();\n const { cwd, dbPath } = fixture();\n const live = new Map<number, string>([[process.pid, 'owner-start'], [4242, 'provider-start']]);\n const processInspector = { getIdentity: (pid: number) => {\n const startedAt = live.get(pid);\n return startedAt ? { pid, startedAt } : null;\n } };\n const first = new DurableAgentRepository({ dbPath, processInspector });\n const agent = await first.create({ name: 'recoverable', cwd });\n const run = await first.acquireRun(agent.id);\n await first.recordProviderProcess(agent.id, run.token, { pid: 4242, startedAt: 'provider-start' });\n\n live.delete(process.pid);\n await expect(first.acquireRun(agent.id)).rejects.toMatchObject({ code: 'DURABLE_AGENT_BUSY' });\n\n live.delete(4242);\n live.set(process.pid, 'replacement-owner-start');\n const recovered = await first.acquireRun(agent.id);\n expect(recovered.agent).toMatchObject({\n state: 'running',\n lastResult: { status: 'interrupted' },\n });\n await first.completeRun(agent.id, recovered.token, {\n status: 'failed', exitCode: 1, summary: 'failed', sessionHealth: 'unknown',\n });\n });\n\n it('reconciles an interrupted run to degraded during list', async () => {\n const DurableAgentRepository = await loadStore();\n const { cwd, dbPath } = fixture();\n const live = new Map<number, string>([[process.pid, 'owner-start']]);\n const repository = new DurableAgentRepository({ dbPath, incompleteLockGraceMs: 10, processInspector: {\n getIdentity: (pid: number) => {\n const startedAt = live.get(pid);\n return startedAt ? { pid, startedAt } : null;\n },\n } });\n const agent = await repository.create({ name: 'crashed', cwd });\n await repository.acquireRun(agent.id);\n live.clear();\n\n const listed = await repository.list();\n\n expect(listed[0]).toMatchObject({\n state: 'degraded',\n sessionHealth: 'unknown',\n activeRun: null,\n lastResult: { status: 'interrupted' },\n });\n });\n\n it('rejects send acquisition when the bound cwd is replaced by a symlink', async () => {\n const DurableAgentRepository = await loadStore();\n const { root, cwd, dbPath } = fixture();\n const repository = new DurableAgentRepository({ dbPath });\n const agent = await repository.create({ name: 'bound', cwd });\n const moved = path.join(root, 'moved-project');\n const other = path.join(root, 'other-project');\n fs.renameSync(cwd, moved);\n fs.mkdirSync(other);\n fs.symlinkSync(other, cwd);\n\n await expect(repository.acquireRun(agent.id)).rejects.toMatchObject({ code: 'DURABLE_AGENT_REPOSITORY' });\n });\n});\n"],"names":["fs","os","path","afterEach","describe","expect","it","tempDirs","dir","splice","rmSync","recursive","force","loadStore","api","toHaveProperty","DurableAgentRepository","fixture","root","mkdtempSync","join","tmpdir","push","cwd","mkdirSync","dbPath","repository","now","Date","agent","create","name","toMatchObject","provider","mode","realpathSync","state","sessionHealth","activeRun","id","toMatch","providerSessionId","not","toBe","list","toEqual","existsSync","resolve","toBeNull","rejects","code","live","Map","process","pid","processInspector","getIdentity","startedAt","get","acquired","acquireRun","completeRun","status","exitCode","summary","completed","token","first","run","recordProviderProcess","delete","set","recovered","lastResult","incompleteLockGraceMs","clear","listed","moved","other","renameSync","symlinkSync"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import Database from 'better-sqlite3';
|
|
4
5
|
import { AgentRegistry, RenameNotFoundError, RenameConflictError } from '../../utils/AgentRegistry.js';
|
|
5
6
|
function makeEntry(over = {}) {
|
|
6
7
|
return {
|
|
@@ -12,6 +13,7 @@ function makeEntry(over = {}) {
|
|
|
12
13
|
startedAt: '2026-05-30T00:00:00.000Z',
|
|
13
14
|
sessionId: 'sid-1',
|
|
14
15
|
sessionFilePath: '/tmp/session.jsonl',
|
|
16
|
+
pinned: false,
|
|
15
17
|
...over
|
|
16
18
|
};
|
|
17
19
|
}
|
|
@@ -228,6 +230,71 @@ describe('AgentRegistry', ()=>{
|
|
|
228
230
|
expect(registry.lookup('a')?.name).toBe('a');
|
|
229
231
|
});
|
|
230
232
|
});
|
|
233
|
+
describe('pinning', ()=>{
|
|
234
|
+
it('defaults new rows to unpinned and toggles the persisted state', ()=>{
|
|
235
|
+
registry.register(makeEntry());
|
|
236
|
+
expect(registry.lookup('agent1')?.pinned).toBe(false);
|
|
237
|
+
expect(registry.togglePin('claude', process.pid)).toBe(true);
|
|
238
|
+
expect(registry.lookup('agent1')?.pinned).toBe(true);
|
|
239
|
+
expect(registry.togglePin('claude', process.pid)).toBe(false);
|
|
240
|
+
expect(registry.lookup('agent1')?.pinned).toBe(false);
|
|
241
|
+
});
|
|
242
|
+
it('updates existing recency when toggled', ()=>{
|
|
243
|
+
let now = new Date('2026-08-16T10:00:00.000Z');
|
|
244
|
+
const clocked = new AgentRegistry(regPath, {
|
|
245
|
+
now: ()=>now
|
|
246
|
+
});
|
|
247
|
+
clocked.register(makeEntry());
|
|
248
|
+
now = new Date('2026-08-16T10:01:00.000Z');
|
|
249
|
+
clocked.togglePin('claude', process.pid);
|
|
250
|
+
expect(clocked.lookup('agent1')?.updatedAt).toBe(now.toISOString());
|
|
251
|
+
const db = new Database(regPath.replace(/\.json$/, '.db'), {
|
|
252
|
+
readonly: true
|
|
253
|
+
});
|
|
254
|
+
const row = db.prepare('SELECT updated_at FROM agents WHERE type = ? AND pid = ?').get('claude', process.pid);
|
|
255
|
+
db.close();
|
|
256
|
+
expect(row.updated_at).toBe(now.toISOString());
|
|
257
|
+
});
|
|
258
|
+
it('returns null when the process row has disappeared', ()=>{
|
|
259
|
+
expect(registry.togglePin('claude', 999999)).toBeNull();
|
|
260
|
+
});
|
|
261
|
+
it('preserves a pin when poll registration updates the row', ()=>{
|
|
262
|
+
registry.register(makeEntry({
|
|
263
|
+
sessionId: 'before'
|
|
264
|
+
}));
|
|
265
|
+
registry.togglePin('claude', process.pid);
|
|
266
|
+
registry.register(makeEntry({
|
|
267
|
+
sessionId: 'after'
|
|
268
|
+
}));
|
|
269
|
+
expect(registry.lookup('agent1')).toMatchObject({
|
|
270
|
+
sessionId: 'after',
|
|
271
|
+
pinned: true
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
it('preserves a pin through rename', ()=>{
|
|
275
|
+
registry.register(makeEntry({
|
|
276
|
+
name: 'before'
|
|
277
|
+
}));
|
|
278
|
+
registry.togglePin('claude', process.pid);
|
|
279
|
+
registry.rename('before', 'after');
|
|
280
|
+
expect(registry.lookup('after')?.pinned).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
it('removes the pin with a pruned process row', ()=>{
|
|
283
|
+
registry.register(makeEntry({
|
|
284
|
+
pid: 999999
|
|
285
|
+
}));
|
|
286
|
+
registry.togglePin('claude', 999999);
|
|
287
|
+
registry.prune();
|
|
288
|
+
expect(registry.lookup('agent1')).toBeNull();
|
|
289
|
+
});
|
|
290
|
+
it('reports a clear error when a readonly registry toggles a pin', ()=>{
|
|
291
|
+
registry.register(makeEntry());
|
|
292
|
+
const readonlyRegistry = new AgentRegistry(regPath, {
|
|
293
|
+
readonly: true
|
|
294
|
+
});
|
|
295
|
+
expect(()=>readonlyRegistry.togglePin('claude', process.pid)).toThrow(/readonly/i);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
231
298
|
describe('list', ()=>{
|
|
232
299
|
it('returns empty array when database does not contain entries', ()=>{
|
|
233
300
|
expect(registry.list()).toEqual([]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/__tests__/utils/AgentRegistry.test.ts"],"sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport { AgentRegistry, RenameNotFoundError, RenameConflictError, type RegistryEntry } from '../../utils/AgentRegistry.js';\n\nfunction makeEntry(over: Partial<RegistryEntry> = {}): RegistryEntry {\n return {\n name: 'agent1',\n type: 'claude',\n pid: process.pid,\n tmuxSession: 'agent1',\n cwd: '/tmp',\n startedAt: '2026-05-30T00:00:00.000Z',\n sessionId: 'sid-1',\n sessionFilePath: '/tmp/session.jsonl',\n ...over,\n };\n}\n\ndescribe('AgentRegistry', () => {\n let tmpDir: string;\n let regPath: string;\n let registry: AgentRegistry;\n\n beforeEach(() => {\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-registry-'));\n regPath = path.join(tmpDir, 'nested', 'agents.json');\n registry = new AgentRegistry(regPath);\n });\n\n afterEach(() => {\n vi.restoreAllMocks();\n fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n describe('register', () => {\n it('creates the SQLite database and parent directory if missing', () => {\n registry.register(makeEntry());\n expect(fs.existsSync(regPath.replace(/\\.json$/, '.db'))).toBe(true);\n expect(registry.list()[0].name).toBe('agent1');\n });\n\n it('appends a new entry when name is unique', () => {\n registry.register(makeEntry({ name: 'a' }));\n registry.register(makeEntry({ name: 'b', pid: process.ppid }));\n expect(registry.list()).toHaveLength(2);\n });\n\n it('upserts in place when type and pid already exist', () => {\n registry.register(makeEntry({ name: 'a', pid: process.pid }));\n registry.register(makeEntry({ name: 'fallback', pid: process.pid, tmuxSession: '' }));\n const all = registry.list();\n expect(all).toHaveLength(1);\n expect(all[0].pid).toBe(process.pid);\n expect(all[0].name).toBe('a');\n });\n\n it('does not write through the legacy fixed .tmp path', () => {\n registry.register(makeEntry());\n expect(fs.existsSync(`${regPath}.tmp`)).toBe(false);\n });\n\n it('persists session fields', () => {\n registry.register(makeEntry({ sessionId: 'sid-xyz', sessionFilePath: '/foo/bar.jsonl' }));\n const saved = registry.list()[0];\n expect(saved.sessionId).toBe('sid-xyz');\n expect(saved.sessionFilePath).toBe('/foo/bar.jsonl');\n });\n\n it('preserves existing tmuxSession when incoming is empty string', () => {\n registry.register(makeEntry({ name: 'a', tmuxSession: 'pinned' }));\n registry.register(makeEntry({ name: 'fallback', tmuxSession: '', pid: process.pid }));\n const saved = registry.lookup('a');\n expect(saved?.tmuxSession).toBe('pinned');\n expect(saved?.pid).toBe(process.pid);\n });\n\n it('lets a managed start entry replace a generated fallback for the same pid', () => {\n registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, tmuxSession: '' }));\n registry.register(makeEntry({ name: 'custom-name', tmuxSession: 'custom-name' }));\n expect(registry.lookup('custom-name')?.tmuxSession).toBe('custom-name');\n expect(registry.lookup(`ai-devkit-${process.pid}`)).toBeNull();\n expect(registry.list()).toHaveLength(1);\n });\n\n it('preserves an existing name conflict when its probe fails with EPERM', () => {\n registry.register(makeEntry({ name: 'claimed-name', pid: process.pid }));\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });\n });\n\n expect(() => registry.register(makeEntry({\n name: 'claimed-name',\n pid: process.pid + 1,\n }))).toThrow();\n expect(registry.lookup('claimed-name')?.pid).toBe(process.pid);\n });\n });\n\n describe('registerBatch', () => {\n it('is a no-op on empty array', () => {\n registry.registerBatch([]);\n expect(fs.existsSync(regPath)).toBe(false);\n });\n\n it('upserts multiple entries in a single batch', () => {\n registry.registerBatch([\n makeEntry({ name: 'a' }),\n makeEntry({ name: 'b', pid: process.pid + 1 }),\n makeEntry({ name: 'c', pid: process.pid + 2 }),\n ]);\n expect(registry.list()).toHaveLength(3);\n });\n\n it('applies the tmuxSession merge per entry', () => {\n registry.register(makeEntry({ name: 'a', tmuxSession: 'pinned' }));\n registry.registerBatch([\n makeEntry({ name: 'fallback', tmuxSession: '', pid: process.pid }),\n makeEntry({ name: 'b', tmuxSession: '', pid: process.pid + 1 }),\n ]);\n expect(registry.lookup('a')?.tmuxSession).toBe('pinned');\n expect(registry.lookup('a')?.pid).toBe(process.pid);\n expect(registry.lookup('b')?.tmuxSession).toBe('');\n });\n\n it('handles concurrent registry instances without duplicate pid rows', () => {\n const other = new AgentRegistry(regPath);\n registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, tmuxSession: '' }));\n other.register(makeEntry({ name: 'custom-name', tmuxSession: 'custom-name' }));\n registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, tmuxSession: '' }));\n\n expect(registry.list()).toHaveLength(1);\n expect(registry.lookup('custom-name')?.pid).toBe(process.pid);\n });\n\n it('cleans up a cross-type row when its pid has been reused', () => {\n registry.register(makeEntry({ name: 'old-claude', type: 'claude', pid: process.pid }));\n\n registry.register(makeEntry({\n name: 'new-codex',\n type: 'codex',\n pid: process.pid,\n tmuxSession: '',\n }));\n\n expect(registry.lookup('old-claude')).toBeNull();\n expect(registry.lookup('new-codex')).toMatchObject({ type: 'codex', pid: process.pid });\n expect(registry.list()).toHaveLength(1);\n });\n\n it('rolls back the whole batch when a live name conflict rejects one entry', () => {\n registry.register(makeEntry({ name: 'taken', pid: process.pid }));\n\n expect(() => registry.registerBatch([\n makeEntry({ name: 'fresh', pid: 999998 }),\n makeEntry({ name: 'taken', type: 'codex', pid: 999997 }),\n ])).toThrow(/UNIQUE constraint failed/);\n\n expect(registry.lookup('fresh')).toBeNull();\n expect(registry.lookup('taken')?.pid).toBe(process.pid);\n });\n });\n\n describe('lookup', () => {\n it('returns null when name not found', () => {\n expect(registry.lookup('missing')).toBeNull();\n });\n\n it('returns the entry when name matches', () => {\n registry.register(makeEntry({ name: 'a' }));\n expect(registry.lookup('a')?.name).toBe('a');\n });\n });\n\n describe('list', () => {\n it('returns empty array when database does not contain entries', () => {\n expect(registry.list()).toEqual([]);\n });\n\n it('ignores existing legacy agents.json entries', () => {\n const legacyEntry = makeEntry({ name: 'legacy', tmuxSession: 'legacy' });\n fs.mkdirSync(path.dirname(regPath), { recursive: true });\n fs.writeFileSync(regPath, JSON.stringify({ entries: [legacyEntry] }), 'utf8');\n\n const legacyRegistry = new AgentRegistry(regPath);\n\n expect(legacyRegistry.lookup('legacy')).toBeNull();\n expect(legacyRegistry.list()).toEqual([]);\n expect(fs.existsSync(regPath.replace(/\\.json$/, '.db'))).toBe(true);\n });\n });\n\n describe('isAlive', () => {\n it('returns true for the current process', () => {\n expect(registry.isAlive(makeEntry({ pid: process.pid }))).toBe(true);\n });\n\n it('returns false for a PID that does not exist', () => {\n expect(registry.isAlive(makeEntry({ pid: 999999 }))).toBe(false);\n });\n\n it('returns true when the process probe is forbidden with EPERM', () => {\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });\n });\n\n expect(registry.isAlive(makeEntry())).toBe(true);\n });\n\n it('returns false when the process probe reports ESRCH', () => {\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('no such process'), { code: 'ESRCH' });\n });\n\n expect(registry.isAlive(makeEntry())).toBe(false);\n });\n\n it('returns true when the process probe fails without a definitive error code', () => {\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw new Error('indeterminate probe failure');\n });\n\n expect(registry.isAlive(makeEntry())).toBe(true);\n });\n });\n\n describe('prune', () => {\n it('removes entries whose PIDs are dead', () => {\n registry.register(makeEntry({ name: 'alive', pid: process.pid }));\n registry.register(makeEntry({ name: 'dead', pid: 999999 }));\n registry.prune();\n const remaining = registry.list();\n expect(remaining).toHaveLength(1);\n expect(remaining[0].name).toBe('alive');\n });\n\n it('is a no-op when all entries are alive', () => {\n registry.register(makeEntry({ pid: process.pid }));\n const before = registry.list();\n registry.prune();\n const after = registry.list();\n expect(after).toEqual(before);\n });\n\n it('preserves entries when liveness probing fails with EPERM', () => {\n registry.register(makeEntry({ name: 'custom-name', tmuxSession: 'tmux-custom' }));\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });\n });\n\n registry.prune();\n\n expect(registry.lookup('custom-name')).toMatchObject({\n name: 'custom-name',\n tmuxSession: 'tmux-custom',\n });\n });\n\n it('removes entries when liveness probing fails with ESRCH', () => {\n registry.register(makeEntry({ name: 'dead' }));\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('no such process'), { code: 'ESRCH' });\n });\n\n registry.prune();\n\n expect(registry.lookup('dead')).toBeNull();\n });\n\n it('does nothing when file is missing', () => {\n expect(() => registry.prune()).not.toThrow();\n });\n\n it('keeps forced prune available before the passive cadence is due', () => {\n let nowMs = Date.parse('2026-08-14T10:00:00.000Z');\n const clocked = new AgentRegistry(regPath, {\n now: () => new Date(nowMs),\n pruneIntervalMs: 30_000,\n });\n clocked.register(makeEntry({ name: 'forced', pid: process.pid }));\n const alive = vi.spyOn(clocked, 'isAlive').mockReturnValue(true);\n clocked.pruneIfDue();\n alive.mockReturnValue(false);\n nowMs += 1;\n\n clocked.prune();\n\n expect(alive).toHaveBeenCalledTimes(2);\n expect(clocked.lookup('forced')).toBeNull();\n });\n });\n\n describe('default()', () => {\n it('returns a singleton instance', () => {\n expect(AgentRegistry.default()).toBe(AgentRegistry.default());\n });\n });\n\n describe('rename', () => {\n it('updates the name of an existing entry', () => {\n registry.register(makeEntry({ name: 'old-name', pid: process.pid }));\n registry.rename('old-name', 'new-name');\n expect(registry.lookup('new-name')?.name).toBe('new-name');\n expect(registry.lookup('old-name')).toBeNull();\n });\n\n it('preserves all other fields on the renamed entry', () => {\n registry.register(makeEntry({ name: 'old-name', pid: process.pid, tmuxSession: 'old-name', cwd: '/my/cwd' }));\n registry.rename('old-name', 'new-name');\n const entry = registry.lookup('new-name');\n expect(entry?.tmuxSession).toBe('old-name');\n expect(entry?.cwd).toBe('/my/cwd');\n expect(entry?.pid).toBe(process.pid);\n });\n\n it('throws RenameNotFoundError when current name does not exist', () => {\n expect(() => registry.rename('ghost', 'new-name')).toThrow(RenameNotFoundError);\n });\n\n it('throws RenameConflictError when new name is already in use by a live entry', () => {\n registry.register(makeEntry({ name: 'agent-a', pid: process.pid }));\n registry.register(makeEntry({ name: 'agent-b', pid: process.ppid }));\n expect(() => registry.rename('agent-a', 'agent-b')).toThrow(RenameConflictError);\n });\n\n it('throws RenameConflictError when the conflicting entry probe fails with EPERM', () => {\n registry.register(makeEntry({ name: 'agent-a', pid: process.pid }));\n registry.register(makeEntry({ name: 'agent-b', pid: process.ppid }));\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });\n });\n\n expect(() => registry.rename('agent-a', 'agent-b')).toThrow(RenameConflictError);\n expect(registry.lookup('agent-b')?.pid).toBe(process.ppid);\n });\n\n it('succeeds when new name exists only as a stale (dead) entry', () => {\n registry.register(makeEntry({ name: 'agent-a', pid: process.pid }));\n registry.register(makeEntry({ name: 'agent-b', pid: 999999 }));\n expect(() => registry.rename('agent-a', 'agent-b')).not.toThrow();\n expect(registry.lookup('agent-b')?.pid).toBe(process.pid);\n });\n\n it('does not create the legacy fixed .tmp path on rename', () => {\n registry.register(makeEntry({ name: 'old-name', pid: process.pid }));\n registry.rename('old-name', 'new-name');\n expect(fs.existsSync(`${regPath}.tmp`)).toBe(false);\n });\n });\n});\n"],"names":["fs","os","path","AgentRegistry","RenameNotFoundError","RenameConflictError","makeEntry","over","name","type","pid","process","tmuxSession","cwd","startedAt","sessionId","sessionFilePath","describe","tmpDir","regPath","registry","beforeEach","mkdtempSync","join","tmpdir","afterEach","vi","restoreAllMocks","rmSync","recursive","force","it","register","expect","existsSync","replace","toBe","list","ppid","toHaveLength","all","saved","lookup","toBeNull","spyOn","mockImplementation","Object","assign","Error","code","toThrow","registerBatch","other","toMatchObject","toEqual","legacyEntry","mkdirSync","dirname","writeFileSync","JSON","stringify","entries","legacyRegistry","isAlive","prune","remaining","before","after","not","nowMs","Date","parse","clocked","now","pruneIntervalMs","alive","mockReturnValue","pruneIfDue","toHaveBeenCalledTimes","default","rename","entry"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/utils/AgentRegistry.test.ts"],"sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport Database from 'better-sqlite3';\nimport { AgentRegistry, RenameNotFoundError, RenameConflictError, type RegistryEntry } from '../../utils/AgentRegistry.js';\n\nfunction makeEntry(over: Partial<RegistryEntry> = {}): RegistryEntry {\n return {\n name: 'agent1',\n type: 'claude',\n pid: process.pid,\n tmuxSession: 'agent1',\n cwd: '/tmp',\n startedAt: '2026-05-30T00:00:00.000Z',\n sessionId: 'sid-1',\n sessionFilePath: '/tmp/session.jsonl',\n pinned: false,\n ...over,\n };\n}\n\ndescribe('AgentRegistry', () => {\n let tmpDir: string;\n let regPath: string;\n let registry: AgentRegistry;\n\n beforeEach(() => {\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-registry-'));\n regPath = path.join(tmpDir, 'nested', 'agents.json');\n registry = new AgentRegistry(regPath);\n });\n\n afterEach(() => {\n vi.restoreAllMocks();\n fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n describe('register', () => {\n it('creates the SQLite database and parent directory if missing', () => {\n registry.register(makeEntry());\n expect(fs.existsSync(regPath.replace(/\\.json$/, '.db'))).toBe(true);\n expect(registry.list()[0].name).toBe('agent1');\n });\n\n it('appends a new entry when name is unique', () => {\n registry.register(makeEntry({ name: 'a' }));\n registry.register(makeEntry({ name: 'b', pid: process.ppid }));\n expect(registry.list()).toHaveLength(2);\n });\n\n it('upserts in place when type and pid already exist', () => {\n registry.register(makeEntry({ name: 'a', pid: process.pid }));\n registry.register(makeEntry({ name: 'fallback', pid: process.pid, tmuxSession: '' }));\n const all = registry.list();\n expect(all).toHaveLength(1);\n expect(all[0].pid).toBe(process.pid);\n expect(all[0].name).toBe('a');\n });\n\n it('does not write through the legacy fixed .tmp path', () => {\n registry.register(makeEntry());\n expect(fs.existsSync(`${regPath}.tmp`)).toBe(false);\n });\n\n it('persists session fields', () => {\n registry.register(makeEntry({ sessionId: 'sid-xyz', sessionFilePath: '/foo/bar.jsonl' }));\n const saved = registry.list()[0];\n expect(saved.sessionId).toBe('sid-xyz');\n expect(saved.sessionFilePath).toBe('/foo/bar.jsonl');\n });\n\n it('preserves existing tmuxSession when incoming is empty string', () => {\n registry.register(makeEntry({ name: 'a', tmuxSession: 'pinned' }));\n registry.register(makeEntry({ name: 'fallback', tmuxSession: '', pid: process.pid }));\n const saved = registry.lookup('a');\n expect(saved?.tmuxSession).toBe('pinned');\n expect(saved?.pid).toBe(process.pid);\n });\n\n it('lets a managed start entry replace a generated fallback for the same pid', () => {\n registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, tmuxSession: '' }));\n registry.register(makeEntry({ name: 'custom-name', tmuxSession: 'custom-name' }));\n expect(registry.lookup('custom-name')?.tmuxSession).toBe('custom-name');\n expect(registry.lookup(`ai-devkit-${process.pid}`)).toBeNull();\n expect(registry.list()).toHaveLength(1);\n });\n\n it('preserves an existing name conflict when its probe fails with EPERM', () => {\n registry.register(makeEntry({ name: 'claimed-name', pid: process.pid }));\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });\n });\n\n expect(() => registry.register(makeEntry({\n name: 'claimed-name',\n pid: process.pid + 1,\n }))).toThrow();\n expect(registry.lookup('claimed-name')?.pid).toBe(process.pid);\n });\n });\n\n describe('registerBatch', () => {\n it('is a no-op on empty array', () => {\n registry.registerBatch([]);\n expect(fs.existsSync(regPath)).toBe(false);\n });\n\n it('upserts multiple entries in a single batch', () => {\n registry.registerBatch([\n makeEntry({ name: 'a' }),\n makeEntry({ name: 'b', pid: process.pid + 1 }),\n makeEntry({ name: 'c', pid: process.pid + 2 }),\n ]);\n expect(registry.list()).toHaveLength(3);\n });\n\n it('applies the tmuxSession merge per entry', () => {\n registry.register(makeEntry({ name: 'a', tmuxSession: 'pinned' }));\n registry.registerBatch([\n makeEntry({ name: 'fallback', tmuxSession: '', pid: process.pid }),\n makeEntry({ name: 'b', tmuxSession: '', pid: process.pid + 1 }),\n ]);\n expect(registry.lookup('a')?.tmuxSession).toBe('pinned');\n expect(registry.lookup('a')?.pid).toBe(process.pid);\n expect(registry.lookup('b')?.tmuxSession).toBe('');\n });\n\n it('handles concurrent registry instances without duplicate pid rows', () => {\n const other = new AgentRegistry(regPath);\n registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, tmuxSession: '' }));\n other.register(makeEntry({ name: 'custom-name', tmuxSession: 'custom-name' }));\n registry.register(makeEntry({ name: `ai-devkit-${process.pid}`, tmuxSession: '' }));\n\n expect(registry.list()).toHaveLength(1);\n expect(registry.lookup('custom-name')?.pid).toBe(process.pid);\n });\n\n it('cleans up a cross-type row when its pid has been reused', () => {\n registry.register(makeEntry({ name: 'old-claude', type: 'claude', pid: process.pid }));\n\n registry.register(makeEntry({\n name: 'new-codex',\n type: 'codex',\n pid: process.pid,\n tmuxSession: '',\n }));\n\n expect(registry.lookup('old-claude')).toBeNull();\n expect(registry.lookup('new-codex')).toMatchObject({ type: 'codex', pid: process.pid });\n expect(registry.list()).toHaveLength(1);\n });\n\n it('rolls back the whole batch when a live name conflict rejects one entry', () => {\n registry.register(makeEntry({ name: 'taken', pid: process.pid }));\n\n expect(() => registry.registerBatch([\n makeEntry({ name: 'fresh', pid: 999998 }),\n makeEntry({ name: 'taken', type: 'codex', pid: 999997 }),\n ])).toThrow(/UNIQUE constraint failed/);\n\n expect(registry.lookup('fresh')).toBeNull();\n expect(registry.lookup('taken')?.pid).toBe(process.pid);\n });\n });\n\n describe('lookup', () => {\n it('returns null when name not found', () => {\n expect(registry.lookup('missing')).toBeNull();\n });\n\n it('returns the entry when name matches', () => {\n registry.register(makeEntry({ name: 'a' }));\n expect(registry.lookup('a')?.name).toBe('a');\n });\n });\n\n describe('pinning', () => {\n it('defaults new rows to unpinned and toggles the persisted state', () => {\n registry.register(makeEntry());\n\n expect(registry.lookup('agent1')?.pinned).toBe(false);\n expect(registry.togglePin('claude', process.pid)).toBe(true);\n expect(registry.lookup('agent1')?.pinned).toBe(true);\n expect(registry.togglePin('claude', process.pid)).toBe(false);\n expect(registry.lookup('agent1')?.pinned).toBe(false);\n });\n\n it('updates existing recency when toggled', () => {\n let now = new Date('2026-08-16T10:00:00.000Z');\n const clocked = new AgentRegistry(regPath, { now: () => now });\n clocked.register(makeEntry());\n now = new Date('2026-08-16T10:01:00.000Z');\n\n clocked.togglePin('claude', process.pid);\n\n expect(clocked.lookup('agent1')?.updatedAt).toBe(now.toISOString());\n const db = new Database(regPath.replace(/\\.json$/, '.db'), { readonly: true });\n const row = db.prepare('SELECT updated_at FROM agents WHERE type = ? AND pid = ?')\n .get('claude', process.pid) as { updated_at: string };\n db.close();\n expect(row.updated_at).toBe(now.toISOString());\n });\n\n it('returns null when the process row has disappeared', () => {\n expect(registry.togglePin('claude', 999999)).toBeNull();\n });\n\n it('preserves a pin when poll registration updates the row', () => {\n registry.register(makeEntry({ sessionId: 'before' }));\n registry.togglePin('claude', process.pid);\n\n registry.register(makeEntry({ sessionId: 'after' }));\n\n expect(registry.lookup('agent1')).toMatchObject({ sessionId: 'after', pinned: true });\n });\n\n it('preserves a pin through rename', () => {\n registry.register(makeEntry({ name: 'before' }));\n registry.togglePin('claude', process.pid);\n\n registry.rename('before', 'after');\n\n expect(registry.lookup('after')?.pinned).toBe(true);\n });\n\n it('removes the pin with a pruned process row', () => {\n registry.register(makeEntry({ pid: 999999 }));\n registry.togglePin('claude', 999999);\n\n registry.prune();\n\n expect(registry.lookup('agent1')).toBeNull();\n });\n\n it('reports a clear error when a readonly registry toggles a pin', () => {\n registry.register(makeEntry());\n const readonlyRegistry = new AgentRegistry(regPath, { readonly: true });\n\n expect(() => readonlyRegistry.togglePin('claude', process.pid)).toThrow(/readonly/i);\n });\n });\n\n describe('list', () => {\n it('returns empty array when database does not contain entries', () => {\n expect(registry.list()).toEqual([]);\n });\n\n it('ignores existing legacy agents.json entries', () => {\n const legacyEntry = makeEntry({ name: 'legacy', tmuxSession: 'legacy' });\n fs.mkdirSync(path.dirname(regPath), { recursive: true });\n fs.writeFileSync(regPath, JSON.stringify({ entries: [legacyEntry] }), 'utf8');\n\n const legacyRegistry = new AgentRegistry(regPath);\n\n expect(legacyRegistry.lookup('legacy')).toBeNull();\n expect(legacyRegistry.list()).toEqual([]);\n expect(fs.existsSync(regPath.replace(/\\.json$/, '.db'))).toBe(true);\n });\n });\n\n describe('isAlive', () => {\n it('returns true for the current process', () => {\n expect(registry.isAlive(makeEntry({ pid: process.pid }))).toBe(true);\n });\n\n it('returns false for a PID that does not exist', () => {\n expect(registry.isAlive(makeEntry({ pid: 999999 }))).toBe(false);\n });\n\n it('returns true when the process probe is forbidden with EPERM', () => {\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });\n });\n\n expect(registry.isAlive(makeEntry())).toBe(true);\n });\n\n it('returns false when the process probe reports ESRCH', () => {\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('no such process'), { code: 'ESRCH' });\n });\n\n expect(registry.isAlive(makeEntry())).toBe(false);\n });\n\n it('returns true when the process probe fails without a definitive error code', () => {\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw new Error('indeterminate probe failure');\n });\n\n expect(registry.isAlive(makeEntry())).toBe(true);\n });\n });\n\n describe('prune', () => {\n it('removes entries whose PIDs are dead', () => {\n registry.register(makeEntry({ name: 'alive', pid: process.pid }));\n registry.register(makeEntry({ name: 'dead', pid: 999999 }));\n registry.prune();\n const remaining = registry.list();\n expect(remaining).toHaveLength(1);\n expect(remaining[0].name).toBe('alive');\n });\n\n it('is a no-op when all entries are alive', () => {\n registry.register(makeEntry({ pid: process.pid }));\n const before = registry.list();\n registry.prune();\n const after = registry.list();\n expect(after).toEqual(before);\n });\n\n it('preserves entries when liveness probing fails with EPERM', () => {\n registry.register(makeEntry({ name: 'custom-name', tmuxSession: 'tmux-custom' }));\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });\n });\n\n registry.prune();\n\n expect(registry.lookup('custom-name')).toMatchObject({\n name: 'custom-name',\n tmuxSession: 'tmux-custom',\n });\n });\n\n it('removes entries when liveness probing fails with ESRCH', () => {\n registry.register(makeEntry({ name: 'dead' }));\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('no such process'), { code: 'ESRCH' });\n });\n\n registry.prune();\n\n expect(registry.lookup('dead')).toBeNull();\n });\n\n it('does nothing when file is missing', () => {\n expect(() => registry.prune()).not.toThrow();\n });\n\n it('keeps forced prune available before the passive cadence is due', () => {\n let nowMs = Date.parse('2026-08-14T10:00:00.000Z');\n const clocked = new AgentRegistry(regPath, {\n now: () => new Date(nowMs),\n pruneIntervalMs: 30_000,\n });\n clocked.register(makeEntry({ name: 'forced', pid: process.pid }));\n const alive = vi.spyOn(clocked, 'isAlive').mockReturnValue(true);\n clocked.pruneIfDue();\n alive.mockReturnValue(false);\n nowMs += 1;\n\n clocked.prune();\n\n expect(alive).toHaveBeenCalledTimes(2);\n expect(clocked.lookup('forced')).toBeNull();\n });\n });\n\n describe('default()', () => {\n it('returns a singleton instance', () => {\n expect(AgentRegistry.default()).toBe(AgentRegistry.default());\n });\n });\n\n describe('rename', () => {\n it('updates the name of an existing entry', () => {\n registry.register(makeEntry({ name: 'old-name', pid: process.pid }));\n registry.rename('old-name', 'new-name');\n expect(registry.lookup('new-name')?.name).toBe('new-name');\n expect(registry.lookup('old-name')).toBeNull();\n });\n\n it('preserves all other fields on the renamed entry', () => {\n registry.register(makeEntry({ name: 'old-name', pid: process.pid, tmuxSession: 'old-name', cwd: '/my/cwd' }));\n registry.rename('old-name', 'new-name');\n const entry = registry.lookup('new-name');\n expect(entry?.tmuxSession).toBe('old-name');\n expect(entry?.cwd).toBe('/my/cwd');\n expect(entry?.pid).toBe(process.pid);\n });\n\n it('throws RenameNotFoundError when current name does not exist', () => {\n expect(() => registry.rename('ghost', 'new-name')).toThrow(RenameNotFoundError);\n });\n\n it('throws RenameConflictError when new name is already in use by a live entry', () => {\n registry.register(makeEntry({ name: 'agent-a', pid: process.pid }));\n registry.register(makeEntry({ name: 'agent-b', pid: process.ppid }));\n expect(() => registry.rename('agent-a', 'agent-b')).toThrow(RenameConflictError);\n });\n\n it('throws RenameConflictError when the conflicting entry probe fails with EPERM', () => {\n registry.register(makeEntry({ name: 'agent-a', pid: process.pid }));\n registry.register(makeEntry({ name: 'agent-b', pid: process.ppid }));\n vi.spyOn(process, 'kill').mockImplementation(() => {\n throw Object.assign(new Error('operation not permitted'), { code: 'EPERM' });\n });\n\n expect(() => registry.rename('agent-a', 'agent-b')).toThrow(RenameConflictError);\n expect(registry.lookup('agent-b')?.pid).toBe(process.ppid);\n });\n\n it('succeeds when new name exists only as a stale (dead) entry', () => {\n registry.register(makeEntry({ name: 'agent-a', pid: process.pid }));\n registry.register(makeEntry({ name: 'agent-b', pid: 999999 }));\n expect(() => registry.rename('agent-a', 'agent-b')).not.toThrow();\n expect(registry.lookup('agent-b')?.pid).toBe(process.pid);\n });\n\n it('does not create the legacy fixed .tmp path on rename', () => {\n registry.register(makeEntry({ name: 'old-name', pid: process.pid }));\n registry.rename('old-name', 'new-name');\n expect(fs.existsSync(`${regPath}.tmp`)).toBe(false);\n });\n });\n});\n"],"names":["fs","os","path","Database","AgentRegistry","RenameNotFoundError","RenameConflictError","makeEntry","over","name","type","pid","process","tmuxSession","cwd","startedAt","sessionId","sessionFilePath","pinned","describe","tmpDir","regPath","registry","beforeEach","mkdtempSync","join","tmpdir","afterEach","vi","restoreAllMocks","rmSync","recursive","force","it","register","expect","existsSync","replace","toBe","list","ppid","toHaveLength","all","saved","lookup","toBeNull","spyOn","mockImplementation","Object","assign","Error","code","toThrow","registerBatch","other","toMatchObject","togglePin","now","Date","clocked","updatedAt","toISOString","db","readonly","row","prepare","get","close","updated_at","rename","prune","readonlyRegistry","toEqual","legacyEntry","mkdirSync","dirname","writeFileSync","JSON","stringify","entries","legacyRegistry","isAlive","remaining","before","after","not","nowMs","parse","pruneIntervalMs","alive","mockReturnValue","pruneIfDue","toHaveBeenCalledTimes","default","entry"],"mappings":"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"}
|
|
@@ -37,6 +37,8 @@ export interface AgentInfo {
|
|
|
37
37
|
sessionId: string;
|
|
38
38
|
/** Timestamp of last activity */
|
|
39
39
|
lastActive: Date;
|
|
40
|
+
/** Whether the live process is pinned in the agent console */
|
|
41
|
+
pinned?: boolean;
|
|
40
42
|
/** Path to the session JSONL file on disk */
|
|
41
43
|
sessionFilePath?: string;
|
|
42
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/AgentAdapter.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/AgentAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/adapters/AgentAdapter.ts"],"sourcesContent":["/**\n * Agent Adapter Interface\n * \n * Defines the contract for detecting and managing different types of AI agents.\n * Each adapter is responsible for detecting agents of a specific type (e.g., claude).\n */\n\n/**\n * Type of AI agent\n */\nexport type AgentType = 'claude' | 'gemini_cli' | 'grok_cli' | 'codex' | 'opencode' | 'copilot' | 'pi' | 'other';\n\n/**\n * Current status of an agent\n */\nexport enum AgentStatus {\n RUNNING = 'running',\n WAITING = 'waiting',\n IDLE = 'idle',\n UNKNOWN = 'unknown'\n}\n\n/**\n * Information about a detected agent\n */\nexport interface AgentInfo {\n /** Project-based name (e.g., \"ai-devkit\" or \"ai-devkit (merry)\") */\n name: string;\n\n /** Type of agent */\n type: AgentType;\n\n /** Current status */\n status: AgentStatus;\n\n /** Last user prompt from history */\n summary: string;\n\n /** Process ID */\n pid: number;\n\n /** Working directory/project path */\n projectPath: string;\n\n /** Session UUID */\n sessionId: string;\n\n /** Timestamp of last activity */\n lastActive: Date;\n\n /** Path to the session JSONL file on disk */\n sessionFilePath?: string;\n}\n\n/**\n * Information about a running process\n */\nexport interface ProcessInfo {\n /** Process ID */\n pid: number;\n\n /** Parent process ID, populated by process discovery when available */\n ppid?: number;\n\n /** Process command */\n command: string;\n\n /** Working directory */\n cwd: string;\n\n /** Terminal TTY (e.g., \"ttys030\") */\n tty: string;\n\n /** Process start time, populated by process enrichment */\n startTime?: Date;\n}\n\n/**\n * A single message in a conversation\n */\nexport interface ConversationMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n timestamp?: string;\n}\n\n/**\n * A historical session discovered on disk (running or not).\n *\n * Used by `listSessions` to surface enough context for a user to identify\n * a session and resume it via the originating tool's resume command.\n */\nexport interface SessionSummary {\n /** Tool that produced this session */\n type: AgentType;\n\n /**\n * ID accepted by the tool's resume command. Adapters MUST pass this\n * through verbatim — no normalization, no encoding/decoding — so it\n * round-trips into `claude --resume <id>` (and equivalents).\n */\n sessionId: string;\n\n /** Working directory the session was started in (best-known value) */\n cwd: string;\n\n /**\n * Trimmed first user message; empty string if none. Adapters apply\n * the same noise-filter their existing parsers use (skip tool_result\n * blocks, request-interruption notices, system-injected skill\n * content). The CLI table renderer substitutes a placeholder for\n * empty values; JSON output keeps the empty string raw.\n */\n firstUserMessage: string;\n\n /** Last activity timestamp (from session content; falls back to file mtime) */\n lastActive: Date;\n\n /** Session start time (from session content; falls back to file birthtime/mtime) */\n startedAt: Date;\n\n /** Absolute path to the session file on disk (debug/diagnostics) */\n sessionFilePath: string;\n}\n\n/**\n * Filters passed by the CLI to {@link AgentAdapter.listSessions}.\n *\n * The CLI is the source of truth for filter defaults and semantics\n * (e.g. cwd defaults to process.cwd(); --all clears it). Adapters apply\n * the values they receive — they don't invent defaults.\n */\nexport interface ListSessionsOptions {\n /**\n * Filter to sessions whose recorded cwd matches this path using strict\n * equality (no prefix/ancestor matching in v1). Undefined = no cwd\n * filter.\n */\n cwd?: string;\n\n /**\n * Filter to a single tool. Enforced by `AgentManager.listSessions`,\n * which skips adapters whose `type` doesn't match. Adapters MAY\n * ignore this field — by the time their `listSessions` runs, the\n * type filter is already satisfied. Undefined = include every\n * registered adapter.\n */\n type?: AgentType;\n}\n\nexport interface AgentDetectionContext {\n /** One enriched process snapshot shared across this manager refresh. */\n readonly processes: readonly ProcessInfo[];\n}\n\n/**\n * Agent Adapter Interface\n *\n * Implementations must provide detection logic for a specific agent type.\n */\nexport interface AgentAdapter {\n /** Type of agent this adapter handles */\n readonly type: AgentType;\n\n /** Executable basenames required for shared process discovery. */\n readonly processNames?: readonly string[];\n\n /**\n * Detect running agents of this type\n * @returns List of detected agents\n */\n detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]>;\n\n /**\n * Check if this adapter can handle the given process\n * @param processInfo Process information\n * @returns True if this adapter can handle the process\n */\n canHandle(processInfo: ProcessInfo): boolean;\n\n /**\n * Read the full conversation from a session file\n * @param sessionFilePath Path to the session JSONL file\n * @param options.verbose Include tool call/result details\n * @returns Array of conversation messages\n */\n getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[];\n\n /**\n * Enumerate historical sessions for this tool from disk.\n *\n * Applies `opts.cwd` as a strict-equality filter when set. Returns\n * {@link SessionSummary} entries unsorted; sorting and global filters\n * are handled by `AgentManager` and the CLI.\n *\n * @param opts Filter options computed by the CLI\n * @returns Array of sessions discovered on disk\n */\n listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]>;\n}\n"],"names":["AgentStatus"],"mappings":"AAAA;;;;;CAKC,GAED;;CAEC,GAGD;;CAEC,GACD,OAAO,IAAA,AAAKA,qCAAAA;;;;;WAAAA;MAKX"}
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/AgentAdapter.ts"],"sourcesContent":["/**\n * Agent Adapter Interface\n * \n * Defines the contract for detecting and managing different types of AI agents.\n * Each adapter is responsible for detecting agents of a specific type (e.g., claude).\n */\n\n/**\n * Type of AI agent\n */\nexport type AgentType = 'claude' | 'gemini_cli' | 'grok_cli' | 'codex' | 'opencode' | 'copilot' | 'pi' | 'other';\n\n/**\n * Current status of an agent\n */\nexport enum AgentStatus {\n RUNNING = 'running',\n WAITING = 'waiting',\n IDLE = 'idle',\n UNKNOWN = 'unknown'\n}\n\n/**\n * Information about a detected agent\n */\nexport interface AgentInfo {\n /** Project-based name (e.g., \"ai-devkit\" or \"ai-devkit (merry)\") */\n name: string;\n\n /** Type of agent */\n type: AgentType;\n\n /** Current status */\n status: AgentStatus;\n\n /** Last user prompt from history */\n summary: string;\n\n /** Process ID */\n pid: number;\n\n /** Working directory/project path */\n projectPath: string;\n\n /** Session UUID */\n sessionId: string;\n\n /** Timestamp of last activity */\n lastActive: Date;\n\n /** Whether the live process is pinned in the agent console */\n pinned?: boolean;\n\n /** Path to the session JSONL file on disk */\n sessionFilePath?: string;\n}\n\n/**\n * Information about a running process\n */\nexport interface ProcessInfo {\n /** Process ID */\n pid: number;\n\n /** Parent process ID, populated by process discovery when available */\n ppid?: number;\n\n /** Process command */\n command: string;\n\n /** Working directory */\n cwd: string;\n\n /** Terminal TTY (e.g., \"ttys030\") */\n tty: string;\n\n /** Process start time, populated by process enrichment */\n startTime?: Date;\n}\n\n/**\n * A single message in a conversation\n */\nexport interface ConversationMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n timestamp?: string;\n}\n\n/**\n * A historical session discovered on disk (running or not).\n *\n * Used by `listSessions` to surface enough context for a user to identify\n * a session and resume it via the originating tool's resume command.\n */\nexport interface SessionSummary {\n /** Tool that produced this session */\n type: AgentType;\n\n /**\n * ID accepted by the tool's resume command. Adapters MUST pass this\n * through verbatim — no normalization, no encoding/decoding — so it\n * round-trips into `claude --resume <id>` (and equivalents).\n */\n sessionId: string;\n\n /** Working directory the session was started in (best-known value) */\n cwd: string;\n\n /**\n * Trimmed first user message; empty string if none. Adapters apply\n * the same noise-filter their existing parsers use (skip tool_result\n * blocks, request-interruption notices, system-injected skill\n * content). The CLI table renderer substitutes a placeholder for\n * empty values; JSON output keeps the empty string raw.\n */\n firstUserMessage: string;\n\n /** Last activity timestamp (from session content; falls back to file mtime) */\n lastActive: Date;\n\n /** Session start time (from session content; falls back to file birthtime/mtime) */\n startedAt: Date;\n\n /** Absolute path to the session file on disk (debug/diagnostics) */\n sessionFilePath: string;\n}\n\n/**\n * Filters passed by the CLI to {@link AgentAdapter.listSessions}.\n *\n * The CLI is the source of truth for filter defaults and semantics\n * (e.g. cwd defaults to process.cwd(); --all clears it). Adapters apply\n * the values they receive — they don't invent defaults.\n */\nexport interface ListSessionsOptions {\n /**\n * Filter to sessions whose recorded cwd matches this path using strict\n * equality (no prefix/ancestor matching in v1). Undefined = no cwd\n * filter.\n */\n cwd?: string;\n\n /**\n * Filter to a single tool. Enforced by `AgentManager.listSessions`,\n * which skips adapters whose `type` doesn't match. Adapters MAY\n * ignore this field — by the time their `listSessions` runs, the\n * type filter is already satisfied. Undefined = include every\n * registered adapter.\n */\n type?: AgentType;\n}\n\nexport interface AgentDetectionContext {\n /** One enriched process snapshot shared across this manager refresh. */\n readonly processes: readonly ProcessInfo[];\n}\n\n/**\n * Agent Adapter Interface\n *\n * Implementations must provide detection logic for a specific agent type.\n */\nexport interface AgentAdapter {\n /** Type of agent this adapter handles */\n readonly type: AgentType;\n\n /** Executable basenames required for shared process discovery. */\n readonly processNames?: readonly string[];\n\n /**\n * Detect running agents of this type\n * @returns List of detected agents\n */\n detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]>;\n\n /**\n * Check if this adapter can handle the given process\n * @param processInfo Process information\n * @returns True if this adapter can handle the process\n */\n canHandle(processInfo: ProcessInfo): boolean;\n\n /**\n * Read the full conversation from a session file\n * @param sessionFilePath Path to the session JSONL file\n * @param options.verbose Include tool call/result details\n * @returns Array of conversation messages\n */\n getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[];\n\n /**\n * Enumerate historical sessions for this tool from disk.\n *\n * Applies `opts.cwd` as a strict-equality filter when set. Returns\n * {@link SessionSummary} entries unsorted; sorting and global filters\n * are handled by `AgentManager` and the CLI.\n *\n * @param opts Filter options computed by the CLI\n * @returns Array of sessions discovered on disk\n */\n listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]>;\n}\n"],"names":["AgentStatus"],"mappings":"AAAA;;;;;CAKC,GAED;;CAEC,GAGD;;CAEC,GACD,OAAO,IAAA,AAAKA,qCAAAA;;;;;WAAAA;MAKX"}
|