@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.
Files changed (110) hide show
  1. package/README.md +12 -4
  2. package/dist/AgentManager.d.ts +5 -0
  3. package/dist/AgentManager.d.ts.map +1 -1
  4. package/dist/AgentManager.js +21 -1
  5. package/dist/AgentManager.js.map +1 -1
  6. package/dist/__tests__/AgentManager.test.js +112 -0
  7. package/dist/__tests__/AgentManager.test.js.map +1 -1
  8. package/dist/__tests__/adapters/CodexAdapter.test.js +199 -0
  9. package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
  10. package/dist/__tests__/database/DurableAgentsDatabase.test.js +100 -0
  11. package/dist/__tests__/database/DurableAgentsDatabase.test.js.map +1 -0
  12. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js +7 -7
  13. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js.map +1 -1
  14. package/dist/__tests__/print/ClaudePrintAgentService.test.js +7 -7
  15. package/dist/__tests__/print/ClaudePrintAgentService.test.js.map +1 -1
  16. package/dist/__tests__/print/ClaudePrintRunner.test.js +1 -1
  17. package/dist/__tests__/print/ClaudePrintRunner.test.js.map +1 -1
  18. package/dist/__tests__/print/DurableAgent.test.js +17 -0
  19. package/dist/__tests__/print/DurableAgent.test.js.map +1 -0
  20. package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js +186 -0
  21. package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js.map +1 -0
  22. package/dist/__tests__/print/{PrintAgentStore.test.js → DurableAgentRepository.test.js} +63 -110
  23. package/dist/__tests__/print/DurableAgentRepository.test.js.map +1 -0
  24. package/dist/__tests__/utils/AgentRegistry.test.js +67 -0
  25. package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -1
  26. package/dist/adapters/AgentAdapter.d.ts +2 -0
  27. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  28. package/dist/adapters/AgentAdapter.js.map +1 -1
  29. package/dist/adapters/CodexAdapter.d.ts +1 -0
  30. package/dist/adapters/CodexAdapter.d.ts.map +1 -1
  31. package/dist/adapters/CodexAdapter.js +16 -6
  32. package/dist/adapters/CodexAdapter.js.map +1 -1
  33. package/dist/database/connection.d.ts +1 -0
  34. package/dist/database/connection.d.ts.map +1 -1
  35. package/dist/database/connection.js +20 -5
  36. package/dist/database/connection.js.map +1 -1
  37. package/dist/database/migrations/002_pins.sql +1 -0
  38. package/dist/database/migrations/003_durable_agents.sql +43 -0
  39. package/dist/durable/ClaudeCliProbe.d.ts.map +1 -0
  40. package/dist/{print → durable}/ClaudeCliProbe.js +1 -1
  41. package/dist/durable/ClaudeCliProbe.js.map +1 -0
  42. package/dist/{print → durable}/ClaudePrintAgentService.d.ts +11 -11
  43. package/dist/durable/ClaudePrintAgentService.d.ts.map +1 -0
  44. package/dist/{print → durable}/ClaudePrintAgentService.js +12 -12
  45. package/dist/durable/ClaudePrintAgentService.js.map +1 -0
  46. package/dist/{print → durable}/ClaudePrintRunner.d.ts +3 -3
  47. package/dist/durable/ClaudePrintRunner.d.ts.map +1 -0
  48. package/dist/{print → durable}/ClaudePrintRunner.js +2 -2
  49. package/dist/durable/ClaudePrintRunner.js.map +1 -0
  50. package/dist/durable/DurableAgent.d.ts +61 -0
  51. package/dist/durable/DurableAgent.d.ts.map +1 -0
  52. package/dist/durable/DurableAgent.js +46 -0
  53. package/dist/durable/DurableAgent.js.map +1 -0
  54. package/dist/durable/DurableAgentRepository.d.ts +60 -0
  55. package/dist/durable/DurableAgentRepository.d.ts.map +1 -0
  56. package/dist/durable/DurableAgentRepository.js +325 -0
  57. package/dist/durable/DurableAgentRepository.js.map +1 -0
  58. package/dist/index.d.ts +12 -12
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +7 -7
  61. package/dist/index.js.map +1 -1
  62. package/dist/utils/AgentRegistry.d.ts +5 -0
  63. package/dist/utils/AgentRegistry.d.ts.map +1 -1
  64. package/dist/utils/AgentRegistry.js +19 -2
  65. package/dist/utils/AgentRegistry.js.map +1 -1
  66. package/package.json +1 -1
  67. package/src/AgentManager.ts +21 -0
  68. package/src/__tests__/AgentManager.test.ts +117 -0
  69. package/src/__tests__/adapters/CodexAdapter.test.ts +132 -0
  70. package/src/__tests__/database/DurableAgentsDatabase.test.ts +77 -0
  71. package/src/__tests__/print/ClaudePrintAgent.integration.test.ts +6 -6
  72. package/src/__tests__/print/ClaudePrintAgentService.test.ts +7 -7
  73. package/src/__tests__/print/ClaudePrintRunner.test.ts +3 -3
  74. package/src/__tests__/print/{PrintAgent.test.ts → DurableAgent.test.ts} +6 -6
  75. package/src/__tests__/print/DurableAgentRepository.sqlite.test.ts +105 -0
  76. package/src/__tests__/print/DurableAgentRepository.test.ts +163 -0
  77. package/src/__tests__/utils/AgentRegistry.test.ts +68 -0
  78. package/src/adapters/AgentAdapter.ts +3 -0
  79. package/src/adapters/CodexAdapter.ts +14 -11
  80. package/src/database/connection.ts +18 -5
  81. package/src/database/migrations/002_pins.sql +1 -0
  82. package/src/database/migrations/003_durable_agents.sql +43 -0
  83. package/src/{print → durable}/ClaudeCliProbe.ts +1 -1
  84. package/src/{print → durable}/ClaudePrintAgentService.ts +21 -21
  85. package/src/{print → durable}/ClaudePrintRunner.ts +4 -4
  86. package/src/durable/DurableAgent.ts +91 -0
  87. package/src/durable/DurableAgentRepository.ts +307 -0
  88. package/src/index.ts +27 -26
  89. package/src/utils/AgentRegistry.ts +21 -0
  90. package/dist/__tests__/print/PrintAgent.test.js +0 -17
  91. package/dist/__tests__/print/PrintAgent.test.js.map +0 -1
  92. package/dist/__tests__/print/PrintAgentStore.test.js.map +0 -1
  93. package/dist/print/ClaudeCliProbe.d.ts.map +0 -1
  94. package/dist/print/ClaudeCliProbe.js.map +0 -1
  95. package/dist/print/ClaudePrintAgentService.d.ts.map +0 -1
  96. package/dist/print/ClaudePrintAgentService.js.map +0 -1
  97. package/dist/print/ClaudePrintRunner.d.ts.map +0 -1
  98. package/dist/print/ClaudePrintRunner.js.map +0 -1
  99. package/dist/print/PrintAgent.d.ts +0 -57
  100. package/dist/print/PrintAgent.d.ts.map +0 -1
  101. package/dist/print/PrintAgent.js +0 -42
  102. package/dist/print/PrintAgent.js.map +0 -1
  103. package/dist/print/PrintAgentStore.d.ts +0 -69
  104. package/dist/print/PrintAgentStore.d.ts.map +0 -1
  105. package/dist/print/PrintAgentStore.js +0 -484
  106. package/dist/print/PrintAgentStore.js.map +0 -1
  107. package/src/__tests__/print/PrintAgentStore.test.ts +0 -192
  108. package/src/print/PrintAgent.ts +0 -86
  109. package/src/print/PrintAgentStore.ts +0 -503
  110. /package/dist/{print → durable}/ClaudeCliProbe.d.ts +0 -0
