@ai-devkit/agent-manager 0.38.0 → 0.39.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 (94) hide show
  1. package/dist/__tests__/harnesses/devin/DevinAdapter.test.js +597 -0
  2. package/dist/__tests__/harnesses/devin/DevinAdapter.test.js.map +1 -0
  3. package/dist/__tests__/harnesses/devin/DevinAgentMapper.test.js +91 -0
  4. package/dist/__tests__/harnesses/devin/DevinAgentMapper.test.js.map +1 -0
  5. package/dist/__tests__/harnesses/devin/DevinSessionLocator.test.js +192 -0
  6. package/dist/__tests__/harnesses/devin/DevinSessionLocator.test.js.map +1 -0
  7. package/dist/__tests__/harnesses/devin/DevinSessionParser.test.js +407 -0
  8. package/dist/__tests__/harnesses/devin/DevinSessionParser.test.js.map +1 -0
  9. package/dist/__tests__/harnesses/devin/fixtures.js +95 -0
  10. package/dist/__tests__/harnesses/devin/fixtures.js.map +1 -0
  11. package/dist/__tests__/harnesses/readiness/AgentReadiness.test.js +64 -1
  12. package/dist/__tests__/harnesses/readiness/AgentReadiness.test.js.map +1 -1
  13. package/dist/__tests__/terminal/GhosttyBackend.test.js +334 -0
  14. package/dist/__tests__/terminal/GhosttyBackend.test.js.map +1 -0
  15. package/dist/adapters/AgentAdapter.d.ts +1 -1
  16. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  17. package/dist/adapters/AgentAdapter.js +2 -1
  18. package/dist/adapters/AgentAdapter.js.map +1 -1
  19. package/dist/harnesses/devin/DevinAdapter.d.ts +39 -0
  20. package/dist/harnesses/devin/DevinAdapter.d.ts.map +1 -0
  21. package/dist/harnesses/devin/DevinAdapter.js +177 -0
  22. package/dist/harnesses/devin/DevinAdapter.js.map +1 -0
  23. package/dist/harnesses/devin/DevinAgentMapper.d.ts +11 -0
  24. package/dist/harnesses/devin/DevinAgentMapper.d.ts.map +1 -0
  25. package/dist/harnesses/devin/DevinAgentMapper.js +35 -0
  26. package/dist/harnesses/devin/DevinAgentMapper.js.map +1 -0
  27. package/dist/harnesses/devin/DevinSessionLocator.d.ts +46 -0
  28. package/dist/harnesses/devin/DevinSessionLocator.d.ts.map +1 -0
  29. package/dist/harnesses/devin/DevinSessionLocator.js +176 -0
  30. package/dist/harnesses/devin/DevinSessionLocator.js.map +1 -0
  31. package/dist/harnesses/devin/DevinSessionParser.d.ts +29 -0
  32. package/dist/harnesses/devin/DevinSessionParser.d.ts.map +1 -0
  33. package/dist/harnesses/devin/DevinSessionParser.js +166 -0
  34. package/dist/harnesses/devin/DevinSessionParser.js.map +1 -0
  35. package/dist/harnesses/devin/readiness.d.ts +3 -0
  36. package/dist/harnesses/devin/readiness.d.ts.map +1 -0
  37. package/dist/harnesses/devin/readiness.js +39 -0
  38. package/dist/harnesses/devin/readiness.js.map +1 -0
  39. package/dist/harnesses/index.d.ts.map +1 -1
  40. package/dist/harnesses/index.js +3 -1
  41. package/dist/harnesses/index.js.map +1 -1
  42. package/dist/harnesses/readiness/AgentReadiness.d.ts.map +1 -1
  43. package/dist/harnesses/readiness/AgentReadiness.js +3 -1
  44. package/dist/harnesses/readiness/AgentReadiness.js.map +1 -1
  45. package/dist/harnesses/readiness/types.d.ts +1 -1
  46. package/dist/harnesses/readiness/types.d.ts.map +1 -1
  47. package/dist/harnesses/runtimeProfiles.d.ts.map +1 -1
  48. package/dist/harnesses/runtimeProfiles.js +2 -1
  49. package/dist/harnesses/runtimeProfiles.js.map +1 -1
  50. package/dist/terminal/TtyWriter.d.ts.map +1 -1
  51. package/dist/terminal/TtyWriter.js +4 -3
  52. package/dist/terminal/TtyWriter.js.map +1 -1
  53. package/dist/terminal/backends/ghostty.d.ts +3 -0
  54. package/dist/terminal/backends/ghostty.d.ts.map +1 -0
  55. package/dist/terminal/backends/ghostty.js +161 -0
  56. package/dist/terminal/backends/ghostty.js.map +1 -0
  57. package/dist/terminal/backends/index.d.ts.map +1 -1
  58. package/dist/terminal/backends/index.js +2 -0
  59. package/dist/terminal/backends/index.js.map +1 -1
  60. package/dist/terminal/backends/shared.d.ts +2 -0
  61. package/dist/terminal/backends/shared.d.ts.map +1 -1
  62. package/dist/terminal/backends/shared.js +4 -1
  63. package/dist/terminal/backends/shared.js.map +1 -1
  64. package/dist/terminal/types.d.ts +1 -0
  65. package/dist/terminal/types.d.ts.map +1 -1
  66. package/dist/terminal/types.js +1 -0
  67. package/dist/terminal/types.js.map +1 -1
  68. package/dist/utils/process.d.ts +2 -0
  69. package/dist/utils/process.d.ts.map +1 -1
  70. package/dist/utils/process.js +3 -0
  71. package/dist/utils/process.js.map +1 -1
  72. package/package.json +1 -1
  73. package/src/__tests__/harnesses/devin/DevinAdapter.test.ts +292 -0
  74. package/src/__tests__/harnesses/devin/DevinAgentMapper.test.ts +88 -0
  75. package/src/__tests__/harnesses/devin/DevinSessionLocator.test.ts +139 -0
  76. package/src/__tests__/harnesses/devin/DevinSessionParser.test.ts +218 -0
  77. package/src/__tests__/harnesses/devin/fixtures.ts +144 -0
  78. package/src/__tests__/harnesses/readiness/AgentReadiness.test.ts +41 -0
  79. package/src/__tests__/terminal/GhosttyBackend.test.ts +271 -0
  80. package/src/adapters/AgentAdapter.ts +1 -0
  81. package/src/harnesses/devin/DevinAdapter.ts +224 -0
  82. package/src/harnesses/devin/DevinAgentMapper.ts +43 -0
  83. package/src/harnesses/devin/DevinSessionLocator.ts +230 -0
  84. package/src/harnesses/devin/DevinSessionParser.ts +236 -0
  85. package/src/harnesses/devin/readiness.ts +36 -0
  86. package/src/harnesses/index.ts +2 -0
  87. package/src/harnesses/readiness/AgentReadiness.ts +2 -0
  88. package/src/harnesses/runtimeProfiles.ts +1 -0
  89. package/src/terminal/TtyWriter.ts +5 -5
  90. package/src/terminal/backends/ghostty.ts +180 -0
  91. package/src/terminal/backends/index.ts +2 -0
  92. package/src/terminal/backends/shared.ts +6 -1
  93. package/src/terminal/types.ts +1 -0
  94. package/src/utils/process.ts +5 -0
