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