@@ -0,0 +1,100 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import Database from 'better-sqlite3';
5
+ import { afterEach, describe, expect, it } from 'vitest';
6
+ import { DatabaseConnection } from '../../database/connection.js';
7
+ import { getSchemaVersion } from '../../database/schema.js';
8
+ const roots = [];
9
+ afterEach(()=>{
10
+ for (const root of roots.splice(0))fs.rmSync(root, {
11
+ recursive: true,
12
+ force: true
13
+ });
14
+ });
15
+ function dbPath() {
16
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-db-'));
17
+ roots.push(root);
18
+ return path.join(root, 'state', 'agents.db');
19
+ }
20
+ describe('durable agents schema', ()=>{
21
+ it('migrates to version 3 with durable constraints and indexes', ()=>{
22
+ const connection = new DatabaseConnection({
23
+ dbPath: dbPath()
24
+ });
25
+ expect(getSchemaVersion(connection)).toBe(3);
26
+ const table = connection.queryOne("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'durable_agents'");
27
+ expect(table?.sql).toContain("DEFAULT 'durable'");
28
+ expect(table?.sql).toContain("state IN ('ready','running','degraded')");
29
+ expect(connection.query("SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'durable_agents'").map(({ name })=>name)).toEqual(expect.arrayContaining([
30
+ 'idx_durable_agents_state',
31
+ 'idx_durable_agents_list'
32
+ ]));
33
+ connection.close();
34
+ });
35
+ it('enforces case-insensitive names and active-run consistency but permits new providers', ()=>{
36
+ const connection = new DatabaseConnection({
37
+ dbPath: dbPath()
38
+ });
39
+ const insert = (name, provider, state = 'ready')=>connection.execute(`
40
+ INSERT INTO durable_agents (
41
+ id, name, provider, mode, cwd, provider_session_id, state, session_health,
42
+ created_at, updated_at
43
+ ) VALUES (?, ?, ?, 'durable', '/tmp', ?, ?, 'uninitialized', ?, ?)
44
+ `, [
45
+ crypto.randomUUID(),
46
+ name,
47
+ provider,
48
+ crypto.randomUUID(),
49
+ state,
50
+ new Date().toISOString(),
51
+ new Date().toISOString()
52
+ ]);
53
+ expect(()=>insert('Alpha', 'future-provider')).not.toThrow();
54
+ expect(()=>insert('alpha', 'claude')).toThrow(/UNIQUE/i);
55
+ expect(()=>insert('Broken', 'claude', 'running')).toThrow(/CHECK/i);
56
+ connection.close();
57
+ });
58
+ });
59
+ describe('readonly DatabaseConnection', ()=>{
60
+ it('opens an already migrated database without changing it', ()=>{
61
+ const file = dbPath();
62
+ const writable = new DatabaseConnection({
63
+ dbPath: file
64
+ });
65
+ writable.close();
66
+ const before = fs.statSync(file).mtimeMs;
67
+ const readonly = new DatabaseConnection({
68
+ dbPath: file,
69
+ readonly: true
70
+ });
71
+ expect(readonly.queryOne('PRAGMA user_version')?.user_version).toBe(3);
72
+ readonly.close();
73
+ expect(fs.statSync(file).mtimeMs).toBe(before);
74
+ });
75
+ it('does not create a missing database or migrate an old one', ()=>{
76
+ const missing = dbPath();
77
+ expect(()=>new DatabaseConnection({
78
+ dbPath: missing,
79
+ readonly: true
80
+ })).toThrow(/readonly.*exist/i);
81
+ expect(fs.existsSync(missing)).toBe(false);
82
+ fs.mkdirSync(path.dirname(missing), {
83
+ recursive: true
84
+ });
85
+ const raw = new Database(missing);
86
+ raw.pragma('user_version = 2');
87
+ raw.close();
88
+ expect(()=>new DatabaseConnection({
89
+ dbPath: missing,
90
+ readonly: true
91
+ })).toThrow(/schema version 3/i);
92
+ expect(new Database(missing, {
93
+ readonly: true
94
+ }).pragma('user_version', {
95
+ simple: true
96
+ })).toBe(2);
97
+ });
98
+ });
99
+
100
+ //# sourceMappingURL=DurableAgentsDatabase.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/database/DurableAgentsDatabase.test.ts"],"sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport Database from 'better-sqlite3';\nimport { afterEach, describe, expect, it } from 'vitest';\nimport { DatabaseConnection } from '../../database/connection.js';\nimport { getSchemaVersion } from '../../database/schema.js';\n\nconst roots: string[] = [];\n\nafterEach(() => {\n for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });\n});\n\nfunction dbPath(): string {\n const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-db-'));\n roots.push(root);\n return path.join(root, 'state', 'agents.db');\n}\n\ndescribe('durable agents schema', () => {\n it('migrates to version 3 with durable constraints and indexes', () => {\n const connection = new DatabaseConnection({ dbPath: dbPath() });\n expect(getSchemaVersion(connection)).toBe(3);\n const table = connection.queryOne<{ sql: string }>(\n \"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'durable_agents'\",\n );\n expect(table?.sql).toContain(\"DEFAULT 'durable'\");\n expect(table?.sql).toContain(\"state IN ('ready','running','degraded')\");\n expect(connection.query<{ name: string }>(\n \"SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'durable_agents'\",\n ).map(({ name }) => name)).toEqual(expect.arrayContaining([\n 'idx_durable_agents_state', 'idx_durable_agents_list',\n ]));\n connection.close();\n });\n\n it('enforces case-insensitive names and active-run consistency but permits new providers', () => {\n const connection = new DatabaseConnection({ dbPath: dbPath() });\n const insert = (name: string, provider: string, state = 'ready') => connection.execute(`\n INSERT INTO durable_agents (\n id, name, provider, mode, cwd, provider_session_id, state, session_health,\n created_at, updated_at\n ) VALUES (?, ?, ?, 'durable', '/tmp', ?, ?, 'uninitialized', ?, ?)\n `, [crypto.randomUUID(), name, provider, crypto.randomUUID(), state, new Date().toISOString(), new Date().toISOString()]);\n expect(() => insert('Alpha', 'future-provider')).not.toThrow();\n expect(() => insert('alpha', 'claude')).toThrow(/UNIQUE/i);\n expect(() => insert('Broken', 'claude', 'running')).toThrow(/CHECK/i);\n connection.close();\n });\n});\n\ndescribe('readonly DatabaseConnection', () => {\n it('opens an already migrated database without changing it', () => {\n const file = dbPath();\n const writable = new DatabaseConnection({ dbPath: file });\n writable.close();\n const before = fs.statSync(file).mtimeMs;\n const readonly = new DatabaseConnection({ dbPath: file, readonly: true });\n expect(readonly.queryOne<{ user_version: number }>('PRAGMA user_version')?.user_version).toBe(3);\n readonly.close();\n expect(fs.statSync(file).mtimeMs).toBe(before);\n });\n\n it('does not create a missing database or migrate an old one', () => {\n const missing = dbPath();\n expect(() => new DatabaseConnection({ dbPath: missing, readonly: true })).toThrow(/readonly.*exist/i);\n expect(fs.existsSync(missing)).toBe(false);\n\n fs.mkdirSync(path.dirname(missing), { recursive: true });\n const raw = new Database(missing);\n raw.pragma('user_version = 2');\n raw.close();\n expect(() => new DatabaseConnection({ dbPath: missing, readonly: true })).toThrow(/schema version 3/i);\n expect(new Database(missing, { readonly: true }).pragma('user_version', { simple: true })).toBe(2);\n });\n});\n"],"names":["fs","os","path","Database","afterEach","describe","expect","it","DatabaseConnection","getSchemaVersion","roots","root","splice","rmSync","recursive","force","dbPath","mkdtempSync","join","tmpdir","push","connection","toBe","table","queryOne","sql","toContain","query","map","name","toEqual","arrayContaining","close","insert","provider","state","execute","crypto","randomUUID","Date","toISOString","not","toThrow","file","writable","before","statSync","mtimeMs","readonly","user_version","missing","existsSync","mkdirSync","dirname","raw","pragma","simple"],"mappings":"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"}
@@ -3,7 +3,7 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { afterEach, describe, expect, it } from 'vitest';
6
- import { ClaudeCliProbe, ClaudePrintAgentService, ClaudePrintRunner, PrintAgentStore } from '../../index.js';
6
+ import { ClaudeCliProbe, ClaudePrintAgentService, ClaudePrintRunner, DurableAgentRepository } from '../../index.js';
7
7
  const roots = [];
