@ai-devkit/agent-manager 0.33.0 → 0.34.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 (174) hide show
  1. package/dist/AgentManager.d.ts +11 -2
  2. package/dist/AgentManager.d.ts.map +1 -1
  3. package/dist/AgentManager.js +37 -6
  4. package/dist/AgentManager.js.map +1 -1
  5. package/dist/__tests__/AgentManager.test.js +212 -5
  6. package/dist/__tests__/AgentManager.test.js.map +1 -1
  7. package/dist/__tests__/capacity/codex.test.js +2 -1
  8. package/dist/__tests__/capacity/codex.test.js.map +1 -1
  9. package/dist/__tests__/capacity/index.test.js +6 -3
  10. package/dist/__tests__/capacity/index.test.js.map +1 -1
  11. package/dist/__tests__/capacity/zai.test.js +213 -0
  12. package/dist/__tests__/capacity/zai.test.js.map +1 -0
  13. package/dist/__tests__/{adapters → providers/copilot}/CopilotAdapter.test.js +19 -14
  14. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -0
  15. package/dist/__tests__/{adapters → providers/gemini}/GeminiCliAdapter.test.js +37 -29
  16. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -0
  17. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js +189 -0
  18. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js.map +1 -0
  19. package/dist/__tests__/providers/opencode/OpenCodeAgentMapper.test.js +89 -0
  20. package/dist/__tests__/providers/opencode/OpenCodeAgentMapper.test.js.map +1 -0
  21. package/dist/__tests__/providers/opencode/OpenCodeSessionLocator.test.js +94 -0
  22. package/dist/__tests__/providers/opencode/OpenCodeSessionLocator.test.js.map +1 -0
  23. package/dist/__tests__/providers/opencode/OpenCodeSessionParser.test.js +147 -0
  24. package/dist/__tests__/providers/opencode/OpenCodeSessionParser.test.js.map +1 -0
  25. package/dist/__tests__/runtime/HerdrAgentDiscovery.test.js +164 -0
  26. package/dist/__tests__/runtime/HerdrAgentDiscovery.test.js.map +1 -0
  27. package/dist/__tests__/runtime/HerdrAgentRuntime.test.js +37 -1
  28. package/dist/__tests__/runtime/HerdrAgentRuntime.test.js.map +1 -1
  29. package/dist/__tests__/utils/AgentRegistry.test.js +20 -13
  30. package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -1
  31. package/dist/adapters/index.d.ts +3 -3
  32. package/dist/adapters/index.d.ts.map +1 -1
  33. package/dist/adapters/index.js +3 -3
  34. package/dist/adapters/index.js.map +1 -1
  35. package/dist/capacity/codex.d.ts +1 -0
  36. package/dist/capacity/codex.d.ts.map +1 -1
  37. package/dist/capacity/codex.js +9 -4
  38. package/dist/capacity/codex.js.map +1 -1
  39. package/dist/capacity/index.d.ts +8 -0
  40. package/dist/capacity/index.d.ts.map +1 -1
  41. package/dist/capacity/index.js +10 -9
  42. package/dist/capacity/index.js.map +1 -1
  43. package/dist/capacity/types.d.ts +8 -0
  44. package/dist/capacity/types.d.ts.map +1 -1
  45. package/dist/capacity/types.js.map +1 -1
  46. package/dist/capacity/zai.d.ts +14 -0
  47. package/dist/capacity/zai.d.ts.map +1 -0
  48. package/dist/capacity/zai.js +203 -0
  49. package/dist/capacity/zai.js.map +1 -0
  50. package/dist/index.d.ts +8 -6
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +5 -4
  53. package/dist/index.js.map +1 -1
  54. package/dist/{adapters → providers/copilot}/CopilotAdapter.d.ts +11 -25
  55. package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -0
  56. package/dist/providers/copilot/CopilotAdapter.js +108 -0
  57. package/dist/providers/copilot/CopilotAdapter.js.map +1 -0
  58. package/dist/providers/copilot/CopilotAgentMapper.d.ts +9 -0
  59. package/dist/providers/copilot/CopilotAgentMapper.d.ts.map +1 -0
  60. package/dist/providers/copilot/CopilotAgentMapper.js +37 -0
  61. package/dist/providers/copilot/CopilotAgentMapper.js.map +1 -0
  62. package/dist/providers/copilot/CopilotSessionLocator.d.ts +19 -0
  63. package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -0
  64. package/dist/providers/copilot/CopilotSessionLocator.js +41 -0
  65. package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -0
  66. package/dist/providers/copilot/CopilotSessionParser.d.ts +30 -0
  67. package/dist/providers/copilot/CopilotSessionParser.d.ts.map +1 -0
  68. package/dist/providers/copilot/CopilotSessionParser.js +181 -0
  69. package/dist/providers/copilot/CopilotSessionParser.js.map +1 -0
  70. package/dist/providers/gemini/GeminiAgentMapper.d.ts +9 -0
  71. package/dist/providers/gemini/GeminiAgentMapper.d.ts.map +1 -0
  72. package/dist/providers/gemini/GeminiAgentMapper.js +37 -0
  73. package/dist/providers/gemini/GeminiAgentMapper.js.map +1 -0
  74. package/dist/providers/gemini/GeminiCliAdapter.d.ts +52 -0
  75. package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -0
  76. package/dist/providers/gemini/GeminiCliAdapter.js +188 -0
  77. package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -0
  78. package/dist/providers/gemini/GeminiSessionLocator.d.ts +30 -0
  79. package/dist/providers/gemini/GeminiSessionLocator.d.ts.map +1 -0
  80. package/dist/providers/gemini/GeminiSessionLocator.js +142 -0
  81. package/dist/providers/gemini/GeminiSessionLocator.js.map +1 -0
  82. package/dist/providers/gemini/GeminiSessionParser.d.ts +94 -0
  83. package/dist/providers/gemini/GeminiSessionParser.d.ts.map +1 -0
  84. package/dist/providers/gemini/GeminiSessionParser.js +166 -0
  85. package/dist/providers/gemini/GeminiSessionParser.js.map +1 -0
  86. package/dist/{adapters → providers/opencode}/OpenCodeAdapter.d.ts +6 -12
  87. package/dist/providers/opencode/OpenCodeAdapter.d.ts.map +1 -0
  88. package/dist/providers/opencode/OpenCodeAdapter.js +86 -0
  89. package/dist/providers/opencode/OpenCodeAdapter.js.map +1 -0
  90. package/dist/providers/opencode/OpenCodeAgentMapper.d.ts +11 -0
  91. package/dist/providers/opencode/OpenCodeAgentMapper.d.ts.map +1 -0
  92. package/dist/providers/opencode/OpenCodeAgentMapper.js +50 -0
  93. package/dist/providers/opencode/OpenCodeAgentMapper.js.map +1 -0
  94. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts +21 -0
  95. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts.map +1 -0
  96. package/dist/providers/opencode/OpenCodeSessionLocator.js +99 -0
  97. package/dist/providers/opencode/OpenCodeSessionLocator.js.map +1 -0
  98. package/dist/providers/opencode/OpenCodeSessionParser.d.ts +18 -0
  99. package/dist/providers/opencode/OpenCodeSessionParser.d.ts.map +1 -0
  100. package/dist/providers/opencode/OpenCodeSessionParser.js +104 -0
  101. package/dist/providers/opencode/OpenCodeSessionParser.js.map +1 -0
  102. package/dist/runtime/herdr/HerdrAgentDiscovery.d.ts +18 -0
  103. package/dist/runtime/herdr/HerdrAgentDiscovery.d.ts.map +1 -0
  104. package/dist/runtime/herdr/HerdrAgentDiscovery.js +183 -0
  105. package/dist/runtime/herdr/HerdrAgentDiscovery.js.map +1 -0
  106. package/dist/runtime/herdr/HerdrAgentRuntime.d.ts +5 -0
  107. package/dist/runtime/herdr/HerdrAgentRuntime.d.ts.map +1 -1
  108. package/dist/runtime/herdr/HerdrAgentRuntime.js +26 -8
  109. package/dist/runtime/herdr/HerdrAgentRuntime.js.map +1 -1
  110. package/dist/runtime/herdr/HerdrResponseParsers.d.ts +2 -0
  111. package/dist/runtime/herdr/HerdrResponseParsers.d.ts.map +1 -1
  112. package/dist/runtime/herdr/HerdrResponseParsers.js +12 -1
  113. package/dist/runtime/herdr/HerdrResponseParsers.js.map +1 -1
  114. package/dist/utils/AgentRegistry.d.ts.map +1 -1
  115. package/dist/utils/AgentRegistry.js +5 -5
  116. package/dist/utils/AgentRegistry.js.map +1 -1
  117. package/package.json +1 -1
  118. package/src/AgentManager.ts +67 -5
  119. package/src/__tests__/AgentManager.test.ts +231 -5
  120. package/src/__tests__/capacity/codex.test.ts +94 -26
  121. package/src/__tests__/capacity/fixtures/zai-auth.json +6 -0
  122. package/src/__tests__/capacity/fixtures/zai-quota-invalid.json +7 -0
  123. package/src/__tests__/capacity/fixtures/zai-quota.json +38 -0
  124. package/src/__tests__/capacity/index.test.ts +10 -3
  125. package/src/__tests__/capacity/zai.test.ts +186 -0
  126. package/src/__tests__/{adapters → providers/copilot}/CopilotAdapter.test.ts +13 -16
  127. package/src/__tests__/{adapters → providers/gemini}/GeminiCliAdapter.test.ts +39 -31
  128. package/src/__tests__/providers/opencode/OpenCodeAdapter.test.ts +197 -0
  129. package/src/__tests__/providers/opencode/OpenCodeAgentMapper.test.ts +83 -0
  130. package/src/__tests__/providers/opencode/OpenCodeSessionLocator.test.ts +84 -0
  131. package/src/__tests__/providers/opencode/OpenCodeSessionParser.test.ts +148 -0
  132. package/src/__tests__/runtime/HerdrAgentDiscovery.test.ts +131 -0
  133. package/src/__tests__/runtime/HerdrAgentRuntime.test.ts +20 -1
  134. package/src/__tests__/utils/AgentRegistry.test.ts +20 -10
  135. package/src/adapters/index.ts +3 -3
  136. package/src/capacity/codex.ts +96 -28
  137. package/src/capacity/index.ts +29 -11
  138. package/src/capacity/types.ts +8 -0
  139. package/src/capacity/zai.ts +237 -0
  140. package/src/index.ts +18 -6
  141. package/src/providers/copilot/CopilotAdapter.ts +145 -0
  142. package/src/providers/copilot/CopilotAgentMapper.ts +36 -0
  143. package/src/providers/copilot/CopilotSessionLocator.ts +57 -0
  144. package/src/providers/copilot/CopilotSessionParser.ts +289 -0
  145. package/src/providers/gemini/GeminiAgentMapper.ts +36 -0
  146. package/src/providers/gemini/GeminiCliAdapter.ts +266 -0
  147. package/src/providers/gemini/GeminiSessionLocator.ts +183 -0
  148. package/src/providers/gemini/GeminiSessionParser.ts +271 -0
  149. package/src/providers/opencode/OpenCodeAdapter.ts +112 -0
  150. package/src/providers/opencode/OpenCodeAgentMapper.ts +61 -0
  151. package/src/providers/opencode/OpenCodeSessionLocator.ts +123 -0
  152. package/src/providers/opencode/OpenCodeSessionParser.ts +145 -0
  153. package/src/runtime/herdr/HerdrAgentDiscovery.ts +178 -0
  154. package/src/runtime/herdr/HerdrAgentRuntime.ts +33 -7
  155. package/src/runtime/herdr/HerdrResponseParsers.ts +11 -1
  156. package/src/utils/AgentRegistry.ts +8 -6
  157. package/dist/__tests__/adapters/CopilotAdapter.test.js.map +0 -1
  158. package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +0 -1
  159. package/dist/__tests__/adapters/OpenCodeAdapter.test.js +0 -635
  160. package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +0 -1
  161. package/dist/adapters/CopilotAdapter.d.ts.map +0 -1
  162. package/dist/adapters/CopilotAdapter.js +0 -329
  163. package/dist/adapters/CopilotAdapter.js.map +0 -1
  164. package/dist/adapters/GeminiCliAdapter.d.ts +0 -98
  165. package/dist/adapters/GeminiCliAdapter.d.ts.map +0 -1
  166. package/dist/adapters/GeminiCliAdapter.js +0 -513
  167. package/dist/adapters/GeminiCliAdapter.js.map +0 -1
  168. package/dist/adapters/OpenCodeAdapter.d.ts.map +0 -1
  169. package/dist/adapters/OpenCodeAdapter.js +0 -286
  170. package/dist/adapters/OpenCodeAdapter.js.map +0 -1
  171. package/src/__tests__/adapters/OpenCodeAdapter.test.ts +0 -543
  172. package/src/adapters/CopilotAdapter.ts +0 -483
  173. package/src/adapters/GeminiCliAdapter.ts +0 -699
  174. package/src/adapters/OpenCodeAdapter.ts +0 -369