@@ -0,0 +1,597 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { AgentStatus } from "../../../adapters/AgentAdapter.js";
6
+ import { DevinAdapter } from "../../../harnesses/devin/DevinAdapter.js";
7
+ import { writeDatabase, writeLocks } from "./fixtures.js";
8
+ describe("DevinAdapter", ()=>{
9
+ let adapter;
10
+ let tmpDir;
11
+ let dbPath;
12
+ let locksDir;
13
+ beforeEach(()=>{
14
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "devin-adapter-test-"));
15
+ dbPath = path.join(tmpDir, "sessions.db");
16
+ locksDir = path.join(tmpDir, "session_locks");
17
+ adapter = new DevinAdapter(dbPath, locksDir);
18
+ });
19
+ afterEach(()=>{
20
+ adapter.close();
21
+ fs.rmSync(tmpDir, {
22
+ recursive: true,
23
+ force: true
24
+ });
25
+ });
26
+ it("accepts agent invocations and rejects utility subcommands", ()=>{
27
+ const cases = [
28
+ [
29
+ "devin",
30
+ true
31
+ ],
32
+ [
33
+ "/usr/local/bin/devin",
34
+ true
35
+ ],
36
+ [
37
+ "devin -r brawny-shirt",
38
+ true
39
+ ],
40
+ [
41
+ "devin --resume brawny-shirt --model x",
42
+ true
43
+ ],
44
+ [
45
+ "devin -p fix the bug",
46
+ true
47
+ ],
48
+ [
49
+ "devin -p update the schema",
50
+ true
51
+ ],
52
+ [
53
+ "devin --print list files",
54
+ true
55
+ ],
56
+ [
57
+ "devin -r",
58
+ true
59
+ ],
60
+ [
61
+ "devin --resume",
62
+ true
63
+ ],
64
+ [
65
+ "devin --cloud",
66
+ true
67
+ ],
68
+ [
69
+ "devin acp",
70
+ true
71
+ ],
72
+ [
73
+ "/Users/x/.local/bin/devin acp --cloud",
74
+ true
75
+ ],
76
+ [
77
+ "devin -- list the files",
78
+ true
79
+ ],
80
+ [
81
+ "devin list",
82
+ false
83
+ ],
84
+ [
85
+ "devin ls",
86
+ false
87
+ ],
88
+ [
89
+ "devin doctor",
90
+ false
91
+ ],
92
+ [
93
+ "devin auth status",
94
+ false
95
+ ],
96
+ [
97
+ "devin ssh sess-1",
98
+ false
99
+ ],
100
+ [
101
+ "devin forward 8080",
102
+ false
103
+ ],
104
+ [
105
+ "devin models",
106
+ false
107
+ ],
108
+ [
109
+ "devin skills list",
110
+ false
111
+ ],
112
+ [
113
+ "devin rules",
114
+ false
115
+ ],
116
+ [
117
+ "devin mcp add x",
118
+ false
119
+ ],
120
+ [
121
+ "devin plugins list",
122
+ false
123
+ ],
124
+ [
125
+ "devin cloud ls",
126
+ false
127
+ ],
128
+ [
129
+ "devin desktop /repo",
130
+ false
131
+ ],
132
+ [
133
+ "devin update",
134
+ false
135
+ ],
136
+ [
137
+ "devin version",
138
+ false
139
+ ],
140
+ [
141
+ "devin migrate",
142
+ false
143
+ ],
144
+ [
145
+ "devin sandbox run",
146
+ false
147
+ ],
148
+ [
149
+ "devin setup",
150
+ false
151
+ ],
152
+ [
153
+ "devin uninstall",
154
+ false
155
+ ],
156
+ [
157
+ "devin rm sess-1",
158
+ false
159
+ ],
160
+ [
161
+ "devin help",
162
+ false
163
+ ],
164
+ [
165
+ "devin --model opus list",
166
+ false
167
+ ],
168
+ [
169
+ "devin --model=opus list",
170
+ false
171
+ ],
172
+ [
173
+ "devin --config /tmp/c.json update",
174
+ false
175
+ ],
176
+ [
177
+ "devin --prompt-file /tmp/p.txt rm",
178
+ false
179
+ ],
180
+ [
181
+ "node devin.js",
182
+ false
183
+ ],
184
+ [
185
+ "devinition",
186
+ false
187
+ ]
188
+ ];
189
+ for (const [command, expected] of cases){
190
+ expect(adapter.canHandle({
191
+ pid: 1,
192
+ command,
193
+ cwd: "/repo",
194
+ tty: "ttys001"
195
+ })).toBe(expected, command);
196
+ }
197
+ });
198
+ it("returns process-only agents when no database or lock exists", async ()=>{
199
+ const agents = await adapter.detectAgents({
200
+ processes: [
201
+ {
202
+ pid: 100,
203
+ command: "devin",
204
+ cwd: "/repo",
205
+ tty: "ttys001"
206
+ }
207
+ ]
208
+ });
209
+ expect(agents).toEqual([
210
+ expect.objectContaining({
211
+ type: "devin",
212
+ status: AgentStatus.RUNNING,
213
+ pid: 100,
214
+ sessionId: "pid-100",
215
+ summary: "Devin process running"
216
+ })
217
+ ]);
218
+ });
219
+ it("matches a TUI to its session through the acp backend's lock", async ()=>{
220
+ const now = Math.floor(Date.now() / 1000);
221
+ writeDatabase(dbPath, {
222
+ sessions: [
223
+ {
224
+ id: "brawny-shirt",
225
+ directory: "/repo",
226
+ timeCreated: now - 100
227
+ }
228
+ ],
229
+ nodes: [
230
+ {
231
+ sessionId: "brawny-shirt",
232
+ nodeId: 1,
233
+ role: "user",
234
+ content: "fix the tests",
235
+ isUserInput: true,
236
+ createdAt: now - 90
237
+ },
238
+ {
239
+ sessionId: "brawny-shirt",
240
+ nodeId: 2,
241
+ role: "assistant",
242
+ content: "on it",
243
+ createdAt: now - 60
244
+ }
245
+ ],
246
+ prompts: [
247
+ {
248
+ sessionId: "brawny-shirt",
249
+ content: "fix the tests",
250
+ timestamp: now - 90
251
+ }
252
+ ]
253
+ });
254
+ writeLocks(locksDir, {
255
+ "brawny-shirt": 201
256
+ });
257
+ const tui = {
258
+ pid: 200,
259
+ command: "devin",
260
+ cwd: "/repo",
261
+ tty: "ttys001"
262
+ };
263
+ const acp = {
264
+ pid: 201,
265
+ ppid: 200,
266
+ command: "/usr/local/bin/devin acp",
267
+ cwd: "/repo",
268
+ tty: "??"
269
+ };
270
+ const agents = await adapter.detectAgents({
271
+ processes: [
272
+ tui,
273
+ acp
274
+ ]
275
+ });
276
+ expect(agents).toHaveLength(1);
277
+ expect(agents[0]).toMatchObject({
278
+ type: "devin",
279
+ status: AgentStatus.WAITING,
280
+ pid: 200,
281
+ sessionId: "brawny-shirt",
282
+ summary: "fix the tests",
283
+ sessionFilePath: `${dbPath}::brawny-shirt`
284
+ });
285
+ });
286
+ it("lists a standalone devin acp process as an agent via its own lock", async ()=>{
287
+ const now = Math.floor(Date.now() / 1000);
288
+ writeDatabase(dbPath, {
289
+ sessions: [
290
+ {
291
+ id: "merry-frog",
292
+ directory: "/repo",
293
+ timeCreated: now - 50
294
+ }
295
+ ],
296
+ nodes: [
297
+ {
298
+ sessionId: "merry-frog",
299
+ nodeId: 1,
300
+ role: "tool",
301
+ content: "running",
302
+ createdAt: now - 10
303
+ }
304
+ ]
305
+ });
306
+ writeLocks(locksDir, {
307
+ "merry-frog": 300
308
+ });
309
+ const acp = {
310
+ pid: 300,
311
+ ppid: 999,
312
+ command: "devin acp",
313
+ cwd: "/repo",
314
+ tty: "??"
315
+ };
316
+ const agents = await adapter.detectAgents({
317
+ processes: [
318
+ acp
319
+ ]
320
+ });
321
+ expect(agents).toHaveLength(1);
322
+ expect(agents[0]).toMatchObject({
323
+ type: "devin",
324
+ status: AgentStatus.RUNNING,
325
+ pid: 300,
326
+ sessionId: "merry-frog"
327
+ });
328
+ });
329
+ it("ignores stale locks whose holder is not a live devin process", async ()=>{
330
+ const now = Math.floor(Date.now() / 1000);
331
+ writeDatabase(dbPath, {
332
+ sessions: [
333
+ {
334
+ id: "dead-session",
335
+ directory: "/repo",
336
+ timeCreated: now - 500
337
+ }
338
+ ]
339
+ });
340
+ writeLocks(locksDir, {
341
+ "dead-session": 4242
342
+ });
343
+ const agents = await adapter.detectAgents({
344
+ processes: [
345
+ {
346
+ pid: 100,
347
+ command: "devin",
348
+ cwd: "/repo",
349
+ tty: "ttys001"
350
+ }
351
+ ]
352
+ });
353
+ // No lock match; cwd fallback still finds the session row.
354
+ expect(agents).toHaveLength(1);
355
+ expect(agents[0]).toMatchObject({
356
+ type: "devin",
357
+ sessionId: "dead-session",
358
+ pid: 100
359
+ });
360
+ });
361
+ it("ignores a lock when its pid is reused by a non-devin process", async ()=>{
362
+ const now = Math.floor(Date.now() / 1000);
363
+ writeDatabase(dbPath, {
364
+ sessions: [
365
+ {
366
+ id: "reused-pid",
367
+ directory: "/other",
368
+ timeCreated: now - 500
369
+ }
370
+ ]
371
+ });
372
+ writeLocks(locksDir, {
373
+ "reused-pid": 777
374
+ });
375
+ const processes = [
376
+ {
377
+ pid: 777,
378
+ command: "redis-server",
379
+ cwd: "/",
380
+ tty: "??"
381
+ },
382
+ {
383
+ pid: 100,
384
+ command: "devin",
385
+ cwd: "/repo",
386
+ tty: "ttys001"
387
+ }
388
+ ];
389
+ const agents = await adapter.detectAgents({
390
+ processes
391
+ });
392
+ expect(agents).toHaveLength(1);
393
+ expect(agents[0]).toMatchObject({
394
+ sessionId: "pid-100"
395
+ });
396
+ });
397
+ it("disambiguates two TUIs in the same directory via their locks", async ()=>{
398
+ const now = Math.floor(Date.now() / 1000);
399
+ writeDatabase(dbPath, {
400
+ sessions: [
401
+ {
402
+ id: "first",
403
+ directory: "/repo",
404
+ timeCreated: now - 200
405
+ },
406
+ {
407
+ id: "second",
408
+ directory: "/repo",
409
+ timeCreated: now - 100
410
+ }
411
+ ],
412
+ nodes: [
413
+ {
414
+ sessionId: "first",
415
+ nodeId: 1,
416
+ role: "assistant",
417
+ content: "a",
418
+ createdAt: now - 50
419
+ },
420
+ {
421
+ sessionId: "second",
422
+ nodeId: 1,
423
+ role: "assistant",
424
+ content: "b",
425
+ createdAt: now - 40
426
+ }
427
+ ]
428
+ });
429
+ writeLocks(locksDir, {
430
+ first: 401,
431
+ second: 501
432
+ });
433
+ const processes = [
434
+ {
435
+ pid: 400,
436
+ command: "devin",
437
+ cwd: "/repo",
438
+ tty: "ttys001"
439
+ },
440
+ {
441
+ pid: 401,
442
+ ppid: 400,
443
+ command: "devin acp",
444
+ cwd: "/repo",
445
+ tty: "??"
446
+ },
447
+ {
448
+ pid: 500,
449
+ command: "devin",
450
+ cwd: "/repo",
451
+ tty: "ttys002"
452
+ },
453
+ {
454
+ pid: 501,
455
+ ppid: 500,
456
+ command: "devin acp",
457
+ cwd: "/repo",
458
+ tty: "??"
459
+ }
460
+ ];
461
+ const agents = await adapter.detectAgents({
462
+ processes
463
+ });
464
+ expect(agents).toHaveLength(2);
465
+ expect(agents.find((a)=>a.pid === 400)?.sessionId).toBe("first");
466
+ expect(agents.find((a)=>a.pid === 500)?.sessionId).toBe("second");
467
+ });
468
+ it("falls back to working_directory matching without a lock", async ()=>{
469
+ const now = Math.floor(Date.now() / 1000);
470
+ writeDatabase(dbPath, {
471
+ sessions: [
472
+ {
473
+ id: "older",
474
+ directory: "/repo",
475
+ timeCreated: now - 300,
476
+ lastActivityAt: now - 300
477
+ },
478
+ {
479
+ id: "newer",
480
+ directory: "/repo",
481
+ timeCreated: now - 200,
482
+ lastActivityAt: now - 60
483
+ },
484
+ {
485
+ id: "hidden-sess",
486
+ directory: "/repo",
487
+ timeCreated: now - 10,
488
+ hidden: 1
489
+ }
490
+ ],
491
+ nodes: [
492
+ {
493
+ sessionId: "newer",
494
+ nodeId: 1,
495
+ role: "user",
496
+ content: "hi",
497
+ createdAt: now - 60
498
+ }
499
+ ]
500
+ });
501
+ const agents = await adapter.detectAgents({
502
+ processes: [
503
+ {
504
+ pid: 100,
505
+ command: "devin -p work",
506
+ cwd: "/repo",
507
+ tty: "ttys001"
508
+ }
509
+ ]
510
+ });
511
+ expect(agents).toHaveLength(1);
512
+ expect(agents[0]).toMatchObject({
513
+ sessionId: "newer",
514
+ type: "devin"
515
+ });
516
+ });
517
+ it("survives an unreadable database", async ()=>{
518
+ fs.writeFileSync(dbPath, "not sqlite at all");
519
+ const agents = await adapter.detectAgents({
520
+ processes: [
521
+ {
522
+ pid: 100,
523
+ command: "devin",
524
+ cwd: "/repo",
525
+ tty: "ttys001"
526
+ }
527
+ ]
528
+ });
529
+ expect(agents).toEqual([
530
+ expect.objectContaining({
531
+ sessionId: "pid-100"
532
+ })
533
+ ]);
534
+ });
535
+ it("returns conversation messages for encoded session refs", ()=>{
536
+ writeDatabase(dbPath, {
537
+ sessions: [
538
+ {
539
+ id: "sess-1",
540
+ directory: "/repo",
541
+ timeCreated: 1000
542
+ }
543
+ ],
544
+ nodes: [
545
+ {
546
+ sessionId: "sess-1",
547
+ nodeId: 1,
548
+ role: "user",
549
+ content: "hello",
550
+ isUserInput: true
551
+ },
552
+ {
553
+ sessionId: "sess-1",
554
+ nodeId: 2,
555
+ role: "assistant",
556
+ content: "hi there"
557
+ }
558
+ ]
559
+ });
560
+ expect(adapter.getConversation(`${dbPath}::sess-1`)).toEqual([
561
+ {
562
+ role: "user",
563
+ content: "hello"
564
+ },
565
+ {
566
+ role: "assistant",
567
+ content: "hi there"
568
+ }
569
+ ]);
570
+ expect(adapter.getConversation("/not-a-ref")).toEqual([]);
571
+ });
572
+ it("finds sessions by exact slug", async ()=>{
573
+ writeDatabase(dbPath, {
574
+ sessions: [
575
+ {
576
+ id: "wanted",
577
+ directory: "/repo",
578
+ timeCreated: 1000
579
+ },
580
+ {
581
+ id: "other",
582
+ directory: "/other",
583
+ timeCreated: 999
584
+ }
585
+ ]
586
+ });
587
+ await expect(adapter.findSessionsById("wanted")).resolves.toEqual([
588
+ expect.objectContaining({
589
+ type: "devin",
590
+ sessionId: "wanted"
591
+ })
592
+ ]);
593
+ await expect(adapter.findSessionsById("missing")).resolves.toEqual([]);
594
+ });
595
+ });
596
+
597
+ //# sourceMappingURL=DevinAdapter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/__tests__/harnesses/devin/DevinAdapter.test.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport { AgentStatus, type ProcessInfo } from \"../../../adapters/AgentAdapter.js\";\nimport { DevinAdapter } from \"../../../harnesses/devin/DevinAdapter.js\";\nimport { writeDatabase, writeLocks } from \"./fixtures.js\";\n\ndescribe(\"DevinAdapter\", () => {\n let adapter: DevinAdapter;\n let tmpDir: string;\n let dbPath: string;\n let locksDir: string;\n\n beforeEach(() => {\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), \"devin-adapter-test-\"));\n dbPath = path.join(tmpDir, \"sessions.db\");\n locksDir = path.join(tmpDir, \"session_locks\");\n adapter = new DevinAdapter(dbPath, locksDir);\n });\n\n afterEach(() => {\n adapter.close();\n fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n it(\"accepts agent invocations and rejects utility subcommands\", () => {\n const cases: [string, boolean][] = [\n [\"devin\", true],\n [\"/usr/local/bin/devin\", true],\n [\"devin -r brawny-shirt\", true],\n [\"devin --resume brawny-shirt --model x\", true],\n [\"devin -p fix the bug\", true],\n [\"devin -p update the schema\", true],\n [\"devin --print list files\", true],\n [\"devin -r\", true],\n [\"devin --resume\", true],\n [\"devin --cloud\", true],\n [\"devin acp\", true],\n [\"/Users/x/.local/bin/devin acp --cloud\", true],\n [\"devin -- list the files\", true],\n [\"devin list\", false],\n [\"devin ls\", false],\n [\"devin doctor\", false],\n [\"devin auth status\", false],\n [\"devin ssh sess-1\", false],\n [\"devin forward 8080\", false],\n [\"devin models\", false],\n [\"devin skills list\", false],\n [\"devin rules\", false],\n [\"devin mcp add x\", false],\n [\"devin plugins list\", false],\n [\"devin cloud ls\", false],\n [\"devin desktop /repo\", false],\n [\"devin update\", false],\n [\"devin version\", false],\n [\"devin migrate\", false],\n [\"devin sandbox run\", false],\n [\"devin setup\", false],\n [\"devin uninstall\", false],\n [\"devin rm sess-1\", false],\n [\"devin help\", false],\n [\"devin --model opus list\", false],\n [\"devin --model=opus list\", false],\n [\"devin --config /tmp/c.json update\", false],\n [\"devin --prompt-file /tmp/p.txt rm\", false],\n [\"node devin.js\", false],\n [\"devinition\", false],\n ];\n for (const [command, expected] of cases) {\n expect(adapter.canHandle({ pid: 1, command, cwd: \"/repo\", tty: \"ttys001\" })).toBe(\n expected,\n command,\n );\n }\n });\n\n it(\"returns process-only agents when no database or lock exists\", async () => {\n const agents = await adapter.detectAgents({\n processes: [{ pid: 100, command: \"devin\", cwd: \"/repo\", tty: \"ttys001\" }],\n });\n\n expect(agents).toEqual([\n expect.objectContaining({\n type: \"devin\",\n status: AgentStatus.RUNNING,\n pid: 100,\n sessionId: \"pid-100\",\n summary: \"Devin process running\",\n }),\n ]);\n });\n\n it(\"matches a TUI to its session through the acp backend's lock\", async () => {\n const now = Math.floor(Date.now() / 1000);\n writeDatabase(dbPath, {\n sessions: [{ id: \"brawny-shirt\", directory: \"/repo\", timeCreated: now - 100 }],\n nodes: [\n {\n sessionId: \"brawny-shirt\",\n nodeId: 1,\n role: \"user\",\n content: \"fix the tests\",\n isUserInput: true,\n createdAt: now - 90,\n },\n {\n sessionId: \"brawny-shirt\",\n nodeId: 2,\n role: \"assistant\",\n content: \"on it\",\n createdAt: now - 60,\n },\n ],\n prompts: [{ sessionId: \"brawny-shirt\", content: \"fix the tests\", timestamp: now - 90 }],\n });\n writeLocks(locksDir, { \"brawny-shirt\": 201 });\n\n const tui: ProcessInfo = { pid: 200, command: \"devin\", cwd: \"/repo\", tty: \"ttys001\" };\n const acp: ProcessInfo = {\n pid: 201,\n ppid: 200,\n command: \"/usr/local/bin/devin acp\",\n cwd: \"/repo\",\n tty: \"??\",\n };\n const agents = await adapter.detectAgents({ processes: [tui, acp] });\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"devin\",\n status: AgentStatus.WAITING,\n pid: 200,\n sessionId: \"brawny-shirt\",\n summary: \"fix the tests\",\n sessionFilePath: `${dbPath}::brawny-shirt`,\n });\n });\n\n it(\"lists a standalone devin acp process as an agent via its own lock\", async () => {\n const now = Math.floor(Date.now() / 1000);\n writeDatabase(dbPath, {\n sessions: [{ id: \"merry-frog\", directory: \"/repo\", timeCreated: now - 50 }],\n nodes: [\n {\n sessionId: \"merry-frog\",\n nodeId: 1,\n role: \"tool\",\n content: \"running\",\n createdAt: now - 10,\n },\n ],\n });\n writeLocks(locksDir, { \"merry-frog\": 300 });\n\n const acp: ProcessInfo = {\n pid: 300,\n ppid: 999,\n command: \"devin acp\",\n cwd: \"/repo\",\n tty: \"??\",\n };\n const agents = await adapter.detectAgents({ processes: [acp] });\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"devin\",\n status: AgentStatus.RUNNING,\n pid: 300,\n sessionId: \"merry-frog\",\n });\n });\n\n it(\"ignores stale locks whose holder is not a live devin process\", async () => {\n const now = Math.floor(Date.now() / 1000);\n writeDatabase(dbPath, {\n sessions: [{ id: \"dead-session\", directory: \"/repo\", timeCreated: now - 500 }],\n });\n writeLocks(locksDir, { \"dead-session\": 4242 });\n\n const agents = await adapter.detectAgents({\n processes: [{ pid: 100, command: \"devin\", cwd: \"/repo\", tty: \"ttys001\" }],\n });\n\n // No lock match; cwd fallback still finds the session row.\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({ type: \"devin\", sessionId: \"dead-session\", pid: 100 });\n });\n\n it(\"ignores a lock when its pid is reused by a non-devin process\", async () => {\n const now = Math.floor(Date.now() / 1000);\n writeDatabase(dbPath, {\n sessions: [{ id: \"reused-pid\", directory: \"/other\", timeCreated: now - 500 }],\n });\n writeLocks(locksDir, { \"reused-pid\": 777 });\n\n const processes: ProcessInfo[] = [\n { pid: 777, command: \"redis-server\", cwd: \"/\", tty: \"??\" },\n { pid: 100, command: \"devin\", cwd: \"/repo\", tty: \"ttys001\" },\n ];\n const agents = await adapter.detectAgents({ processes });\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({ sessionId: \"pid-100\" });\n });\n\n it(\"disambiguates two TUIs in the same directory via their locks\", async () => {\n const now = Math.floor(Date.now() / 1000);\n writeDatabase(dbPath, {\n sessions: [\n { id: \"first\", directory: \"/repo\", timeCreated: now - 200 },\n { id: \"second\", directory: \"/repo\", timeCreated: now - 100 },\n ],\n nodes: [\n { sessionId: \"first\", nodeId: 1, role: \"assistant\", content: \"a\", createdAt: now - 50 },\n { sessionId: \"second\", nodeId: 1, role: \"assistant\", content: \"b\", createdAt: now - 40 },\n ],\n });\n writeLocks(locksDir, { first: 401, second: 501 });\n\n const processes: ProcessInfo[] = [\n { pid: 400, command: \"devin\", cwd: \"/repo\", tty: \"ttys001\" },\n { pid: 401, ppid: 400, command: \"devin acp\", cwd: \"/repo\", tty: \"??\" },\n { pid: 500, command: \"devin\", cwd: \"/repo\", tty: \"ttys002\" },\n { pid: 501, ppid: 500, command: \"devin acp\", cwd: \"/repo\", tty: \"??\" },\n ];\n const agents = await adapter.detectAgents({ processes });\n\n expect(agents).toHaveLength(2);\n expect(agents.find((a) => a.pid === 400)?.sessionId).toBe(\"first\");\n expect(agents.find((a) => a.pid === 500)?.sessionId).toBe(\"second\");\n });\n\n it(\"falls back to working_directory matching without a lock\", async () => {\n const now = Math.floor(Date.now() / 1000);\n writeDatabase(dbPath, {\n sessions: [\n { id: \"older\", directory: \"/repo\", timeCreated: now - 300, lastActivityAt: now - 300 },\n { id: \"newer\", directory: \"/repo\", timeCreated: now - 200, lastActivityAt: now - 60 },\n { id: \"hidden-sess\", directory: \"/repo\", timeCreated: now - 10, hidden: 1 },\n ],\n nodes: [\n { sessionId: \"newer\", nodeId: 1, role: \"user\", content: \"hi\", createdAt: now - 60 },\n ],\n });\n\n const agents = await adapter.detectAgents({\n processes: [{ pid: 100, command: \"devin -p work\", cwd: \"/repo\", tty: \"ttys001\" }],\n });\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({ sessionId: \"newer\", type: \"devin\" });\n });\n\n it(\"survives an unreadable database\", async () => {\n fs.writeFileSync(dbPath, \"not sqlite at all\");\n const agents = await adapter.detectAgents({\n processes: [{ pid: 100, command: \"devin\", cwd: \"/repo\", tty: \"ttys001\" }],\n });\n expect(agents).toEqual([expect.objectContaining({ sessionId: \"pid-100\" })]);\n });\n\n it(\"returns conversation messages for encoded session refs\", () => {\n writeDatabase(dbPath, {\n sessions: [{ id: \"sess-1\", directory: \"/repo\", timeCreated: 1000 }],\n nodes: [\n { sessionId: \"sess-1\", nodeId: 1, role: \"user\", content: \"hello\", isUserInput: true },\n { sessionId: \"sess-1\", nodeId: 2, role: \"assistant\", content: \"hi there\" },\n ],\n });\n\n expect(adapter.getConversation(`${dbPath}::sess-1`)).toEqual([\n { role: \"user\", content: \"hello\" },\n { role: \"assistant\", content: \"hi there\" },\n ]);\n expect(adapter.getConversation(\"/not-a-ref\")).toEqual([]);\n });\n\n it(\"finds sessions by exact slug\", async () => {\n writeDatabase(dbPath, {\n sessions: [\n { id: \"wanted\", directory: \"/repo\", timeCreated: 1000 },\n { id: \"other\", directory: \"/other\", timeCreated: 999 },\n ],\n });\n\n await expect(adapter.findSessionsById(\"wanted\")).resolves.toEqual([\n expect.objectContaining({ type: \"devin\", sessionId: \"wanted\" }),\n ]);\n await expect(adapter.findSessionsById(\"missing\")).resolves.toEqual([]);\n });\n});\n"],"names":["fs","os","path","afterEach","beforeEach","describe","expect","it","AgentStatus","DevinAdapter","writeDatabase","writeLocks","adapter","tmpDir","dbPath","locksDir","mkdtempSync","join","tmpdir","close","rmSync","recursive","force","cases","command","expected","canHandle","pid","cwd","tty","toBe","agents","detectAgents","processes","toEqual","objectContaining","type","status","RUNNING","sessionId","summary","now","Math","floor","Date","sessions","id","directory","timeCreated","nodes","nodeId","role","content","isUserInput","createdAt","prompts","timestamp","tui","acp","ppid","toHaveLength","toMatchObject","WAITING","sessionFilePath","first","second","find","a","lastActivityAt","hidden","writeFileSync","getConversation","findSessionsById","resolves"],"mappings":"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"}