8
8
  const originalCapture = process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE;
9
9
  afterEach(()=>{
@@ -14,20 +14,20 @@ afterEach(()=>{
14
14
  force: true
15
15
  });
16
16
  });
17
- describe('Claude print-agent fake-provider journey', ()=>{
17
+ describe('Claude durable-agent fake-provider journey', ()=>{
18
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-'));
19
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-integration-'));
20
20
  roots.push(root);
21
21
  const cwd = path.join(root, 'project');
22
22
  fs.mkdirSync(cwd);
23
23
  const capture = path.join(root, 'capture.jsonl');
24
24
  process.env.AI_DEVKIT_FAKE_CLAUDE_CAPTURE = capture;
25
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')
26
+ const repository = new DurableAgentRepository({
27
+ dbPath: path.join(root, 'state', 'agents.db')
28
28
  });
29
29
  const service = new ClaudePrintAgentService({
30
- store,
30
+ repository,
31
31
  probe: new ClaudeCliProbe({
32
32
  executable
33
33
  }),
@@ -58,7 +58,7 @@ describe('Claude print-agent fake-provider journey', ()=>{
58
58
  });
59
59
  expect(invocations[1].args).toContain('--resume');
60
60
  expect(invocations[1].args[invocations[1].args.indexOf('--resume') + 1]).toBe(created.providerSessionId);
61
- const persisted = await store.getById(created.id);
61
+ const persisted = await repository.getById(created.id);
62
62
  expect(persisted).toMatchObject({
63
63
  state: 'ready',
64
64
  sessionHealth: 'healthy'
@@ -1 +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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"}
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 DurableAgentRepository,\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 durable-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(), 'durable-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 repository = new DurableAgentRepository({ dbPath: path.join(root, 'state', 'agents.db') });\n const service = new ClaudePrintAgentService({\n repository,\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 repository.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","DurableAgentRepository","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","repository","dbPath","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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"}
@@ -9,7 +9,7 @@ describe('ClaudePrintAgentService', ()=>{
9
9
  version: '2.1.220'
10
10
  })
11
11
  };
12
- const store = {
12
+ const repository = {
13
13
  create: vi.fn().mockResolvedValue({
14
14
  id: 'agent-id',
15
15
  name: 'reviewer'
@@ -20,7 +20,7 @@ describe('ClaudePrintAgentService', ()=>{
20
20
  };
21
21
  const Service = api.ClaudePrintAgentService;
22
22
  await expect(new Service({
23
- store,
23
+ repository,
24
24
  probe,
25
25
  runner
26
26
  }).create({
@@ -30,7 +30,7 @@ describe('ClaudePrintAgentService', ()=>{
30
30
  id: 'agent-id'
31
31
  });
32
32
  expect(probe.validate).toHaveBeenCalledOnce();
33
- expect(store.create).toHaveBeenCalledWith({
33
+ expect(repository.create).toHaveBeenCalledWith({
34
34
  name: 'reviewer',
35
35
  cwd: '/project'
36
36
  });
@@ -44,7 +44,7 @@ describe('ClaudePrintAgentService', ()=>{
44
44
  providerSessionId: 'session',
45
45
  sessionHealth: 'uninitialized'
46
46
  };
47
- const store = {
47
+ const repository = {
48
48
  resolve: vi.fn().mockResolvedValue(base),
49
49
  acquireRun: vi.fn().mockResolvedValueOnce({
50
50
  agent: base,
@@ -74,7 +74,7 @@ describe('ClaudePrintAgentService', ()=>{
74
74
  };
75
75
  const Service = api.ClaudePrintAgentService;
76
76
  const service = new Service({
77
- store,
77
+ repository,
78
78
  probe: {
79
79
  validate: vi.fn()
80
80
  },
@@ -93,11 +93,11 @@ describe('ClaudePrintAgentService', ()=>{
93
93
  firstRun: false,
94
94
  executable: 'fake-claude'
95
95
  });
96
- expect(store.recordProviderProcess).toHaveBeenCalledWith('id', 'one', {
96
+ expect(repository.recordProviderProcess).toHaveBeenCalledWith('id', 'one', {
97
97
  pid: 42,
98
98
  startedAt: 'start'
99
99
  });
100
- expect(store.completeRun).toHaveBeenCalledWith('id', 'one', expect.objectContaining({
100
+ expect(repository.completeRun).toHaveBeenCalledWith('id', 'one', expect.objectContaining({
101
101
  status: 'succeeded',
102
102
  exitCode: 0,
103
103
  sessionHealth: 'healthy'
@@ -1 +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"}
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 repository = { 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({ repository, probe, runner }).create({ name: 'reviewer', cwd: '/project' }))\n .resolves.toMatchObject({ id: 'agent-id' });\n expect(probe.validate).toHaveBeenCalledOnce();\n expect(repository.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 repository = {\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({ repository, 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(repository.recordProviderProcess).toHaveBeenCalledWith('id', 'one', { pid: 42, startedAt: 'start' });\n expect(repository.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","repository","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,aAAa;YAAEC,QAAQV,GAAGK,EAAE,GAAGC,iBAAiB,CAAC;gBAAEK,IAAI;gBAAYC,MAAM;YAAW;QAAG;QAC7F,MAAMC,SAAS;YAAEC,KAAKd,GAAGK,EAAE;QAAG;QAC9B,MAAMU,UAAUd,IAAIe,uBAAuB;QAE3C,MAAMlB,OAAO,IAAIiB,QAAQ;YAAEN;YAAYN;YAAOU;QAAO,GAAGH,MAAM,CAAC;YAAEE,MAAM;YAAYK,KAAK;QAAW,IAC9FC,QAAQ,CAACC,aAAa,CAAC;YAAER,IAAI;QAAW;QAC7Cb,OAAOK,MAAMC,QAAQ,EAAEgB,oBAAoB;QAC3CtB,OAAOW,WAAWC,MAAM,EAAEW,oBAAoB,CAAC;YAAET,MAAM;YAAYK,KAAK;QAAW;QACnFnB,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,aAAa;YACfkB,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;YAAYN,OAAO;gBAAEC,UAAUJ,GAAGK,EAAE;YAAG;YAAGQ;YAAQN,YAAY;QAAc;QAE1G,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,WAAWuB,qBAAqB,EAAEX,oBAAoB,CAAC,MAAM,OAAO;YAAEgB,KAAK;YAAIC,WAAW;QAAQ;QACzGxC,OAAOW,WAAWwB,WAAW,EAAEZ,oBAAoB,CAAC,MAAM,OAAOvB,OAAOkD,gBAAgB,CAAC;YACrFC,QAAQ;YAAaR,UAAU;YAAGf,eAAe;QACrD;IACJ;AACJ"}
@@ -6,7 +6,7 @@ function agent() {
6
6
  id: '11111111-1111-4111-8111-111111111111',
7
7
  name: 'reviewer',
8
8
  provider: 'claude',
9
- mode: 'print',
9
+ mode: 'durable',
10
10
  cwd: '/project',
11
11
  providerSessionId: '22222222-2222-4222-8222-222222222222',
12
12
  state: 'running',
@@ -1 +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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"}
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 { DurableAgent } from '../../index.js';\n\nfunction agent(): DurableAgent {\n return {\n id: '11111111-1111-4111-8111-111111111111', name: 'reviewer', provider: 'claude', mode: 'durable',\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('durable-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('DurableAgentBusyError');
6
+ const ErrorType = api.DurableAgentBusyError;
7
+ const error = new ErrorType('agent-id', 'reviewer');
8
+ expect(error).toMatchObject({
9
+ name: 'DurableAgentBusyError',
10
+ code: 'DURABLE_AGENT_BUSY',
11
+ agentId: 'agent-id',
12
+ message: 'Durable agent "reviewer" is busy.'
13
+ });
14
+ });
15
+ });
16
+
17
+ //# sourceMappingURL=DurableAgent.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/print/DurableAgent.test.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest';\n\ndescribe('durable-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('DurableAgentBusyError');\n const ErrorType = api.DurableAgentBusyError 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: 'DurableAgentBusyError',\n code: 'DURABLE_AGENT_BUSY',\n agentId: 'agent-id',\n message: 'Durable agent \"reviewer\" is busy.',\n });\n });\n});\n"],"names":["describe","expect","it","api","toHaveProperty","ErrorType","DurableAgentBusyError","error","toMatchObject","name","code","agentId","message"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAS;AAE9CF,SAAS,+BAA+B;IACpCE,GAAG,gEAAgE;QAC/D,MAAMC,MAAM,MAAM,MAAM,CAAC;QAEzBF,OAAOE,KAAKC,cAAc,CAAC;QAC3B,MAAMC,YAAYF,IAAIG,qBAAqB;QAI3C,MAAMC,QAAQ,IAAIF,UAAU,YAAY;QAExCJ,OAAOM,OAAOC,aAAa,CAAC;YACxBC,MAAM;YACNC,MAAM;YACNC,SAAS;YACTC,SAAS;QACb;IACJ;AACJ"}
@@ -0,0 +1,186 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import Database from 'better-sqlite3';
5
+ import { afterEach, describe, expect, it } from 'vitest';
6
+ import { DurableAgentRepository } from '../../durable/DurableAgentRepository.js';
7
+ const roots = [];
8
+ afterEach(()=>{
9
+ for (const root of roots.splice(0))fs.rmSync(root, {
10
+ recursive: true,
11
+ force: true
12
+ });
13
+ });
14
+ function fixture() {
15
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-sqlite-'));
16
+ roots.push(root);
17
+ const cwd = path.join(root, 'project');
18
+ fs.mkdirSync(cwd);
19
+ return {
20
+ root,
21
+ cwd,
22
+ dbPath: path.join(root, 'state', 'agents.db')
23
+ };
24
+ }
25
+ describe('DurableAgentRepository SQLite concurrency', ()=>{
26
+ it('allows exactly one acquisition across two connections', async ()=>{
27
+ const { cwd, dbPath } = fixture();
28
+ const identity = {
29
+ pid: process.pid,
30
+ startedAt: 'owner-start'
31
+ };
32
+ const processInspector = {
33
+ getIdentity: (pid)=>pid === process.pid ? identity : null
34
+ };
35
+ const first = new DurableAgentRepository({
36
+ dbPath,
37
+ processInspector
38
+ });
39
+ const second = new DurableAgentRepository({
40
+ dbPath,
41
+ processInspector
42
+ });
43
+ const agent = await first.create({
44
+ name: 'race',
45
+ cwd
46
+ });
47
+ const results = await Promise.allSettled([
48
+ first.acquireRun(agent.id),
49
+ second.acquireRun(agent.id)
50
+ ]);
51
+ expect(results.filter(({ status })=>status === 'fulfilled')).toHaveLength(1);
52
+ const rejected = results.find(({ status })=>status === 'rejected');
53
+ expect(rejected).toMatchObject({
54
+ reason: {
55
+ code: 'DURABLE_AGENT_BUSY'
56
+ }
57
+ });
58
+ });
59
+ it('accepts deprecated lock options without creating lock artifacts', async ()=>{
60
+ const { root, cwd, dbPath } = fixture();
61
+ const repository = new DurableAgentRepository({
62
+ dbPath,
63
+ lockTimeoutMs: 1,
64
+ incompleteLockGraceMs: 1,
65
+ mutationLockStaleMs: 1
66
+ });
67
+ await repository.create({
68
+ name: 'lockless',
69
+ cwd
70
+ });
71
+ expect(fs.existsSync(`${dbPath}.lock`)).toBe(false);
72
+ expect(fs.existsSync(path.join(root, 'state', 'durable-agent-locks'))).toBe(false);
73
+ });
74
+ it('keeps readonly listing pure', async ()=>{
75
+ const { cwd, dbPath } = fixture();
76
+ const live = new Map([
77
+ [
78
+ process.pid,
79
+ 'owner-start'
80
+ ]
81
+ ]);
82
+ const processInspector = {
83
+ getIdentity: (pid)=>{
84
+ const startedAt = live.get(pid);
85
+ return startedAt ? {
86
+ pid,
87
+ startedAt
88
+ } : null;
89
+ }
90
+ };
91
+ const writable = new DurableAgentRepository({
92
+ dbPath,
93
+ processInspector
94
+ });
95
+ const agent = await writable.create({
96
+ name: 'readonly',
97
+ cwd
98
+ });
99
+ await writable.acquireRun(agent.id);
100
+ live.clear();
101
+ const readonly = new DurableAgentRepository({
102
+ dbPath,
103
+ readonly: true,
104
+ processInspector
105
+ });
106
+ expect((await readonly.list())[0]?.state).toBe('running');
107
+ });
108
+ it('rejects stale tokens and caps the persisted completion summary', async ()=>{
109
+ const { cwd, dbPath } = fixture();
110
+ const processInspector = {
111
+ getIdentity: (pid)=>({
112
+ pid,
113
+ startedAt: 'owner-start'
114
+ })
115
+ };
116
+ const repository = new DurableAgentRepository({
117
+ dbPath,
118
+ processInspector
119
+ });
120
+ const agent = await repository.create({
121
+ name: 'token',
122
+ cwd
123
+ });
124
+ const run = await repository.acquireRun(agent.id);
125
+ await expect(repository.recordProviderProcess(agent.id, 'stale', {
126
+ pid: 42,
127
+ startedAt: 'provider'
128
+ })).rejects.toMatchObject({
129
+ code: 'DURABLE_AGENT_REPOSITORY'
130
+ });
131
+ const completed = await repository.completeRun(agent.id, run.token, {
132
+ status: 'succeeded',
133
+ exitCode: 0,
134
+ summary: 'x'.repeat(5000),
135
+ sessionHealth: 'healthy'
136
+ });
137
+ expect(completed.lastResult?.summary).toHaveLength(4096);
138
+ });
139
+ it('does not reconcile over ownership changed after process inspection', async ()=>{
140
+ const { cwd, dbPath } = fixture();
141
+ let inspect;
142
+ const processInspector = {
143
+ getIdentity: (pid)=>{
144
+ if (inspect) inspect();
145
+ return inspect ? null : {
146
+ pid,
147
+ startedAt: 'owner-start'
148
+ };
149
+ }
150
+ };
151
+ const repository = new DurableAgentRepository({
152
+ dbPath,
153
+ processInspector
154
+ });
155
+ const agent = await repository.create({
156
+ name: 'cas',
157
+ cwd
158
+ });
159
+ await repository.acquireRun(agent.id);
160
+ const other = new Database(dbPath);
161
+ inspect = ()=>{
162
+ inspect = undefined;
163
+ other.prepare(`UPDATE durable_agents SET
164
+ active_run_token = 'replacement-token', active_owner_started_at = 'replacement-owner'
165
+ WHERE id = ?`).run(agent.id);
166
+ };
167
+ await repository.reconcile();
168
+ expect(other.prepare('SELECT state, active_run_token FROM durable_agents WHERE id = ?').get(agent.id)).toEqual({
169
+ state: 'running',
170
+ active_run_token: 'replacement-token'
171
+ });
172
+ other.close();
173
+ });
174
+ it('maps a corrupt database to a store error', ()=>{
175
+ const { dbPath } = fixture();
176
+ fs.mkdirSync(path.dirname(dbPath), {
177
+ recursive: true
178
+ });
179
+ fs.writeFileSync(dbPath, 'not sqlite');
180
+ expect(()=>new DurableAgentRepository({
181
+ dbPath
182
+ })).toThrow(/Cannot open durable-agent database/);
183
+ });
184
+ });
185
+
186
+ //# sourceMappingURL=DurableAgentRepository.sqlite.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/print/DurableAgentRepository.sqlite.test.ts"],"sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport Database from 'better-sqlite3';\nimport { afterEach, describe, expect, it } from 'vitest';\nimport { DurableAgentRepository } from '../../durable/DurableAgentRepository.js';\n\nconst roots: string[] = [];\nafterEach(() => {\n for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });\n});\n\nfunction fixture() {\n const root = fs.mkdtempSync(path.join(os.tmpdir(), 'durable-agent-sqlite-'));\n roots.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 SQLite concurrency', () => {\n it('allows exactly one acquisition across two connections', async () => {\n const { cwd, dbPath } = fixture();\n const identity = { pid: process.pid, startedAt: 'owner-start' };\n const processInspector = { getIdentity: (pid: number) => pid === process.pid ? identity : null };\n const first = new DurableAgentRepository({ dbPath, processInspector });\n const second = new DurableAgentRepository({ dbPath, processInspector });\n const agent = await first.create({ name: 'race', cwd });\n const results = await Promise.allSettled([first.acquireRun(agent.id), second.acquireRun(agent.id)]);\n expect(results.filter(({ status }) => status === 'fulfilled')).toHaveLength(1);\n const rejected = results.find(({ status }) => status === 'rejected');\n expect(rejected).toMatchObject({ reason: { code: 'DURABLE_AGENT_BUSY' } });\n });\n\n it('accepts deprecated lock options without creating lock artifacts', async () => {\n const { root, cwd, dbPath } = fixture();\n const repository = new DurableAgentRepository({\n dbPath, lockTimeoutMs: 1, incompleteLockGraceMs: 1, mutationLockStaleMs: 1,\n });\n await repository.create({ name: 'lockless', cwd });\n expect(fs.existsSync(`${dbPath}.lock`)).toBe(false);\n expect(fs.existsSync(path.join(root, 'state', 'durable-agent-locks'))).toBe(false);\n });\n\n it('keeps readonly listing pure', async () => {\n const { cwd, dbPath } = fixture();\n const live = new Map([[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 writable = new DurableAgentRepository({ dbPath, processInspector });\n const agent = await writable.create({ name: 'readonly', cwd });\n await writable.acquireRun(agent.id);\n live.clear();\n const readonly = new DurableAgentRepository({ dbPath, readonly: true, processInspector });\n expect((await readonly.list())[0]?.state).toBe('running');\n });\n\n it('rejects stale tokens and caps the persisted completion summary', async () => {\n const { cwd, dbPath } = fixture();\n const processInspector = { getIdentity: (pid: number) => ({ pid, startedAt: 'owner-start' }) };\n const repository = new DurableAgentRepository({ dbPath, processInspector });\n const agent = await repository.create({ name: 'token', cwd });\n const run = await repository.acquireRun(agent.id);\n await expect(repository.recordProviderProcess(agent.id, 'stale', { pid: 42, startedAt: 'provider' }))\n .rejects.toMatchObject({ code: 'DURABLE_AGENT_REPOSITORY' });\n const completed = await repository.completeRun(agent.id, run.token, {\n status: 'succeeded', exitCode: 0, summary: 'x'.repeat(5000), sessionHealth: 'healthy',\n });\n expect(completed.lastResult?.summary).toHaveLength(4096);\n });\n\n it('does not reconcile over ownership changed after process inspection', async () => {\n const { cwd, dbPath } = fixture();\n let inspect: (() => void) | undefined;\n const processInspector = { getIdentity: (pid: number) => {\n if (inspect) inspect();\n return inspect ? null : { pid, startedAt: 'owner-start' };\n } };\n const repository = new DurableAgentRepository({ dbPath, processInspector });\n const agent = await repository.create({ name: 'cas', cwd });\n await repository.acquireRun(agent.id);\n const other = new Database(dbPath);\n inspect = () => {\n inspect = undefined;\n other.prepare(`UPDATE durable_agents SET\n active_run_token = 'replacement-token', active_owner_started_at = 'replacement-owner'\n WHERE id = ?`).run(agent.id);\n };\n\n await repository.reconcile();\n\n expect(other.prepare('SELECT state, active_run_token FROM durable_agents WHERE id = ?').get(agent.id))\n .toEqual({ state: 'running', active_run_token: 'replacement-token' });\n other.close();\n });\n\n it('maps a corrupt database to a store error', () => {\n const { dbPath } = fixture();\n fs.mkdirSync(path.dirname(dbPath), { recursive: true });\n fs.writeFileSync(dbPath, 'not sqlite');\n expect(() => new DurableAgentRepository({ dbPath })).toThrow(/Cannot open durable-agent database/);\n });\n});\n"],"names":["fs","os","path","Database","afterEach","describe","expect","it","DurableAgentRepository","roots","root","splice","rmSync","recursive","force","fixture","mkdtempSync","join","tmpdir","push","cwd","mkdirSync","dbPath","identity","pid","process","startedAt","processInspector","getIdentity","first","second","agent","create","name","results","Promise","allSettled","acquireRun","id","filter","status","toHaveLength","rejected","find","toMatchObject","reason","code","repository","lockTimeoutMs","incompleteLockGraceMs","mutationLockStaleMs","existsSync","toBe","live","Map","get","writable","clear","readonly","list","state","run","recordProviderProcess","rejects","completed","completeRun","token","exitCode","summary","repeat","sessionHealth","lastResult","inspect","other","undefined","prepare","reconcile","toEqual","active_run_token","close","dirname","writeFileSync","toThrow"],"mappings":"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"}