@@ -1,635 +0,0 @@
1
- /**
2
- * Tests for OpenCodeAdapter
3
- */ import * as fs from "fs";
4
- import * as path from "path";
5
- import { OpenCodeAdapter } from "../../adapters/OpenCodeAdapter.js";
6
- import { AgentStatus } from "../../adapters/AgentAdapter.js";
7
- import { listAgentProcesses, enrichProcesses, captureProcessSnapshot } from "../../utils/process.js";
8
- import { generateAgentName } from "../../utils/matching.js";
9
- import * as os from "os";
10
- vi.mock("../../utils/process.js", async (importOriginal)=>{
11
- const actual = await importOriginal();
12
- return {
13
- ...actual,
14
- listAgentProcesses: vi.fn(),
15
- enrichProcesses: vi.fn(),
16
- captureProcessSnapshot: vi.fn()
17
- };
18
- });
19
- vi.mock("../../utils/matching.js", ()=>({
20
- generateAgentName: vi.fn(),
21
- matchProcessesToSessions: vi.fn()
22
- }));
23
- const mockedListAgentProcesses = listAgentProcesses;
24
- const mockedEnrichProcesses = enrichProcesses;
25
- const mockedCaptureProcessSnapshot = captureProcessSnapshot;
26
- const mockedGenerateAgentName = generateAgentName;
27
- function makeDb(queries) {
28
- const prepareImpl = (sql)=>{
29
- const normalized = sql.replace(/\s+/g, " ").trim().toLowerCase();
30
- if (normalized.includes("from session")) {
31
- return {
32
- all: ()=>(queries.session ?? []).map((r)=>({
33
- id: r.id,
34
- directory: r.directory,
35
- timeCreated: r.time_created
36
- })),
37
- get: (dir)=>{
38
- const match = (queries.session ?? []).find((r)=>r.directory === dir);
39
- if (!match) return undefined;
40
- return {
41
- id: match.id,
42
- directory: match.directory,
43
- time_created: match.time_created
44
- };
45
- }
46
- };
47
- }
48
- if (normalized.includes("max(time_updated)")) {
49
- return {
50
- get: ()=>({
51
- maxUpdated: queries.lastMessage?.timeUpdated ?? 0
52
- })
53
- };
54
- }
55
- if (normalized.includes("from message") && !normalized.includes("'$.time.completed'") && !normalized.includes("order by p.time_created")) {
56
- return {
57
- get: ()=>queries.lastMessage ?? undefined
58
- };
59
- }
60
- if (normalized.includes("'$.time.completed'")) {
61
- return {
62
- get: ()=>queries.lastAssistant === undefined ? undefined : queries.lastAssistant ?? undefined
63
- };
64
- }
65
- if (normalized.includes("json_extract(m.data, '$.role') = 'user'")) {
66
- return {
67
- get: ()=>queries.firstUserText === undefined ? undefined : queries.firstUserText ?? undefined
68
- };
69
- }
70
- if (normalized.includes("order by p.time_created asc")) {
71
- return {
72
- all: ()=>queries.parts ?? []
73
- };
74
- }
75
- return {
76
- all: ()=>[],
77
- get: ()=>undefined
78
- };
79
- };
80
- return {
81
- prepare: prepareImpl,
82
- close: vi.fn()
83
- };
84
- }
85
- function makeDbConstructor(db) {
86
- return vi.fn().mockReturnValue(db);
87
- }
88
- describe("OpenCodeAdapter", ()=>{
89
- let adapter;
90
- let tmpDir;
91
- let dbPath;
92
- beforeEach(()=>{
93
- adapter = new OpenCodeAdapter();
94
- mockedListAgentProcesses.mockReset();
95
- mockedEnrichProcesses.mockReset();
96
- mockedCaptureProcessSnapshot.mockReset();
97
- mockedGenerateAgentName.mockReset();
98
- mockedEnrichProcesses.mockImplementation((procs)=>procs);
99
- // Compatibility shim for standalone adapter discovery; the manager captures once and slices by name.
100
- mockedCaptureProcessSnapshot.mockImplementation(async (names)=>enrichProcesses(names.flatMap((name)=>listAgentProcesses(name))));
101
- mockedGenerateAgentName.mockImplementation((cwd, pid)=>{
102
- const folder = path.basename(cwd) || "unknown";
103
- return `${folder}-${pid}`;
104
- });
105
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-test-"));
106
- dbPath = path.join(tmpDir, "opencode.db");
107
- adapter.dbPath = dbPath;
108
- adapter.db = null;
109
- });
110
- afterEach(()=>{
111
- fs.rmSync(tmpDir, {
112
- recursive: true,
113
- force: true
114
- });
115
- vi.restoreAllMocks();
116
- });
117
- describe("type", ()=>{
118
- it("exposes opencode type", ()=>{
119
- expect(adapter.type).toBe("opencode");
120
- });
121
- });
122
- describe("canHandle", ()=>{
123
- it("returns true for opencode command", ()=>{
124
- expect(adapter.canHandle({
125
- pid: 1,
126
- command: "opencode",
127
- cwd: "/repo",
128
- tty: "ttys001"
129
- })).toBe(true);
130
- });
131
- it("returns true for full path opencode", ()=>{
132
- expect(adapter.canHandle({
133
- pid: 2,
134
- command: "/usr/local/bin/opencode serve",
135
- cwd: "/repo",
136
- tty: "ttys002"
137
- })).toBe(true);
138
- });
139
- it("returns true for opencode.exe with unix-style path", ()=>{
140
- expect(adapter.canHandle({
141
- pid: 3,
142
- command: "/usr/bin/opencode.exe",
143
- cwd: "/repo",
144
- tty: "ttys003"
145
- })).toBe(true);
146
- });
147
- it("returns false for non-opencode processes", ()=>{
148
- expect(adapter.canHandle({
149
- pid: 4,
150
- command: "node server.js",
151
- cwd: "/repo",
152
- tty: "ttys004"
153
- })).toBe(false);
154
- });
155
- it("returns false when opencode appears only in path args", ()=>{
156
- expect(adapter.canHandle({
157
- pid: 5,
158
- command: "node /projects/opencode-plugin/index.js",
159
- cwd: "/repo",
160
- tty: "ttys005"
161
- })).toBe(false);
162
- });
163
- });
164
- describe("detectAgents", ()=>{
165
- it("returns empty list when no opencode processes running", async ()=>{
166
- mockedListAgentProcesses.mockReturnValue([]);
167
- const agents = await adapter.detectAgents();
168
- expect(agents).toEqual([]);
169
- expect(mockedListAgentProcesses).toHaveBeenCalledWith("opencode");
170
- });
171
- it("returns process-only agent when DB does not exist", async ()=>{
172
- const procs = [
173
- {
174
- pid: 100,
175
- command: "opencode",
176
- cwd: "/repo",
177
- tty: "ttys001"
178
- }
179
- ];
180
- mockedListAgentProcesses.mockReturnValue(procs);
181
- mockedEnrichProcesses.mockReturnValue(procs);
182
- const agents = await adapter.detectAgents();
183
- expect(agents).toHaveLength(1);
184
- expect(agents[0]).toMatchObject({
185
- type: "opencode",
186
- status: AgentStatus.RUNNING,
187
- pid: 100,
188
- projectPath: "/repo",
189
- sessionId: "pid-100",
190
- summary: "OpenCode process running"
191
- });
192
- });
193
- it("returns process-only agent when no session matches CWD", async ()=>{
194
- const procs = [
195
- {
196
- pid: 100,
197
- command: "opencode",
198
- cwd: "/repo",
199
- tty: "ttys001"
200
- }
201
- ];
202
- mockedListAgentProcesses.mockReturnValue(procs);
203
- mockedEnrichProcesses.mockReturnValue(procs);
204
- fs.writeFileSync(dbPath, ""); // file exists but empty → sqlite throws
205
- const db = makeDb({
206
- session: []
207
- }); // no matching session
208
- vi.doMock("better-sqlite3", ()=>makeDbConstructor(db));
209
- adapter.db = db;
210
- const agents = await adapter.detectAgents();
211
- expect(agents).toHaveLength(1);
212
- expect(agents[0]).toMatchObject({
213
- type: "opencode",
214
- status: AgentStatus.RUNNING,
215
- sessionId: "pid-100"
216
- });
217
- });
218
- it("returns waiting when assistant turn has time.completed set", async ()=>{
219
- const now = Date.now();
220
- const procs = [
221
- {
222
- pid: 200,
223
- command: "opencode",
224
- cwd: "/my-project",
225
- tty: "ttys002"
226
- }
227
- ];
228
- mockedListAgentProcesses.mockReturnValue(procs);
229
- mockedEnrichProcesses.mockReturnValue(procs);
230
- const db = makeDb({
231
- session: [
232
- {
233
- id: "sess-001",
234
- directory: "/my-project",
235
- time_created: now - 60000
236
- }
237
- ],
238
- lastMessage: {
239
- role: "assistant",
240
- timeUpdated: now - 60_000
241
- },
242
- lastAssistant: {
243
- completed: now - 30_000,
244
- errored: null
245
- },
246
- firstUserText: {
247
- text: "Refactor the auth module"
248
- }
249
- });
250
- adapter.db = db;
251
- const agents = await adapter.detectAgents();
252
- expect(agents).toHaveLength(1);
253
- expect(agents[0]).toMatchObject({
254
- type: "opencode",
255
- status: AgentStatus.WAITING,
256
- sessionId: "sess-001",
257
- summary: "Refactor the auth module"
258
- });
259
- });
260
- it("returns running when assistant turn has no time.completed (in-progress, any age)", async ()=>{
261
- const now = Date.now();
262
- const procs = [
263
- {
264
- pid: 250,
265
- command: "opencode",
266
- cwd: "/proj",
267
- tty: "ttys004"
268
- }
269
- ];
270
- mockedListAgentProcesses.mockReturnValue(procs);
271
- mockedEnrichProcesses.mockReturnValue(procs);
272
- const db = makeDb({
273
- session: [
274
- {
275
- id: "sess-tool",
276
- directory: "/proj",
277
- time_created: now - 180_000
278
- }
279
- ],
280
- lastMessage: {
281
- role: "assistant",
282
- timeUpdated: now - 120_000
283
- },
284
- lastAssistant: {
285
- completed: null,
286
- errored: null
287
- },
288
- firstUserText: {
289
- text: "Run the build"
290
- }
291
- });
292
- adapter.db = db;
293
- const agents = await adapter.detectAgents();
294
- expect(agents[0]).toMatchObject({
295
- status: AgentStatus.RUNNING,
296
- sessionId: "sess-tool"
297
- });
298
- });
299
- it("returns running between steps (the bug fix) — no time.completed even during quiet moment", async ()=>{
300
- const now = Date.now();
301
- const procs = [
302
- {
303
- pid: 260,
304
- command: "opencode",
305
- cwd: "/proj-b",
306
- tty: "ttys005"
307
- }
308
- ];
309
- mockedListAgentProcesses.mockReturnValue(procs);
310
- mockedEnrichProcesses.mockReturnValue(procs);
311
- const db = makeDb({
312
- session: [
313
- {
314
- id: "sess-mid",
315
- directory: "/proj-b",
316
- time_created: now - 60_000
317
- }
318
- ],
319
- lastMessage: {
320
- role: "assistant",
321
- timeUpdated: now - 45_000
322
- },
323
- lastAssistant: {
324
- completed: null,
325
- errored: null
326
- },
327
- firstUserText: null
328
- });
329
- adapter.db = db;
330
- const agents = await adapter.detectAgents();
331
- expect(agents[0].status).toBe(AgentStatus.RUNNING);
332
- });
333
- it("returns waiting even when latest user message was metadata-updated after assistant completion", async ()=>{
334
- // Regression: OpenCode updates user.message.time_updated when appending
335
- // summary diffs after a turn finishes. Ordering by time_created (not
336
- // time_updated) keeps the assistant message correctly identified as latest.
337
- const now = Date.now();
338
- const procs = [
339
- {
340
- pid: 270,
341
- command: "opencode",
342
- cwd: "/proj-c",
343
- tty: "ttys006"
344
- }
345
- ];
346
- mockedListAgentProcesses.mockReturnValue(procs);
347
- mockedEnrichProcesses.mockReturnValue(procs);
348
- // makeDb returns lastMessage from the time_created-ordered query — supply the assistant.
349
- const db = makeDb({
350
- session: [
351
- {
352
- id: "sess-meta",
353
- directory: "/proj-c",
354
- time_created: now - 120_000
355
- }
356
- ],
357
- lastMessage: {
358
- role: "assistant",
359
- timeUpdated: now - 30_000
360
- },
361
- lastAssistant: {
362
- completed: now - 30_000,
363
- errored: null
364
- },
365
- firstUserText: null
366
- });
367
- adapter.db = db;
368
- const agents = await adapter.detectAgents();
369
- expect(agents[0].status).toBe(AgentStatus.WAITING);
370
- });
371
- it("returns running agent when last role is user (no assistant message yet)", async ()=>{
372
- const now = Date.now();
373
- const procs = [
374
- {
375
- pid: 300,
376
- command: "opencode",
377
- cwd: "/work",
378
- tty: "ttys003"
379
- }
380
- ];
381
- mockedListAgentProcesses.mockReturnValue(procs);
382
- mockedEnrichProcesses.mockReturnValue(procs);
383
- const db = makeDb({
384
- session: [
385
- {
386
- id: "sess-002",
387
- directory: "/work",
388
- time_created: now - 30000
389
- }
390
- ],
391
- lastMessage: {
392
- role: "user",
393
- timeUpdated: now - 30_000
394
- },
395
- lastAssistant: null,
396
- firstUserText: {
397
- text: "Add unit tests"
398
- }
399
- });
400
- adapter.db = db;
401
- const agents = await adapter.detectAgents();
402
- expect(agents[0]).toMatchObject({
403
- status: AgentStatus.RUNNING,
404
- sessionId: "sess-002"
405
- });
406
- });
407
- it("returns idle agent when last activity exceeds threshold", async ()=>{
408
- const now = Date.now();
409
- const staleTime = now - 10 * 60 * 1000; // 10 minutes ago
410
- const procs = [
411
- {
412
- pid: 400,
413
- command: "opencode",
414
- cwd: "/old-work",
415
- tty: "ttys004"
416
- }
417
- ];
418
- mockedListAgentProcesses.mockReturnValue(procs);
419
- mockedEnrichProcesses.mockReturnValue(procs);
420
- const db = makeDb({
421
- session: [
422
- {
423
- id: "sess-003",
424
- directory: "/old-work",
425
- time_created: staleTime
426
- }
427
- ],
428
- lastMessage: {
429
- role: "assistant",
430
- timeUpdated: staleTime
431
- },
432
- lastAssistant: {
433
- completed: staleTime,
434
- errored: null
435
- },
436
- firstUserText: null
437
- });
438
- adapter.db = db;
439
- const agents = await adapter.detectAgents();
440
- expect(agents[0]).toMatchObject({
441
- status: AgentStatus.IDLE,
442
- sessionId: "sess-003"
443
- });
444
- });
445
- });
446
- describe("getConversation", ()=>{
447
- it("returns empty array for invalid session ref", ()=>{
448
- const messages = adapter.getConversation("/no-separator-here");
449
- expect(messages).toEqual([]);
450
- });
451
- it("returns text messages from session parts", ()=>{
452
- const db = makeDb({
453
- parts: [
454
- {
455
- role: "user",
456
- partData: JSON.stringify({
457
- type: "text",
458
- text: "Hello agent"
459
- }),
460
- timeCreated: 1000
461
- },
462
- {
463
- role: "assistant",
464
- partData: JSON.stringify({
465
- type: "text",
466
- text: "Hi, how can I help?"
467
- }),
468
- timeCreated: 2000
469
- }
470
- ]
471
- });
472
- adapter.db = db;
473
- const ref = `${dbPath}::sess-abc`;
474
- const messages = adapter.getConversation(ref);
475
- expect(messages).toHaveLength(2);
476
- expect(messages[0]).toEqual({
477
- role: "user",
478
- content: "Hello agent"
479
- });
480
- expect(messages[1]).toEqual({
481
- role: "assistant",
482
- content: "Hi, how can I help?"
483
- });
484
- });
485
- it("skips reasoning parts when verbose is false", ()=>{
486
- const db = makeDb({
487
- parts: [
488
- {
489
- role: "assistant",
490
- partData: JSON.stringify({
491
- type: "reasoning",
492
- reasoning: "internal thought"
493
- }),
494
- timeCreated: 1000
495
- },
496
- {
497
- role: "assistant",
498
- partData: JSON.stringify({
499
- type: "text",
500
- text: "Answer"
501
- }),
502
- timeCreated: 2000
503
- }
504
- ]
505
- });
506
- adapter.db = db;
507
- const messages = adapter.getConversation(`${dbPath}::sess-x`);
508
- expect(messages).toHaveLength(1);
509
- expect(messages[0].content).toBe("Answer");
510
- });
511
- it("includes reasoning and tool parts when verbose is true", ()=>{
512
- const db = makeDb({
513
- parts: [
514
- {
515
- role: "assistant",
516
- partData: JSON.stringify({
517
- type: "reasoning",
518
- reasoning: "my thinking"
519
- }),
520
- timeCreated: 1000
521
- },
522
- {
523
- role: "assistant",
524
- partData: JSON.stringify({
525
- type: "tool",
526
- tool: "read_file"
527
- }),
528
- timeCreated: 2000
529
- }
530
- ]
531
- });
532
- adapter.db = db;
533
- const messages = adapter.getConversation(`${dbPath}::sess-y`, {
534
- verbose: true
535
- });
536
- expect(messages).toHaveLength(2);
537
- expect(messages[0].content).toContain("my thinking");
538
- expect(messages[1].content).toContain("read_file");
539
- });
540
- it("returns empty array when DB cannot be opened", ()=>{
541
- adapter.db = null;
542
- const messages = adapter.getConversation(`${dbPath}::sess-z`);
543
- expect(messages).toEqual([]);
544
- });
545
- });
546
- describe("listSessions", ()=>{
547
- it("returns empty array when DB does not exist", async ()=>{
548
- const sessions = await adapter.listSessions();
549
- expect(sessions).toEqual([]);
550
- });
551
- it("returns all sessions from DB", async ()=>{
552
- const now = Date.now();
553
- const db = makeDb({
554
- session: [
555
- {
556
- id: "sess-a",
557
- directory: "/proj-a",
558
- time_created: now - 3000
559
- },
560
- {
561
- id: "sess-b",
562
- directory: "/proj-b",
563
- time_created: now - 6000
564
- }
565
- ],
566
- lastMessage: null,
567
- lastAssistant: null,
568
- firstUserText: {
569
- text: "Build the feature"
570
- }
571
- });
572
- adapter.db = db;
573
- const sessions = await adapter.listSessions();
574
- expect(sessions).toHaveLength(2);
575
- expect(sessions[0]).toMatchObject({
576
- type: "opencode",
577
- sessionId: "sess-a",
578
- cwd: "/proj-a"
579
- });
580
- expect(sessions[1]).toMatchObject({
581
- type: "opencode",
582
- sessionId: "sess-b",
583
- cwd: "/proj-b"
584
- });
585
- expect(sessions[0].sessionFilePath).toContain("sess-a");
586
- });
587
- it("filters sessions by cwd when opts.cwd is set", async ()=>{
588
- const now = Date.now();
589
- const db = makeDb({
590
- session: [
591
- {
592
- id: "sess-a",
593
- directory: "/proj-a",
594
- time_created: now - 3000
595
- },
596
- {
597
- id: "sess-b",
598
- directory: "/proj-b",
599
- time_created: now - 6000
600
- }
601
- ],
602
- lastMessage: null,
603
- lastAssistant: null,
604
- firstUserText: null
605
- });
606
- adapter.db = db;
607
- const sessions = await adapter.listSessions({
608
- cwd: "/proj-a"
609
- });
610
- expect(sessions).toHaveLength(1);
611
- expect(sessions[0].sessionId).toBe("sess-a");
612
- });
613
- it("uses session time_created as startedAt and lastActive when no parts exist", async ()=>{
614
- const timeCreated = Date.now() - 120000;
615
- const db = makeDb({
616
- session: [
617
- {
618
- id: "sess-c",
619
- directory: "/repo",
620
- time_created: timeCreated
621
- }
622
- ],
623
- lastMessage: null,
624
- lastAssistant: null,
625
- firstUserText: null
626
- });
627
- adapter.db = db;
628
- const [session] = await adapter.listSessions();
629
- expect(session.startedAt.getTime()).toBeCloseTo(timeCreated, -2);
630
- expect(session.lastActive.getTime()).toBeCloseTo(timeCreated, -2);
631
- });
632
- });
633
- });
634
-
635
- //# sourceMappingURL=OpenCodeAdapter.test.js.map