@ai-devkit/agent-manager 0.27.0 → 0.28.1
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/__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/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.map +1 -1
- package/dist/database/connection.js +17 -9
- package/dist/database/connection.js.map +1 -1
- 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 +11 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- 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/adapters/CodexAdapter.ts +14 -11
- package/src/database/connection.ts +15 -9
- 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 +26 -25
- 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
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
-
|
|
6
|
-
const tempDirs: string[] = [];
|
|
7
|
-
|
|
8
|
-
afterEach(() => {
|
|
9
|
-
for (const dir of tempDirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
async function loadStore(): Promise<any> {
|
|
13
|
-
const api = await import('../../index.js') as Record<string, unknown>;
|
|
14
|
-
expect(api).toHaveProperty('PrintAgentStore');
|
|
15
|
-
return api.PrintAgentStore;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function fixture(): { root: string; cwd: string; filePath: string } {
|
|
19
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'print-agent-store-'));
|
|
20
|
-
tempDirs.push(root);
|
|
21
|
-
const cwd = path.join(root, 'project');
|
|
22
|
-
fs.mkdirSync(cwd);
|
|
23
|
-
return { root, cwd, filePath: path.join(root, 'state', 'print-agents.json') };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
describe('PrintAgentStore create/list/resolve', () => {
|
|
27
|
-
it('creates distinct durable identities with a canonical cwd and lists them', async () => {
|
|
28
|
-
const PrintAgentStore = await loadStore();
|
|
29
|
-
const { cwd, filePath } = fixture();
|
|
30
|
-
const store = new PrintAgentStore({ filePath, now: () => new Date('2026-08-07T09:00:00Z') });
|
|
31
|
-
|
|
32
|
-
const agent = await store.create({ name: 'reviewer', cwd });
|
|
33
|
-
|
|
34
|
-
expect(agent).toMatchObject({
|
|
35
|
-
name: 'reviewer',
|
|
36
|
-
provider: 'claude',
|
|
37
|
-
mode: 'print',
|
|
38
|
-
cwd: fs.realpathSync(cwd),
|
|
39
|
-
state: 'ready',
|
|
40
|
-
sessionHealth: 'uninitialized',
|
|
41
|
-
activeRun: null,
|
|
42
|
-
});
|
|
43
|
-
expect(agent.id).toMatch(/^[0-9a-f-]{36}$/);
|
|
44
|
-
expect(agent.providerSessionId).toMatch(/^[0-9a-f-]{36}$/);
|
|
45
|
-
expect(agent.id).not.toBe(agent.providerSessionId);
|
|
46
|
-
expect(await store.list()).toEqual([agent]);
|
|
47
|
-
expect(fs.statSync(filePath).mode & 0o777).toBe(0o600);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('resolves exact ids and names and rejects duplicate names', async () => {
|
|
51
|
-
const PrintAgentStore = await loadStore();
|
|
52
|
-
const { cwd, filePath } = fixture();
|
|
53
|
-
const store = new PrintAgentStore({ filePath });
|
|
54
|
-
const agent = await store.create({ name: 'Reviewer', cwd });
|
|
55
|
-
|
|
56
|
-
expect(await store.resolve(agent.id)).toMatchObject({ id: agent.id });
|
|
57
|
-
expect(await store.resolve('reviewer')).toMatchObject({ id: agent.id });
|
|
58
|
-
expect(await store.resolve('view')).toBeNull();
|
|
59
|
-
await expect(store.create({ name: 'reviewer', cwd })).rejects.toMatchObject({
|
|
60
|
-
code: 'PRINT_AGENT_NAME_CONFLICT',
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('rejects missing cwd, malformed storage, and symlinked store targets', async () => {
|
|
65
|
-
const PrintAgentStore = await loadStore();
|
|
66
|
-
const { root, cwd, filePath } = fixture();
|
|
67
|
-
const store = new PrintAgentStore({ filePath });
|
|
68
|
-
|
|
69
|
-
await expect(store.create({ name: 'missing', cwd: path.join(root, 'missing') }))
|
|
70
|
-
.rejects.toMatchObject({ code: 'PRINT_AGENT_STORE' });
|
|
71
|
-
|
|
72
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
73
|
-
fs.writeFileSync(filePath, '{bad json', { mode: 0o600 });
|
|
74
|
-
await expect(store.list()).rejects.toMatchObject({ code: 'PRINT_AGENT_STORE' });
|
|
75
|
-
|
|
76
|
-
fs.rmSync(filePath);
|
|
77
|
-
const target = path.join(root, 'target.json');
|
|
78
|
-
fs.writeFileSync(target, JSON.stringify({ version: 1, agents: [] }));
|
|
79
|
-
fs.symlinkSync(target, filePath);
|
|
80
|
-
await expect(store.create({ name: 'unsafe', cwd })).rejects.toMatchObject({
|
|
81
|
-
code: 'PRINT_AGENT_STORE',
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('recovers an abandoned old mutation lock after a crash', async () => {
|
|
86
|
-
const PrintAgentStore = await loadStore();
|
|
87
|
-
const { cwd, filePath } = fixture();
|
|
88
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
89
|
-
const lockPath = `${filePath}.lock`;
|
|
90
|
-
fs.mkdirSync(lockPath);
|
|
91
|
-
const old = new Date(Date.now() - 60_000);
|
|
92
|
-
fs.utimesSync(lockPath, old, old);
|
|
93
|
-
const store = new PrintAgentStore({ filePath, mutationLockStaleMs: 10 });
|
|
94
|
-
|
|
95
|
-
await expect(store.create({ name: 'recovered', cwd })).resolves.toMatchObject({ name: 'recovered' });
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('PrintAgentStore run ownership', () => {
|
|
100
|
-
it('fails fast when another exact owner is live and completes only for its token', async () => {
|
|
101
|
-
const PrintAgentStore = await loadStore();
|
|
102
|
-
const { cwd, filePath } = fixture();
|
|
103
|
-
const live = new Map<number, string>([[process.pid, 'owner-start']]);
|
|
104
|
-
const processInspector = { getIdentity: (pid: number) => {
|
|
105
|
-
const startedAt = live.get(pid);
|
|
106
|
-
return startedAt ? { pid, startedAt } : null;
|
|
107
|
-
} };
|
|
108
|
-
const store = new PrintAgentStore({ filePath, processInspector });
|
|
109
|
-
const agent = await store.create({ name: 'runner', cwd });
|
|
110
|
-
|
|
111
|
-
const acquired = await store.acquireRun(agent.id);
|
|
112
|
-
await expect(store.acquireRun(agent.id)).rejects.toMatchObject({ code: 'PRINT_AGENT_BUSY' });
|
|
113
|
-
await expect(store.completeRun(agent.id, 'wrong-token', {
|
|
114
|
-
status: 'succeeded', exitCode: 0, summary: 'done', sessionHealth: 'healthy',
|
|
115
|
-
})).rejects.toMatchObject({ code: 'PRINT_AGENT_STORE' });
|
|
116
|
-
|
|
117
|
-
const completed = await store.completeRun(agent.id, acquired.token, {
|
|
118
|
-
status: 'succeeded', exitCode: 0, summary: 'done', sessionHealth: 'healthy',
|
|
119
|
-
});
|
|
120
|
-
expect(completed).toMatchObject({ state: 'ready', sessionHealth: 'healthy', activeRun: null });
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('retains busy for a live provider then recovers a dead run without signaling it', async () => {
|
|
124
|
-
const PrintAgentStore = await loadStore();
|
|
125
|
-
const { cwd, filePath } = fixture();
|
|
126
|
-
const live = new Map<number, string>([[process.pid, 'owner-start'], [4242, 'provider-start']]);
|
|
127
|
-
const processInspector = { getIdentity: (pid: number) => {
|
|
128
|
-
const startedAt = live.get(pid);
|
|
129
|
-
return startedAt ? { pid, startedAt } : null;
|
|
130
|
-
} };
|
|
131
|
-
const first = new PrintAgentStore({ filePath, processInspector });
|
|
132
|
-
const agent = await first.create({ name: 'recoverable', cwd });
|
|
133
|
-
const run = await first.acquireRun(agent.id);
|
|
134
|
-
await first.recordProviderProcess(agent.id, run.token, { pid: 4242, startedAt: 'provider-start' });
|
|
135
|
-
|
|
136
|
-
live.delete(process.pid);
|
|
137
|
-
await expect(first.acquireRun(agent.id)).rejects.toMatchObject({ code: 'PRINT_AGENT_BUSY' });
|
|
138
|
-
|
|
139
|
-
live.delete(4242);
|
|
140
|
-
live.set(process.pid, 'replacement-owner-start');
|
|
141
|
-
const recovered = await first.acquireRun(agent.id);
|
|
142
|
-
expect(recovered.agent).toMatchObject({
|
|
143
|
-
state: 'running',
|
|
144
|
-
lastResult: { status: 'interrupted' },
|
|
145
|
-
});
|
|
146
|
-
await first.completeRun(agent.id, recovered.token, {
|
|
147
|
-
status: 'failed', exitCode: 1, summary: 'failed', sessionHealth: 'unknown',
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('reconciles an old incomplete lock to degraded during list', async () => {
|
|
152
|
-
const PrintAgentStore = await loadStore();
|
|
153
|
-
const { root, cwd, filePath } = fixture();
|
|
154
|
-
const live = new Map<number, string>([[process.pid, 'owner-start']]);
|
|
155
|
-
const store = new PrintAgentStore({ filePath, incompleteLockGraceMs: 10, processInspector: {
|
|
156
|
-
getIdentity: (pid: number) => {
|
|
157
|
-
const startedAt = live.get(pid);
|
|
158
|
-
return startedAt ? { pid, startedAt } : null;
|
|
159
|
-
},
|
|
160
|
-
} });
|
|
161
|
-
const agent = await store.create({ name: 'crashed', cwd });
|
|
162
|
-
await store.acquireRun(agent.id);
|
|
163
|
-
const lockPath = path.join(root, 'state', 'print-agent-locks', `${agent.id}.lock`);
|
|
164
|
-
fs.unlinkSync(path.join(lockPath, 'owner.json'));
|
|
165
|
-
const old = new Date(Date.now() - 1000);
|
|
166
|
-
fs.utimesSync(lockPath, old, old);
|
|
167
|
-
live.clear();
|
|
168
|
-
|
|
169
|
-
const listed = await store.list();
|
|
170
|
-
|
|
171
|
-
expect(listed[0]).toMatchObject({
|
|
172
|
-
state: 'degraded',
|
|
173
|
-
sessionHealth: 'unknown',
|
|
174
|
-
activeRun: null,
|
|
175
|
-
lastResult: { status: 'interrupted' },
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('rejects send acquisition when the bound cwd is replaced by a symlink', async () => {
|
|
180
|
-
const PrintAgentStore = await loadStore();
|
|
181
|
-
const { root, cwd, filePath } = fixture();
|
|
182
|
-
const store = new PrintAgentStore({ filePath });
|
|
183
|
-
const agent = await store.create({ name: 'bound', cwd });
|
|
184
|
-
const moved = path.join(root, 'moved-project');
|
|
185
|
-
const other = path.join(root, 'other-project');
|
|
186
|
-
fs.renameSync(cwd, moved);
|
|
187
|
-
fs.mkdirSync(other);
|
|
188
|
-
fs.symlinkSync(other, cwd);
|
|
189
|
-
|
|
190
|
-
await expect(store.acquireRun(agent.id)).rejects.toMatchObject({ code: 'PRINT_AGENT_STORE' });
|
|
191
|
-
});
|
|
192
|
-
});
|
package/src/print/PrintAgent.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
export type PrintAgentState = 'ready' | 'running' | 'degraded';
|
|
2
|
-
export type PrintSessionHealth = 'uninitialized' | 'healthy' | 'unknown' | 'mismatch';
|
|
3
|
-
export type PrintRunStatus = 'succeeded' | 'failed' | 'interrupted';
|
|
4
|
-
|
|
5
|
-
export interface ProcessIdentity {
|
|
6
|
-
pid: number;
|
|
7
|
-
startedAt: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface PrintActiveRun {
|
|
11
|
-
token: string;
|
|
12
|
-
owner: ProcessIdentity;
|
|
13
|
-
provider: ProcessIdentity | null;
|
|
14
|
-
startedAt: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface PrintLastResult {
|
|
18
|
-
status: PrintRunStatus;
|
|
19
|
-
completedAt: string;
|
|
20
|
-
exitCode: number | null;
|
|
21
|
-
summary: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface PrintAgent {
|
|
25
|
-
id: string;
|
|
26
|
-
name: string;
|
|
27
|
-
provider: 'claude';
|
|
28
|
-
mode: 'print';
|
|
29
|
-
cwd: string;
|
|
30
|
-
providerSessionId: string;
|
|
31
|
-
state: PrintAgentState;
|
|
32
|
-
sessionHealth: PrintSessionHealth;
|
|
33
|
-
createdAt: string;
|
|
34
|
-
updatedAt: string;
|
|
35
|
-
lastActiveAt: string | null;
|
|
36
|
-
lastResult: PrintLastResult | null;
|
|
37
|
-
activeRun: PrintActiveRun | null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class PrintAgentError extends Error {
|
|
41
|
-
constructor(
|
|
42
|
-
message: string,
|
|
43
|
-
public readonly code: string,
|
|
44
|
-
) {
|
|
45
|
-
super(message);
|
|
46
|
-
this.name = 'PrintAgentError';
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export class PrintAgentBusyError extends PrintAgentError {
|
|
51
|
-
constructor(
|
|
52
|
-
public readonly agentId: string,
|
|
53
|
-
agentName: string,
|
|
54
|
-
) {
|
|
55
|
-
super(`Print agent "${agentName}" is busy.`, 'PRINT_AGENT_BUSY');
|
|
56
|
-
this.name = 'PrintAgentBusyError';
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export class PrintAgentNotFoundError extends PrintAgentError {
|
|
61
|
-
constructor(public readonly reference: string) {
|
|
62
|
-
super(`Print agent "${reference}" was not found.`, 'PRINT_AGENT_NOT_FOUND');
|
|
63
|
-
this.name = 'PrintAgentNotFoundError';
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export class PrintAgentStoreError extends PrintAgentError {
|
|
68
|
-
constructor(message: string) {
|
|
69
|
-
super(message, 'PRINT_AGENT_STORE');
|
|
70
|
-
this.name = 'PrintAgentStoreError';
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export class PrintAgentNameConflictError extends PrintAgentError {
|
|
75
|
-
constructor(public readonly agentName: string) {
|
|
76
|
-
super(`Print agent name "${agentName}" is already in use.`, 'PRINT_AGENT_NAME_CONFLICT');
|
|
77
|
-
this.name = 'PrintAgentNameConflictError';
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export class ClaudePrintError extends PrintAgentError {
|
|
82
|
-
constructor(message: string, code = 'CLAUDE_PRINT_FAILED') {
|
|
83
|
-
super(message, code);
|
|
84
|
-
this.name = 'ClaudePrintError';
|
|
85
|
-
}
|
|
86
|
-
}
|