@ai-devkit/agent-manager 0.25.0 → 0.26.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 +14 -0
- package/dist/__tests__/print/ClaudeCliProbe.test.js +53 -0
- package/dist/__tests__/print/ClaudeCliProbe.test.js.map +1 -0
- package/dist/__tests__/print/ClaudePrintAgent.integration.test.js +69 -0
- package/dist/__tests__/print/ClaudePrintAgent.integration.test.js.map +1 -0
- package/dist/__tests__/print/ClaudePrintAgentService.test.js +108 -0
- package/dist/__tests__/print/ClaudePrintAgentService.test.js.map +1 -0
- package/dist/__tests__/print/ClaudePrintRunner.test.js +187 -0
- package/dist/__tests__/print/ClaudePrintRunner.test.js.map +1 -0
- package/dist/__tests__/print/PrintAgent.test.js +17 -0
- package/dist/__tests__/print/PrintAgent.test.js.map +1 -0
- package/dist/__tests__/print/PrintAgentStore.test.js +307 -0
- package/dist/__tests__/print/PrintAgentStore.test.js.map +1 -0
- package/dist/__tests__/terminal/TmuxManager.test.js +9 -0
- package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/print/ClaudeCliProbe.d.ts +20 -0
- package/dist/print/ClaudeCliProbe.d.ts.map +1 -0
- package/dist/print/ClaudeCliProbe.js +57 -0
- package/dist/print/ClaudeCliProbe.js.map +1 -0
- package/dist/print/ClaudePrintAgentService.d.ts +44 -0
- package/dist/print/ClaudePrintAgentService.d.ts.map +1 -0
- package/dist/print/ClaudePrintAgentService.js +66 -0
- package/dist/print/ClaudePrintAgentService.js.map +1 -0
- package/dist/print/ClaudePrintRunner.d.ts +32 -0
- package/dist/print/ClaudePrintRunner.d.ts.map +1 -0
- package/dist/print/ClaudePrintRunner.js +128 -0
- package/dist/print/ClaudePrintRunner.js.map +1 -0
- package/dist/print/PrintAgent.d.ts +57 -0
- package/dist/print/PrintAgent.d.ts.map +1 -0
- package/dist/print/PrintAgent.js +42 -0
- package/dist/print/PrintAgent.js.map +1 -0
- package/dist/print/PrintAgentStore.d.ts +69 -0
- package/dist/print/PrintAgentStore.d.ts.map +1 -0
- package/dist/print/PrintAgentStore.js +484 -0
- package/dist/print/PrintAgentStore.js.map +1 -0
- package/dist/terminal/TmuxManager.d.ts +2 -2
- package/dist/terminal/TmuxManager.d.ts.map +1 -1
- package/dist/terminal/TmuxManager.js +5 -7
- package/dist/terminal/TmuxManager.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/fixtures/fake-claude.cjs +24 -0
- package/src/__tests__/print/ClaudeCliProbe.test.ts +32 -0
- package/src/__tests__/print/ClaudePrintAgent.integration.test.ts +56 -0
- package/src/__tests__/print/ClaudePrintAgentService.test.ts +46 -0
- package/src/__tests__/print/ClaudePrintRunner.test.ts +105 -0
- package/src/__tests__/print/PrintAgent.test.ts +21 -0
- package/src/__tests__/print/PrintAgentStore.test.ts +192 -0
- package/src/__tests__/terminal/TmuxManager.test.ts +10 -0
- package/src/index.ts +39 -0
- package/src/print/ClaudeCliProbe.ts +58 -0
- package/src/print/ClaudePrintAgentService.ts +94 -0
- package/src/print/ClaudePrintRunner.ts +139 -0
- package/src/print/PrintAgent.ts +86 -0
- package/src/print/PrintAgentStore.ts +503 -0
- package/src/terminal/TmuxManager.ts +5 -7
package/README.md
CHANGED
|
@@ -24,6 +24,20 @@ ai-devkit agent send "run the tests and report back" --id <agent-name> --wait
|
|
|
24
24
|
npm test 2>&1 | ai-devkit agent send --id <agent-name> --stdin
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
Claude Code can also be registered as a durable print-mode agent. Registration
|
|
28
|
+
does not launch Claude; each send starts one synchronous process and later sends
|
|
29
|
+
resume the same Claude session:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
ai-devkit agent start --type claude --mode print --name reviewer --cwd /path/to/project
|
|
33
|
+
ai-devkit agent send "review the current diff" --id reviewer
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Print mode inherits Claude Code's settings, permissions, hooks, MCP servers, and
|
|
37
|
+
tool side effects for that working directory. AI DevKit adds no permission bypass
|
|
38
|
+
or automatic retry, and prompts are delivered over stdin rather than command-line
|
|
39
|
+
arguments. `--timeout` is not supported for print agents in this first release.
|
|
40
|
+
|
|
27
41
|
Use this package directly only when building custom tooling around AI DevKit's agent detection and control surface.
|
|
28
42
|
|
|
29
43
|
## Documentation
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
describe('ClaudeCliProbe', ()=>{
|
|
3
|
+
it('validates only version/help and requires the print session flags', async ()=>{
|
|
4
|
+
const api = await import('../../index.js');
|
|
5
|
+
expect(api).toHaveProperty('ClaudeCliProbe');
|
|
6
|
+
const exec = vi.fn().mockResolvedValueOnce({
|
|
7
|
+
stdout: '2.1.220\n',
|
|
8
|
+
stderr: ''
|
|
9
|
+
}).mockResolvedValueOnce({
|
|
10
|
+
stdout: '--print --session-id --resume --output-format stream-json',
|
|
11
|
+
stderr: ''
|
|
12
|
+
});
|
|
13
|
+
const Probe = api.ClaudeCliProbe;
|
|
14
|
+
await expect(new Probe({
|
|
15
|
+
exec
|
|
16
|
+
}).validate()).resolves.toEqual({
|
|
17
|
+
executable: 'claude',
|
|
18
|
+
version: '2.1.220'
|
|
19
|
+
});
|
|
20
|
+
expect(exec.mock.calls).toEqual([
|
|
21
|
+
[
|
|
22
|
+
'claude',
|
|
23
|
+
[
|
|
24
|
+
'--version'
|
|
25
|
+
]
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
'claude',
|
|
29
|
+
[
|
|
30
|
+
'--help'
|
|
31
|
+
]
|
|
32
|
+
]
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
it('rejects a CLI missing a required capability', async ()=>{
|
|
36
|
+
const api = await import('../../index.js');
|
|
37
|
+
const exec = vi.fn().mockResolvedValueOnce({
|
|
38
|
+
stdout: 'old',
|
|
39
|
+
stderr: ''
|
|
40
|
+
}).mockResolvedValueOnce({
|
|
41
|
+
stdout: '--print only',
|
|
42
|
+
stderr: ''
|
|
43
|
+
});
|
|
44
|
+
const Probe = api.ClaudeCliProbe;
|
|
45
|
+
await expect(new Probe({
|
|
46
|
+
exec
|
|
47
|
+
}).validate()).rejects.toMatchObject({
|
|
48
|
+
code: 'CLAUDE_CLI_UNSUPPORTED'
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=ClaudeCliProbe.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/print/ClaudeCliProbe.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\n\ndescribe('ClaudeCliProbe', () => {\n it('validates only version/help and requires the print session flags', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n expect(api).toHaveProperty('ClaudeCliProbe');\n const exec = vi.fn()\n .mockResolvedValueOnce({ stdout: '2.1.220\\n', stderr: '' })\n .mockResolvedValueOnce({\n stdout: '--print --session-id --resume --output-format stream-json', stderr: '',\n });\n const Probe = api.ClaudeCliProbe as new (options: unknown) => { validate(): Promise<unknown> };\n\n await expect(new Probe({ exec }).validate()).resolves.toEqual({\n executable: 'claude', version: '2.1.220',\n });\n expect(exec.mock.calls).toEqual([\n ['claude', ['--version']],\n ['claude', ['--help']],\n ]);\n });\n\n it('rejects a CLI missing a required capability', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const exec = vi.fn()\n .mockResolvedValueOnce({ stdout: 'old', stderr: '' })\n .mockResolvedValueOnce({ stdout: '--print only', stderr: '' });\n const Probe = api.ClaudeCliProbe as new (options: unknown) => { validate(): Promise<unknown> };\n\n await expect(new Probe({ exec }).validate()).rejects.toMatchObject({ code: 'CLAUDE_CLI_UNSUPPORTED' });\n });\n});\n"],"names":["describe","expect","it","vi","api","toHaveProperty","exec","fn","mockResolvedValueOnce","stdout","stderr","Probe","ClaudeCliProbe","validate","resolves","toEqual","executable","version","mock","calls","rejects","toMatchObject","code"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAS;AAElDH,SAAS,kBAAkB;IACvBE,GAAG,oEAAoE;QACnE,MAAME,MAAM,MAAM,MAAM,CAAC;QACzBH,OAAOG,KAAKC,cAAc,CAAC;QAC3B,MAAMC,OAAOH,GAAGI,EAAE,GACbC,qBAAqB,CAAC;YAAEC,QAAQ;YAAaC,QAAQ;QAAG,GACxDF,qBAAqB,CAAC;YACnBC,QAAQ;YAA6DC,QAAQ;QACjF;QACJ,MAAMC,QAAQP,IAAIQ,cAAc;QAEhC,MAAMX,OAAO,IAAIU,MAAM;YAAEL;QAAK,GAAGO,QAAQ,IAAIC,QAAQ,CAACC,OAAO,CAAC;YAC1DC,YAAY;YAAUC,SAAS;QACnC;QACAhB,OAAOK,KAAKY,IAAI,CAACC,KAAK,EAAEJ,OAAO,CAAC;YAC5B;gBAAC;gBAAU;oBAAC;iBAAY;aAAC;YACzB;gBAAC;gBAAU;oBAAC;iBAAS;aAAC;SACzB;IACL;IAEAb,GAAG,+CAA+C;QAC9C,MAAME,MAAM,MAAM,MAAM,CAAC;QACzB,MAAME,OAAOH,GAAGI,EAAE,GACbC,qBAAqB,CAAC;YAAEC,QAAQ;YAAOC,QAAQ;QAAG,GAClDF,qBAAqB,CAAC;YAAEC,QAAQ;YAAgBC,QAAQ;QAAG;QAChE,MAAMC,QAAQP,IAAIQ,cAAc;QAEhC,MAAMX,OAAO,IAAIU,MAAM;YAAEL;QAAK,GAAGO,QAAQ,IAAIO,OAAO,CAACC,aAAa,CAAC;YAAEC,MAAM;QAAyB;IACxG;AACJ"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import { ClaudeCliProbe, ClaudePrintAgentService, ClaudePrintRunner, PrintAgentStore } from '../../index.js';
|
|
7
|
+
const roots = [];
|
|
8
|
+
const originalCapture = process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE;
|
|
9
|
+
afterEach(()=>{
|
|
10
|
+
if (originalCapture === undefined) delete process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE;
|
|
11
|
+
else process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = originalCapture;
|
|
12
|
+
for (const root of roots.splice(0))fs.rmSync(root, {
|
|
13
|
+
recursive: true,
|
|
14
|
+
force: true
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe('Claude print-agent fake-provider journey', ()=>{
|
|
18
|
+
it('creates without invocation, then starts and resumes the same session through stdin', async ()=>{
|
|
19
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'print-agent-integration-'));
|
|
20
|
+
roots.push(root);
|
|
21
|
+
const cwd = path.join(root, 'project');
|
|
22
|
+
fs.mkdirSync(cwd);
|
|
23
|
+
const capture = path.join(root, 'capture.jsonl');
|
|
24
|
+
process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = capture;
|
|
25
|
+
const executable = fileURLToPath(new URL('../fixtures/fake-claude.cjs', import.meta.url));
|
|
26
|
+
const store = new PrintAgentStore({
|
|
27
|
+
filePath: path.join(root, 'state', 'print-agents.json')
|
|
28
|
+
});
|
|
29
|
+
const service = new ClaudePrintAgentService({
|
|
30
|
+
store,
|
|
31
|
+
probe: new ClaudeCliProbe({
|
|
32
|
+
executable
|
|
33
|
+
}),
|
|
34
|
+
runner: new ClaudePrintRunner(),
|
|
35
|
+
executable
|
|
36
|
+
});
|
|
37
|
+
const created = await service.create({
|
|
38
|
+
name: 'reviewer',
|
|
39
|
+
cwd
|
|
40
|
+
});
|
|
41
|
+
expect(fs.existsSync(capture)).toBe(false);
|
|
42
|
+
await expect(service.send(created.id, 'first secret')).resolves.toMatchObject({
|
|
43
|
+
result: 'answer:first secret'
|
|
44
|
+
});
|
|
45
|
+
await expect(service.send(created.id, 'follow up')).resolves.toMatchObject({
|
|
46
|
+
result: 'answer:follow up'
|
|
47
|
+
});
|
|
48
|
+
const invocations = fs.readFileSync(capture, 'utf8').trim().split('\n').map((line)=>JSON.parse(line));
|
|
49
|
+
expect(invocations[0]).toMatchObject({
|
|
50
|
+
prompt: 'first secret',
|
|
51
|
+
cwd: fs.realpathSync(cwd)
|
|
52
|
+
});
|
|
53
|
+
expect(invocations[0].args).toContain('--session-id');
|
|
54
|
+
expect(invocations[0].args).not.toContain('first secret');
|
|
55
|
+
expect(invocations[1]).toMatchObject({
|
|
56
|
+
prompt: 'follow up',
|
|
57
|
+
cwd: fs.realpathSync(cwd)
|
|
58
|
+
});
|
|
59
|
+
expect(invocations[1].args).toContain('--resume');
|
|
60
|
+
expect(invocations[1].args[invocations[1].args.indexOf('--resume') + 1]).toBe(created.providerSessionId);
|
|
61
|
+
const persisted = await store.getById(created.id);
|
|
62
|
+
expect(persisted).toMatchObject({
|
|
63
|
+
state: 'ready',
|
|
64
|
+
sessionHealth: 'healthy'
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=ClaudePrintAgent.integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/print/ClaudePrintAgent.integration.test.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { afterEach, describe, expect, it } from 'vitest';\nimport {\n ClaudeCliProbe,\n ClaudePrintAgentService,\n ClaudePrintRunner,\n PrintAgentStore,\n} from '../../index.js';\n\nconst roots: string[] = [];\nconst originalCapture = process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE;\n\nafterEach(() => {\n if (originalCapture === undefined) delete process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE;\n else process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = originalCapture;\n for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });\n});\n\ndescribe('Claude print-agent fake-provider journey', () => {\n it('creates without invocation, then starts and resumes the same session through stdin', async () => {\n const root = fs.mkdtempSync(path.join(os.tmpdir(), 'print-agent-integration-'));\n roots.push(root);\n const cwd = path.join(root, 'project');\n fs.mkdirSync(cwd);\n const capture = path.join(root, 'capture.jsonl');\n process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = capture;\n const executable = fileURLToPath(new URL('../fixtures/fake-claude.cjs', import.meta.url));\n const store = new PrintAgentStore({ filePath: path.join(root, 'state', 'print-agents.json') });\n const service = new ClaudePrintAgentService({\n store,\n probe: new ClaudeCliProbe({ executable }),\n runner: new ClaudePrintRunner(),\n executable,\n });\n\n const created = await service.create({ name: 'reviewer', cwd });\n expect(fs.existsSync(capture)).toBe(false);\n\n await expect(service.send(created.id, 'first secret')).resolves.toMatchObject({ result: 'answer:first secret' });\n await expect(service.send(created.id, 'follow up')).resolves.toMatchObject({ result: 'answer:follow up' });\n\n const invocations = fs.readFileSync(capture, 'utf8').trim().split('\\n').map((line) => JSON.parse(line));\n expect(invocations[0]).toMatchObject({ prompt: 'first secret', cwd: fs.realpathSync(cwd) });\n expect(invocations[0].args).toContain('--session-id');\n expect(invocations[0].args).not.toContain('first secret');\n expect(invocations[1]).toMatchObject({ prompt: 'follow up', cwd: fs.realpathSync(cwd) });\n expect(invocations[1].args).toContain('--resume');\n expect(invocations[1].args[invocations[1].args.indexOf('--resume') + 1]).toBe(created.providerSessionId);\n\n const persisted = await store.getById(created.id);\n expect(persisted).toMatchObject({ state: 'ready', sessionHealth: 'healthy' });\n });\n});\n"],"names":["fs","os","path","fileURLToPath","afterEach","describe","expect","it","ClaudeCliProbe","ClaudePrintAgentService","ClaudePrintRunner","PrintAgentStore","roots","originalCapture","process","env","AI_DEVKIT_FAKE_CLAUDE_CAPTURE","undefined","root","splice","rmSync","recursive","force","mkdtempSync","join","tmpdir","push","cwd","mkdirSync","capture","executable","URL","url","store","filePath","service","probe","runner","created","create","name","existsSync","toBe","send","id","resolves","toMatchObject","result","invocations","readFileSync","trim","split","map","line","JSON","parse","prompt","realpathSync","args","toContain","not","indexOf","providerSessionId","persisted","getById","state","sessionHealth"],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
describe('ClaudePrintAgentService', ()=>{
|
|
3
|
+
it('validates before create and does not run Claude', async ()=>{
|
|
4
|
+
const api = await import('../../index.js');
|
|
5
|
+
expect(api).toHaveProperty('ClaudePrintAgentService');
|
|
6
|
+
const probe = {
|
|
7
|
+
validate: vi.fn().mockResolvedValue({
|
|
8
|
+
executable: 'claude',
|
|
9
|
+
version: '2.1.220'
|
|
10
|
+
})
|
|
11
|
+
};
|
|
12
|
+
const store = {
|
|
13
|
+
create: vi.fn().mockResolvedValue({
|
|
14
|
+
id: 'agent-id',
|
|
15
|
+
name: 'reviewer'
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
const runner = {
|
|
19
|
+
run: vi.fn()
|
|
20
|
+
};
|
|
21
|
+
const Service = api.ClaudePrintAgentService;
|
|
22
|
+
await expect(new Service({
|
|
23
|
+
store,
|
|
24
|
+
probe,
|
|
25
|
+
runner
|
|
26
|
+
}).create({
|
|
27
|
+
name: 'reviewer',
|
|
28
|
+
cwd: '/project'
|
|
29
|
+
})).resolves.toMatchObject({
|
|
30
|
+
id: 'agent-id'
|
|
31
|
+
});
|
|
32
|
+
expect(probe.validate).toHaveBeenCalledOnce();
|
|
33
|
+
expect(store.create).toHaveBeenCalledWith({
|
|
34
|
+
name: 'reviewer',
|
|
35
|
+
cwd: '/project'
|
|
36
|
+
});
|
|
37
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
38
|
+
});
|
|
39
|
+
it('runs first and resumed sends and records provider identity/results', async ()=>{
|
|
40
|
+
const api = await import('../../index.js');
|
|
41
|
+
const base = {
|
|
42
|
+
id: 'id',
|
|
43
|
+
name: 'reviewer',
|
|
44
|
+
providerSessionId: 'session',
|
|
45
|
+
sessionHealth: 'uninitialized'
|
|
46
|
+
};
|
|
47
|
+
const store = {
|
|
48
|
+
resolve: vi.fn().mockResolvedValue(base),
|
|
49
|
+
acquireRun: vi.fn().mockResolvedValueOnce({
|
|
50
|
+
agent: base,
|
|
51
|
+
token: 'one'
|
|
52
|
+
}).mockResolvedValueOnce({
|
|
53
|
+
agent: {
|
|
54
|
+
...base,
|
|
55
|
+
sessionHealth: 'healthy'
|
|
56
|
+
},
|
|
57
|
+
token: 'two'
|
|
58
|
+
}),
|
|
59
|
+
recordProviderProcess: vi.fn(),
|
|
60
|
+
completeRun: vi.fn().mockResolvedValue({})
|
|
61
|
+
};
|
|
62
|
+
const runner = {
|
|
63
|
+
run: vi.fn().mockImplementation(async (request)=>{
|
|
64
|
+
await request.onSpawn({
|
|
65
|
+
pid: 42,
|
|
66
|
+
startedAt: 'start'
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
sessionId: 'session',
|
|
70
|
+
result: 'answer',
|
|
71
|
+
exitCode: 0
|
|
72
|
+
};
|
|
73
|
+
})
|
|
74
|
+
};
|
|
75
|
+
const Service = api.ClaudePrintAgentService;
|
|
76
|
+
const service = new Service({
|
|
77
|
+
store,
|
|
78
|
+
probe: {
|
|
79
|
+
validate: vi.fn()
|
|
80
|
+
},
|
|
81
|
+
runner,
|
|
82
|
+
executable: 'fake-claude'
|
|
83
|
+
});
|
|
84
|
+
await service.send('reviewer', 'first');
|
|
85
|
+
await service.send('id', 'later');
|
|
86
|
+
expect(runner.run.mock.calls[0][0]).toMatchObject({
|
|
87
|
+
prompt: 'first',
|
|
88
|
+
firstRun: true,
|
|
89
|
+
executable: 'fake-claude'
|
|
90
|
+
});
|
|
91
|
+
expect(runner.run.mock.calls[1][0]).toMatchObject({
|
|
92
|
+
prompt: 'later',
|
|
93
|
+
firstRun: false,
|
|
94
|
+
executable: 'fake-claude'
|
|
95
|
+
});
|
|
96
|
+
expect(store.recordProviderProcess).toHaveBeenCalledWith('id', 'one', {
|
|
97
|
+
pid: 42,
|
|
98
|
+
startedAt: 'start'
|
|
99
|
+
});
|
|
100
|
+
expect(store.completeRun).toHaveBeenCalledWith('id', 'one', expect.objectContaining({
|
|
101
|
+
status: 'succeeded',
|
|
102
|
+
exitCode: 0,
|
|
103
|
+
sessionHealth: 'healthy'
|
|
104
|
+
}));
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=ClaudePrintAgentService.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/print/ClaudePrintAgentService.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\n\ndescribe('ClaudePrintAgentService', () => {\n it('validates before create and does not run Claude', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n expect(api).toHaveProperty('ClaudePrintAgentService');\n const probe = { validate: vi.fn().mockResolvedValue({ executable: 'claude', version: '2.1.220' }) };\n const store = { create: vi.fn().mockResolvedValue({ id: 'agent-id', name: 'reviewer' }) };\n const runner = { run: vi.fn() };\n const Service = api.ClaudePrintAgentService as new (options: unknown) => any;\n\n await expect(new Service({ store, probe, runner }).create({ name: 'reviewer', cwd: '/project' }))\n .resolves.toMatchObject({ id: 'agent-id' });\n expect(probe.validate).toHaveBeenCalledOnce();\n expect(store.create).toHaveBeenCalledWith({ name: 'reviewer', cwd: '/project' });\n expect(runner.run).not.toHaveBeenCalled();\n });\n\n it('runs first and resumed sends and records provider identity/results', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const base = { id: 'id', name: 'reviewer', providerSessionId: 'session', sessionHealth: 'uninitialized' };\n const store = {\n resolve: vi.fn().mockResolvedValue(base),\n acquireRun: vi.fn()\n .mockResolvedValueOnce({ agent: base, token: 'one' })\n .mockResolvedValueOnce({ agent: { ...base, sessionHealth: 'healthy' }, token: 'two' }),\n recordProviderProcess: vi.fn(), completeRun: vi.fn().mockResolvedValue({}),\n };\n const runner = { run: vi.fn().mockImplementation(async (request) => {\n await request.onSpawn({ pid: 42, startedAt: 'start' });\n return { sessionId: 'session', result: 'answer', exitCode: 0 };\n }) };\n const Service = api.ClaudePrintAgentService as new (options: unknown) => any;\n const service = new Service({ store, probe: { validate: vi.fn() }, runner, executable: 'fake-claude' });\n\n await service.send('reviewer', 'first');\n await service.send('id', 'later');\n\n expect(runner.run.mock.calls[0][0]).toMatchObject({ prompt: 'first', firstRun: true, executable: 'fake-claude' });\n expect(runner.run.mock.calls[1][0]).toMatchObject({ prompt: 'later', firstRun: false, executable: 'fake-claude' });\n expect(store.recordProviderProcess).toHaveBeenCalledWith('id', 'one', { pid: 42, startedAt: 'start' });\n expect(store.completeRun).toHaveBeenCalledWith('id', 'one', expect.objectContaining({\n status: 'succeeded', exitCode: 0, sessionHealth: 'healthy',\n }));\n });\n});\n"],"names":["describe","expect","it","vi","api","toHaveProperty","probe","validate","fn","mockResolvedValue","executable","version","store","create","id","name","runner","run","Service","ClaudePrintAgentService","cwd","resolves","toMatchObject","toHaveBeenCalledOnce","toHaveBeenCalledWith","not","toHaveBeenCalled","base","providerSessionId","sessionHealth","resolve","acquireRun","mockResolvedValueOnce","agent","token","recordProviderProcess","completeRun","mockImplementation","request","onSpawn","pid","startedAt","sessionId","result","exitCode","service","send","mock","calls","prompt","firstRun","objectContaining","status"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAS;AAElDH,SAAS,2BAA2B;IAChCE,GAAG,mDAAmD;QAClD,MAAME,MAAM,MAAM,MAAM,CAAC;QACzBH,OAAOG,KAAKC,cAAc,CAAC;QAC3B,MAAMC,QAAQ;YAAEC,UAAUJ,GAAGK,EAAE,GAAGC,iBAAiB,CAAC;gBAAEC,YAAY;gBAAUC,SAAS;YAAU;QAAG;QAClG,MAAMC,QAAQ;YAAEC,QAAQV,GAAGK,EAAE,GAAGC,iBAAiB,CAAC;gBAAEK,IAAI;gBAAYC,MAAM;YAAW;QAAG;QACxF,MAAMC,SAAS;YAAEC,KAAKd,GAAGK,EAAE;QAAG;QAC9B,MAAMU,UAAUd,IAAIe,uBAAuB;QAE3C,MAAMlB,OAAO,IAAIiB,QAAQ;YAAEN;YAAON;YAAOU;QAAO,GAAGH,MAAM,CAAC;YAAEE,MAAM;YAAYK,KAAK;QAAW,IACzFC,QAAQ,CAACC,aAAa,CAAC;YAAER,IAAI;QAAW;QAC7Cb,OAAOK,MAAMC,QAAQ,EAAEgB,oBAAoB;QAC3CtB,OAAOW,MAAMC,MAAM,EAAEW,oBAAoB,CAAC;YAAET,MAAM;YAAYK,KAAK;QAAW;QAC9EnB,OAAOe,OAAOC,GAAG,EAAEQ,GAAG,CAACC,gBAAgB;IAC3C;IAEAxB,GAAG,sEAAsE;QACrE,MAAME,MAAM,MAAM,MAAM,CAAC;QACzB,MAAMuB,OAAO;YAAEb,IAAI;YAAMC,MAAM;YAAYa,mBAAmB;YAAWC,eAAe;QAAgB;QACxG,MAAMjB,QAAQ;YACVkB,SAAS3B,GAAGK,EAAE,GAAGC,iBAAiB,CAACkB;YACnCI,YAAY5B,GAAGK,EAAE,GACZwB,qBAAqB,CAAC;gBAAEC,OAAON;gBAAMO,OAAO;YAAM,GAClDF,qBAAqB,CAAC;gBAAEC,OAAO;oBAAE,GAAGN,IAAI;oBAAEE,eAAe;gBAAU;gBAAGK,OAAO;YAAM;YACxFC,uBAAuBhC,GAAGK,EAAE;YAAI4B,aAAajC,GAAGK,EAAE,GAAGC,iBAAiB,CAAC,CAAC;QAC5E;QACA,MAAMO,SAAS;YAAEC,KAAKd,GAAGK,EAAE,GAAG6B,kBAAkB,CAAC,OAAOC;gBACpD,MAAMA,QAAQC,OAAO,CAAC;oBAAEC,KAAK;oBAAIC,WAAW;gBAAQ;gBACpD,OAAO;oBAAEC,WAAW;oBAAWC,QAAQ;oBAAUC,UAAU;gBAAE;YACjE;QAAG;QACH,MAAM1B,UAAUd,IAAIe,uBAAuB;QAC3C,MAAM0B,UAAU,IAAI3B,QAAQ;YAAEN;YAAON,OAAO;gBAAEC,UAAUJ,GAAGK,EAAE;YAAG;YAAGQ;YAAQN,YAAY;QAAc;QAErG,MAAMmC,QAAQC,IAAI,CAAC,YAAY;QAC/B,MAAMD,QAAQC,IAAI,CAAC,MAAM;QAEzB7C,OAAOe,OAAOC,GAAG,CAAC8B,IAAI,CAACC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE1B,aAAa,CAAC;YAAE2B,QAAQ;YAASC,UAAU;YAAMxC,YAAY;QAAc;QAC/GT,OAAOe,OAAOC,GAAG,CAAC8B,IAAI,CAACC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE1B,aAAa,CAAC;YAAE2B,QAAQ;YAASC,UAAU;YAAOxC,YAAY;QAAc;QAChHT,OAAOW,MAAMuB,qBAAqB,EAAEX,oBAAoB,CAAC,MAAM,OAAO;YAAEgB,KAAK;YAAIC,WAAW;QAAQ;QACpGxC,OAAOW,MAAMwB,WAAW,EAAEZ,oBAAoB,CAAC,MAAM,OAAOvB,OAAOkD,gBAAgB,CAAC;YAChFC,QAAQ;YAAaR,UAAU;YAAGf,eAAe;QACrD;IACJ;AACJ"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { PassThrough, Writable } from 'node:stream';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
function agent() {
|
|
5
|
+
return {
|
|
6
|
+
id: '11111111-1111-4111-8111-111111111111',
|
|
7
|
+
name: 'reviewer',
|
|
8
|
+
provider: 'claude',
|
|
9
|
+
mode: 'print',
|
|
10
|
+
cwd: '/project',
|
|
11
|
+
providerSessionId: '22222222-2222-4222-8222-222222222222',
|
|
12
|
+
state: 'running',
|
|
13
|
+
sessionHealth: 'uninitialized',
|
|
14
|
+
createdAt: '',
|
|
15
|
+
updatedAt: '',
|
|
16
|
+
lastActiveAt: null,
|
|
17
|
+
lastResult: null,
|
|
18
|
+
activeRun: null
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function fakeSpawn(events, exitCode = 0) {
|
|
22
|
+
const calls = [];
|
|
23
|
+
const promptChunks = [];
|
|
24
|
+
const child = new EventEmitter();
|
|
25
|
+
child.pid = 4242;
|
|
26
|
+
child.stdout = new PassThrough();
|
|
27
|
+
child.stderr = new PassThrough();
|
|
28
|
+
child.stdin = new Writable({
|
|
29
|
+
write (chunk, _encoding, callback) {
|
|
30
|
+
promptChunks.push(Buffer.from(chunk));
|
|
31
|
+
callback();
|
|
32
|
+
},
|
|
33
|
+
final (callback) {
|
|
34
|
+
for (const event of events)child.stdout.write(`${JSON.stringify(event)}\n`);
|
|
35
|
+
child.stdout.end();
|
|
36
|
+
queueMicrotask(()=>child.emit('close', exitCode, null));
|
|
37
|
+
callback();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
const spawn = vi.fn((...args)=>{
|
|
41
|
+
calls.push(args);
|
|
42
|
+
return child;
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
spawn,
|
|
46
|
+
calls,
|
|
47
|
+
promptChunks
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
describe('ClaudePrintRunner', ()=>{
|
|
51
|
+
it('starts a caller-assigned session and persists provider identity before stdin', async ()=>{
|
|
52
|
+
const api = await import('../../index.js');
|
|
53
|
+
expect(api).toHaveProperty('ClaudePrintRunner');
|
|
54
|
+
const fixture = fakeSpawn([
|
|
55
|
+
{
|
|
56
|
+
type: 'system',
|
|
57
|
+
subtype: 'init',
|
|
58
|
+
session_id: agent().providerSessionId
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: 'result',
|
|
62
|
+
session_id: agent().providerSessionId,
|
|
63
|
+
result: 'done'
|
|
64
|
+
}
|
|
65
|
+
]);
|
|
66
|
+
let persisted = false;
|
|
67
|
+
const Runner = api.ClaudePrintRunner;
|
|
68
|
+
const runner = new Runner({
|
|
69
|
+
spawn: fixture.spawn,
|
|
70
|
+
processInspector: {
|
|
71
|
+
getIdentity: ()=>({
|
|
72
|
+
pid: 4242,
|
|
73
|
+
startedAt: 'provider-start'
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
const result = await runner.run({
|
|
78
|
+
agent: agent(),
|
|
79
|
+
prompt: 'secret prompt',
|
|
80
|
+
executable: 'claude-test',
|
|
81
|
+
firstRun: true,
|
|
82
|
+
onSpawn: async ()=>{
|
|
83
|
+
expect(fixture.promptChunks).toHaveLength(0);
|
|
84
|
+
persisted = true;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
expect(persisted).toBe(true);
|
|
88
|
+
expect(fixture.calls[0]).toEqual([
|
|
89
|
+
'claude-test',
|
|
90
|
+
[
|
|
91
|
+
'-p',
|
|
92
|
+
'--session-id',
|
|
93
|
+
agent().providerSessionId,
|
|
94
|
+
'--output-format',
|
|
95
|
+
'stream-json',
|
|
96
|
+
'--verbose'
|
|
97
|
+
],
|
|
98
|
+
expect.objectContaining({
|
|
99
|
+
cwd: '/project',
|
|
100
|
+
shell: false,
|
|
101
|
+
stdio: [
|
|
102
|
+
'pipe',
|
|
103
|
+
'pipe',
|
|
104
|
+
'pipe'
|
|
105
|
+
]
|
|
106
|
+
})
|
|
107
|
+
]);
|
|
108
|
+
expect(JSON.stringify(fixture.calls)).not.toContain('secret prompt');
|
|
109
|
+
expect(Buffer.concat(fixture.promptChunks).toString()).toBe('secret prompt');
|
|
110
|
+
expect(result).toEqual({
|
|
111
|
+
sessionId: agent().providerSessionId,
|
|
112
|
+
result: 'done',
|
|
113
|
+
exitCode: 0
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
it('uses exact resume and rejects a mismatched result session', async ()=>{
|
|
117
|
+
const api = await import('../../index.js');
|
|
118
|
+
const fixture = fakeSpawn([
|
|
119
|
+
{
|
|
120
|
+
type: 'result',
|
|
121
|
+
session_id: 'wrong',
|
|
122
|
+
result: 'nope'
|
|
123
|
+
}
|
|
124
|
+
]);
|
|
125
|
+
const Runner = api.ClaudePrintRunner;
|
|
126
|
+
const runner = new Runner({
|
|
127
|
+
spawn: fixture.spawn,
|
|
128
|
+
processInspector: {
|
|
129
|
+
getIdentity: ()=>({
|
|
130
|
+
pid: 4242,
|
|
131
|
+
startedAt: 'provider-start'
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
await expect(runner.run({
|
|
136
|
+
agent: agent(),
|
|
137
|
+
prompt: 'followup',
|
|
138
|
+
executable: 'claude',
|
|
139
|
+
firstRun: false,
|
|
140
|
+
onSpawn: vi.fn()
|
|
141
|
+
})).rejects.toMatchObject({
|
|
142
|
+
code: 'CLAUDE_SESSION_MISMATCH'
|
|
143
|
+
});
|
|
144
|
+
expect(fixture.calls[0][1]).toEqual([
|
|
145
|
+
'-p',
|
|
146
|
+
'--resume',
|
|
147
|
+
agent().providerSessionId,
|
|
148
|
+
'--output-format',
|
|
149
|
+
'stream-json',
|
|
150
|
+
'--verbose'
|
|
151
|
+
]);
|
|
152
|
+
});
|
|
153
|
+
it('does not disclose provider stderr in a failed-run error', async ()=>{
|
|
154
|
+
const api = await import('../../index.js');
|
|
155
|
+
const fixture = fakeSpawn([], 1);
|
|
156
|
+
const Runner = api.ClaudePrintRunner;
|
|
157
|
+
const runner = new Runner({
|
|
158
|
+
spawn: fixture.spawn,
|
|
159
|
+
processInspector: {
|
|
160
|
+
getIdentity: ()=>({
|
|
161
|
+
pid: 4242,
|
|
162
|
+
startedAt: 'provider-start'
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
fixture.spawn.mockImplementationOnce((...args)=>{
|
|
167
|
+
const child = fakeSpawn([], 1).spawn(...args);
|
|
168
|
+
child.stdin = new Writable({
|
|
169
|
+
final (callback) {
|
|
170
|
+
child.stderr.write('secret prompt echoed by provider');
|
|
171
|
+
child.stderr.end();
|
|
172
|
+
queueMicrotask(()=>child.emit('close', 1, null));
|
|
173
|
+
callback();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return child;
|
|
177
|
+
});
|
|
178
|
+
await expect(runner.run({
|
|
179
|
+
agent: agent(),
|
|
180
|
+
prompt: 'secret prompt',
|
|
181
|
+
firstRun: true,
|
|
182
|
+
onSpawn: vi.fn()
|
|
183
|
+
})).rejects.not.toThrow(/secret prompt/);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
//# sourceMappingURL=ClaudePrintRunner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/print/ClaudePrintRunner.test.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport { PassThrough, Writable } from 'node:stream';\nimport { describe, expect, it, vi } from 'vitest';\nimport type { PrintAgent } from '../../index.js';\n\nfunction agent(): PrintAgent {\n return {\n id: '11111111-1111-4111-8111-111111111111', name: 'reviewer', provider: 'claude', mode: 'print',\n cwd: '/project', providerSessionId: '22222222-2222-4222-8222-222222222222', state: 'running',\n sessionHealth: 'uninitialized', createdAt: '', updatedAt: '', lastActiveAt: null, lastResult: null,\n activeRun: null,\n };\n}\n\nfunction fakeSpawn(events: object[], exitCode = 0) {\n const calls: unknown[][] = [];\n const promptChunks: Buffer[] = [];\n const child = new EventEmitter() as any;\n child.pid = 4242;\n child.stdout = new PassThrough();\n child.stderr = new PassThrough();\n child.stdin = new Writable({\n write(chunk, _encoding, callback) { promptChunks.push(Buffer.from(chunk)); callback(); },\n final(callback) {\n for (const event of events) child.stdout.write(`${JSON.stringify(event)}\\n`);\n child.stdout.end();\n queueMicrotask(() => child.emit('close', exitCode, null));\n callback();\n },\n });\n const spawn = vi.fn((...args: unknown[]) => { calls.push(args); return child; });\n return { spawn, calls, promptChunks };\n}\n\ndescribe('ClaudePrintRunner', () => {\n it('starts a caller-assigned session and persists provider identity before stdin', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n expect(api).toHaveProperty('ClaudePrintRunner');\n const fixture = fakeSpawn([\n { type: 'system', subtype: 'init', session_id: agent().providerSessionId },\n { type: 'result', session_id: agent().providerSessionId, result: 'done' },\n ]);\n let persisted = false;\n const Runner = api.ClaudePrintRunner as new (options: unknown) => any;\n const runner = new Runner({ spawn: fixture.spawn, processInspector: {\n getIdentity: () => ({ pid: 4242, startedAt: 'provider-start' }),\n } });\n\n const result = await runner.run({\n agent: agent(), prompt: 'secret prompt', executable: 'claude-test', firstRun: true,\n onSpawn: async () => { expect(fixture.promptChunks).toHaveLength(0); persisted = true; },\n });\n\n expect(persisted).toBe(true);\n expect(fixture.calls[0]).toEqual([\n 'claude-test',\n ['-p', '--session-id', agent().providerSessionId, '--output-format', 'stream-json', '--verbose'],\n expect.objectContaining({ cwd: '/project', shell: false, stdio: ['pipe', 'pipe', 'pipe'] }),\n ]);\n expect(JSON.stringify(fixture.calls)).not.toContain('secret prompt');\n expect(Buffer.concat(fixture.promptChunks).toString()).toBe('secret prompt');\n expect(result).toEqual({ sessionId: agent().providerSessionId, result: 'done', exitCode: 0 });\n });\n\n it('uses exact resume and rejects a mismatched result session', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const fixture = fakeSpawn([{ type: 'result', session_id: 'wrong', result: 'nope' }]);\n const Runner = api.ClaudePrintRunner as new (options: unknown) => any;\n const runner = new Runner({ spawn: fixture.spawn, processInspector: {\n getIdentity: () => ({ pid: 4242, startedAt: 'provider-start' }),\n } });\n\n await expect(runner.run({\n agent: agent(), prompt: 'followup', executable: 'claude', firstRun: false, onSpawn: vi.fn(),\n })).rejects.toMatchObject({ code: 'CLAUDE_SESSION_MISMATCH' });\n expect(fixture.calls[0]![1]).toEqual([\n '-p', '--resume', agent().providerSessionId, '--output-format', 'stream-json', '--verbose',\n ]);\n });\n\n it('does not disclose provider stderr in a failed-run error', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n const fixture = fakeSpawn([], 1);\n const Runner = api.ClaudePrintRunner as new (options: unknown) => any;\n const runner = new Runner({ spawn: fixture.spawn, processInspector: {\n getIdentity: () => ({ pid: 4242, startedAt: 'provider-start' }),\n } });\n fixture.spawn.mockImplementationOnce((...args: unknown[]) => {\n const child = (fakeSpawn([], 1).spawn as any)(...args);\n child.stdin = new Writable({\n final(callback) {\n child.stderr.write('secret prompt echoed by provider');\n child.stderr.end();\n queueMicrotask(() => child.emit('close', 1, null));\n callback();\n },\n });\n return child;\n });\n\n await expect(runner.run({\n agent: agent(), prompt: 'secret prompt', firstRun: true, onSpawn: vi.fn(),\n })).rejects.not.toThrow(/secret prompt/);\n });\n});\n"],"names":["EventEmitter","PassThrough","Writable","describe","expect","it","vi","agent","id","name","provider","mode","cwd","providerSessionId","state","sessionHealth","createdAt","updatedAt","lastActiveAt","lastResult","activeRun","fakeSpawn","events","exitCode","calls","promptChunks","child","pid","stdout","stderr","stdin","write","chunk","_encoding","callback","push","Buffer","from","final","event","JSON","stringify","end","queueMicrotask","emit","spawn","fn","args","api","toHaveProperty","fixture","type","subtype","session_id","result","persisted","Runner","ClaudePrintRunner","runner","processInspector","getIdentity","startedAt","run","prompt","executable","firstRun","onSpawn","toHaveLength","toBe","toEqual","objectContaining","shell","stdio","not","toContain","concat","toString","sessionId","rejects","toMatchObject","code","mockImplementationOnce","toThrow"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
describe('print-agent public domain', ()=>{
|
|
3
|
+
it('exports a classified busy error without exposing prompt data', async ()=>{
|
|
4
|
+
const api = await import('../../index.js');
|
|
5
|
+
expect(api).toHaveProperty('PrintAgentBusyError');
|
|
6
|
+
const ErrorType = api.PrintAgentBusyError;
|
|
7
|
+
const error = new ErrorType('agent-id', 'reviewer');
|
|
8
|
+
expect(error).toMatchObject({
|
|
9
|
+
name: 'PrintAgentBusyError',
|
|
10
|
+
code: 'PRINT_AGENT_BUSY',
|
|
11
|
+
agentId: 'agent-id',
|
|
12
|
+
message: 'Print agent "reviewer" is busy.'
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=PrintAgent.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/__tests__/print/PrintAgent.test.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest';\n\ndescribe('print-agent public domain', () => {\n it('exports a classified busy error without exposing prompt data', async () => {\n const api = await import('../../index.js') as Record<string, unknown>;\n\n expect(api).toHaveProperty('PrintAgentBusyError');\n const ErrorType = api.PrintAgentBusyError as new (agentId: string, name: string) => Error & {\n code: string;\n agentId: string;\n };\n const error = new ErrorType('agent-id', 'reviewer');\n\n expect(error).toMatchObject({\n name: 'PrintAgentBusyError',\n code: 'PRINT_AGENT_BUSY',\n agentId: 'agent-id',\n message: 'Print agent \"reviewer\" is busy.',\n });\n });\n});\n"],"names":["describe","expect","it","api","toHaveProperty","ErrorType","PrintAgentBusyError","error","toMatchObject","name","code","agentId","message"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAS;AAE9CF,SAAS,6BAA6B;IAClCE,GAAG,gEAAgE;QAC/D,MAAMC,MAAM,MAAM,MAAM,CAAC;QAEzBF,OAAOE,KAAKC,cAAc,CAAC;QAC3B,MAAMC,YAAYF,IAAIG,mBAAmB;QAIzC,MAAMC,QAAQ,IAAIF,UAAU,YAAY;QAExCJ,OAAOM,OAAOC,aAAa,CAAC;YACxBC,MAAM;YACNC,MAAM;YACNC,SAAS;YACTC,SAAS;QACb;IACJ;AACJ"}
|