@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
@@ -0,0 +1,133 @@
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 { GrokSessionLocator } from "../../../providers/grok/GrokSessionLocator.js";
6
+ describe("GrokSessionLocator", ()=>{
7
+ let baseDir;
8
+ let locator;
9
+ beforeEach(()=>{
10
+ baseDir = fs.mkdtempSync(path.join(os.tmpdir(), "grok-locator-test-"));
11
+ locator = new GrokSessionLocator({
12
+ baseDir
13
+ });
14
+ });
15
+ afterEach(()=>{
16
+ fs.rmSync(baseDir, {
17
+ recursive: true,
18
+ force: true
19
+ });
20
+ });
21
+ function writeSession(cwd, id, mtime) {
22
+ const sessionDir = path.join(baseDir, "sessions", encodeURIComponent(cwd), id);
23
+ fs.mkdirSync(sessionDir, {
24
+ recursive: true
25
+ });
26
+ const chatPath = path.join(sessionDir, "chat_history.jsonl");
27
+ fs.writeFileSync(chatPath, "");
28
+ if (mtime) fs.utimesSync(chatPath, mtime, mtime);
29
+ return sessionDir;
30
+ }
31
+ function proc(overrides = {}) {
32
+ return {
33
+ pid: 4242,
34
+ command: "grok",
35
+ cwd: "/repo/app",
36
+ tty: "ttys001",
37
+ ...overrides
38
+ };
39
+ }
40
+ describe("matchRunningProcesses", ()=>{
41
+ it("prefers the active_sessions.json cwd over the process cwd", ()=>{
42
+ const sessionDir = writeSession("/repo/real", "s1");
43
+ fs.writeFileSync(path.join(baseDir, "active_sessions.json"), JSON.stringify([
44
+ {
45
+ pid: 4242,
46
+ cwd: "/repo/real"
47
+ }
48
+ ]));
49
+ expect(locator.matchRunningProcesses([
50
+ proc({
51
+ cwd: "/wrong"
52
+ })
53
+ ])).toEqual([
54
+ {
55
+ process: expect.objectContaining({
56
+ pid: 4242
57
+ }),
58
+ cwd: "/repo/real",
59
+ sessionDir
60
+ }
61
+ ]);
62
+ });
63
+ it("ignores a malformed active_sessions.json and falls back to the process cwd", ()=>{
64
+ const sessionDir = writeSession("/repo/app", "s1");
65
+ fs.writeFileSync(path.join(baseDir, "active_sessions.json"), "{not json");
66
+ expect(locator.matchRunningProcesses([
67
+ proc()
68
+ ])[0]).toMatchObject({
69
+ cwd: "/repo/app",
70
+ sessionDir
71
+ });
72
+ });
73
+ it("picks the session with the newest chat_history.jsonl", ()=>{
74
+ writeSession("/repo/app", "old", new Date(Date.now() - 60_000));
75
+ const newest = writeSession("/repo/app", "new");
76
+ expect(locator.matchRunningProcesses([
77
+ proc()
78
+ ])[0].sessionDir).toBe(newest);
79
+ });
80
+ it("returns a null sessionDir when no cwd is known", ()=>{
81
+ expect(locator.matchRunningProcesses([
82
+ proc({
83
+ cwd: ""
84
+ })
85
+ ])[0]).toMatchObject({
86
+ cwd: "",
87
+ sessionDir: null
88
+ });
89
+ });
90
+ });
91
+ describe("historical sessions", ()=>{
92
+ it("discovers session dirs with the decoded group cwd", ()=>{
93
+ const a = writeSession("/repo/a", "s1");
94
+ const b = writeSession("/repo/b", "s2");
95
+ expect(locator.discoverHistoricalSessionDirs()).toEqual(expect.arrayContaining([
96
+ {
97
+ sessionDir: a,
98
+ defaultCwd: "/repo/a"
99
+ },
100
+ {
101
+ sessionDir: b,
102
+ defaultCwd: "/repo/b"
103
+ }
104
+ ]));
105
+ });
106
+ it("prefers the .cwd file over the encoded group name", ()=>{
107
+ const groupDir = path.join(baseDir, "sessions", "slug-abc123");
108
+ fs.mkdirSync(path.join(groupDir, "s1"), {
109
+ recursive: true
110
+ });
111
+ fs.writeFileSync(path.join(groupDir, ".cwd"), "/very/long/path\n");
112
+ expect(locator.discoverHistoricalSessionDirs()).toEqual([
113
+ {
114
+ sessionDir: path.join(groupDir, "s1"),
115
+ defaultCwd: "/very/long/path"
116
+ }
117
+ ]);
118
+ });
119
+ it("finds session dirs by id and rejects unsafe ids", ()=>{
120
+ const dir = writeSession("/repo/a", "s1");
121
+ writeSession("/repo/b", "s2");
122
+ expect(locator.findHistoricalSessionDirsById("s1")).toEqual([
123
+ {
124
+ sessionDir: dir,
125
+ defaultCwd: "/repo/a"
126
+ }
127
+ ]);
128
+ expect(locator.findHistoricalSessionDirsById("../s1")).toEqual([]);
129
+ });
130
+ });
131
+ });
132
+
133
+ //# sourceMappingURL=GrokSessionLocator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/__tests__/providers/grok/GrokSessionLocator.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\";\n\nimport type { ProcessInfo } from \"../../../adapters/AgentAdapter.js\";\nimport { GrokSessionLocator } from \"../../../providers/grok/GrokSessionLocator.js\";\n\ndescribe(\"GrokSessionLocator\", () => {\n let baseDir: string;\n let locator: GrokSessionLocator;\n\n beforeEach(() => {\n baseDir = fs.mkdtempSync(path.join(os.tmpdir(), \"grok-locator-test-\"));\n locator = new GrokSessionLocator({ baseDir });\n });\n\n afterEach(() => {\n fs.rmSync(baseDir, { recursive: true, force: true });\n });\n\n function writeSession(cwd: string, id: string, mtime?: Date): string {\n const sessionDir = path.join(baseDir, \"sessions\", encodeURIComponent(cwd), id);\n fs.mkdirSync(sessionDir, { recursive: true });\n const chatPath = path.join(sessionDir, \"chat_history.jsonl\");\n fs.writeFileSync(chatPath, \"\");\n if (mtime) fs.utimesSync(chatPath, mtime, mtime);\n return sessionDir;\n }\n\n function proc(overrides: Partial<ProcessInfo> = {}): ProcessInfo {\n return { pid: 4242, command: \"grok\", cwd: \"/repo/app\", tty: \"ttys001\", ...overrides };\n }\n\n describe(\"matchRunningProcesses\", () => {\n it(\"prefers the active_sessions.json cwd over the process cwd\", () => {\n const sessionDir = writeSession(\"/repo/real\", \"s1\");\n fs.writeFileSync(\n path.join(baseDir, \"active_sessions.json\"),\n JSON.stringify([{ pid: 4242, cwd: \"/repo/real\" }]),\n );\n\n expect(locator.matchRunningProcesses([proc({ cwd: \"/wrong\" })])).toEqual([\n { process: expect.objectContaining({ pid: 4242 }), cwd: \"/repo/real\", sessionDir },\n ]);\n });\n\n it(\"ignores a malformed active_sessions.json and falls back to the process cwd\", () => {\n const sessionDir = writeSession(\"/repo/app\", \"s1\");\n fs.writeFileSync(path.join(baseDir, \"active_sessions.json\"), \"{not json\");\n\n expect(locator.matchRunningProcesses([proc()])[0]).toMatchObject({\n cwd: \"/repo/app\",\n sessionDir,\n });\n });\n\n it(\"picks the session with the newest chat_history.jsonl\", () => {\n writeSession(\"/repo/app\", \"old\", new Date(Date.now() - 60_000));\n const newest = writeSession(\"/repo/app\", \"new\");\n\n expect(locator.matchRunningProcesses([proc()])[0].sessionDir).toBe(newest);\n });\n\n it(\"returns a null sessionDir when no cwd is known\", () => {\n expect(locator.matchRunningProcesses([proc({ cwd: \"\" })])[0]).toMatchObject({\n cwd: \"\",\n sessionDir: null,\n });\n });\n });\n\n describe(\"historical sessions\", () => {\n it(\"discovers session dirs with the decoded group cwd\", () => {\n const a = writeSession(\"/repo/a\", \"s1\");\n const b = writeSession(\"/repo/b\", \"s2\");\n\n expect(locator.discoverHistoricalSessionDirs()).toEqual(\n expect.arrayContaining([\n { sessionDir: a, defaultCwd: \"/repo/a\" },\n { sessionDir: b, defaultCwd: \"/repo/b\" },\n ]),\n );\n });\n\n it(\"prefers the .cwd file over the encoded group name\", () => {\n const groupDir = path.join(baseDir, \"sessions\", \"slug-abc123\");\n fs.mkdirSync(path.join(groupDir, \"s1\"), { recursive: true });\n fs.writeFileSync(path.join(groupDir, \".cwd\"), \"/very/long/path\\n\");\n\n expect(locator.discoverHistoricalSessionDirs()).toEqual([\n { sessionDir: path.join(groupDir, \"s1\"), defaultCwd: \"/very/long/path\" },\n ]);\n });\n\n it(\"finds session dirs by id and rejects unsafe ids\", () => {\n const dir = writeSession(\"/repo/a\", \"s1\");\n writeSession(\"/repo/b\", \"s2\");\n\n expect(locator.findHistoricalSessionDirsById(\"s1\")).toEqual([\n { sessionDir: dir, defaultCwd: \"/repo/a\" },\n ]);\n expect(locator.findHistoricalSessionDirsById(\"../s1\")).toEqual([]);\n });\n });\n});\n"],"names":["fs","os","path","afterEach","beforeEach","describe","expect","it","GrokSessionLocator","baseDir","locator","mkdtempSync","join","tmpdir","rmSync","recursive","force","writeSession","cwd","id","mtime","sessionDir","encodeURIComponent","mkdirSync","chatPath","writeFileSync","utimesSync","proc","overrides","pid","command","tty","JSON","stringify","matchRunningProcesses","toEqual","process","objectContaining","toMatchObject","Date","now","newest","toBe","a","b","discoverHistoricalSessionDirs","arrayContaining","defaultCwd","groupDir","dir","findHistoricalSessionDirsById"],"mappings":"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"}
@@ -0,0 +1,213 @@
1
+ import Database from "better-sqlite3";
2
+ import * as fs from "fs";
3
+ import * as os from "os";
4
+ import * as path from "path";
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+ import { AgentStatus } from "../../../adapters/AgentAdapter.js";
7
+ import { OpenCodeAdapter } from "../../../providers/opencode/OpenCodeAdapter.js";
8
+ describe("OpenCodeAdapter", ()=>{
9
+ let adapter;
10
+ let tmpDir;
11
+ let dbPath;
12
+ beforeEach(()=>{
13
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-adapter-test-"));
14
+ dbPath = path.join(tmpDir, "opencode.db");
15
+ adapter = new OpenCodeAdapter(dbPath);
16
+ });
17
+ afterEach(()=>{
18
+ adapter.close();
19
+ fs.rmSync(tmpDir, {
20
+ recursive: true,
21
+ force: true
22
+ });
23
+ });
24
+ it("detects opencode executables", ()=>{
25
+ expect(adapter.canHandle({
26
+ pid: 1,
27
+ command: "opencode",
28
+ cwd: "/repo"
29
+ })).toBe(true);
30
+ expect(adapter.canHandle({
31
+ pid: 2,
32
+ command: "/usr/bin/opencode.exe",
33
+ cwd: "/repo"
34
+ })).toBe(true);
35
+ expect(adapter.canHandle({
36
+ pid: 3,
37
+ command: "node /repo/opencode-plugin.js"
38
+ })).toBe(false);
39
+ });
40
+ it("returns process-only agents when the database is unavailable", async ()=>{
41
+ const agents = await adapter.detectAgents({
42
+ processes: [
43
+ {
44
+ pid: 100,
45
+ command: "opencode",
46
+ cwd: "/repo",
47
+ tty: "ttys001"
48
+ }
49
+ ]
50
+ });
51
+ expect(agents).toEqual([
52
+ expect.objectContaining({
53
+ type: "opencode",
54
+ status: AgentStatus.RUNNING,
55
+ pid: 100,
56
+ projectPath: "/repo",
57
+ sessionId: "pid-100",
58
+ summary: "OpenCode process running"
59
+ })
60
+ ]);
61
+ });
62
+ it("maps matching OpenCode sessions to agents", async ()=>{
63
+ const now = Date.now();
64
+ writeDatabase(dbPath, {
65
+ sessions: [
66
+ {
67
+ id: "sess-001",
68
+ directory: "/repo",
69
+ timeCreated: now - 60_000
70
+ }
71
+ ],
72
+ messages: [
73
+ {
74
+ id: "msg-user",
75
+ sessionId: "sess-001",
76
+ role: "user",
77
+ timeCreated: now - 50_000,
78
+ timeUpdated: now - 50_000
79
+ },
80
+ {
81
+ id: "msg-assistant",
82
+ sessionId: "sess-001",
83
+ role: "assistant",
84
+ timeCreated: now - 30_000,
85
+ timeUpdated: now - 30_000,
86
+ completed: now - 20_000
87
+ }
88
+ ],
89
+ parts: [
90
+ {
91
+ messageId: "msg-user",
92
+ sessionId: "sess-001",
93
+ type: "text",
94
+ text: "Refactor this module",
95
+ timeCreated: now - 50_000
96
+ }
97
+ ]
98
+ });
99
+ const process = {
100
+ pid: 200,
101
+ command: "opencode",
102
+ cwd: "/repo",
103
+ tty: "ttys002"
104
+ };
105
+ const [agent] = await adapter.detectAgents({
106
+ processes: [
107
+ process
108
+ ]
109
+ });
110
+ expect(agent).toMatchObject({
111
+ type: "opencode",
112
+ status: AgentStatus.WAITING,
113
+ pid: 200,
114
+ projectPath: "/repo",
115
+ sessionId: "sess-001",
116
+ summary: "Refactor this module",
117
+ sessionFilePath: `${dbPath}::sess-001`
118
+ });
119
+ });
120
+ it("returns conversation messages for encoded OpenCode session refs", ()=>{
121
+ writeDatabase(dbPath, {
122
+ sessions: [
123
+ {
124
+ id: "sess-abc",
125
+ directory: "/repo",
126
+ timeCreated: Date.now()
127
+ }
128
+ ],
129
+ messages: [
130
+ {
131
+ id: "msg-user",
132
+ sessionId: "sess-abc",
133
+ role: "user",
134
+ timeCreated: 1000,
135
+ timeUpdated: 1000
136
+ }
137
+ ],
138
+ parts: [
139
+ {
140
+ messageId: "msg-user",
141
+ sessionId: "sess-abc",
142
+ type: "text",
143
+ text: "Hello agent",
144
+ timeCreated: 1000
145
+ }
146
+ ]
147
+ });
148
+ expect(adapter.getConversation(`${dbPath}::sess-abc`)).toEqual([
149
+ {
150
+ role: "user",
151
+ content: "Hello agent"
152
+ }
153
+ ]);
154
+ expect(adapter.getConversation("/not-an-opencode-ref")).toEqual([]);
155
+ });
156
+ it("finds an exact session ID with a targeted database query", async ()=>{
157
+ const now = Date.now();
158
+ writeDatabase(dbPath, {
159
+ sessions: [
160
+ {
161
+ id: "wanted",
162
+ directory: "/repo",
163
+ timeCreated: now
164
+ },
165
+ {
166
+ id: "other",
167
+ directory: "/other",
168
+ timeCreated: now - 1_000
169
+ }
170
+ ]
171
+ });
172
+ await expect(adapter.findSessionsById("wanted")).resolves.toEqual([
173
+ expect.objectContaining({
174
+ type: "opencode",
175
+ sessionId: "wanted",
176
+ sessionFilePath: `${dbPath}::wanted`
177
+ })
178
+ ]);
179
+ });
180
+ });
181
+ function writeDatabase(filePath, data) {
182
+ const db = new Database(filePath);
183
+ db.exec(`
184
+ CREATE TABLE session (id TEXT, directory TEXT, time_created INTEGER);
185
+ CREATE TABLE message (id TEXT, session_id TEXT, data TEXT, time_created INTEGER, time_updated INTEGER);
186
+ CREATE TABLE part (message_id TEXT, session_id TEXT, data TEXT, time_created INTEGER);
187
+ `);
188
+ const insertSession = db.prepare("INSERT INTO session (id, directory, time_created) VALUES (?, ?, ?)");
189
+ for (const session of data.sessions){
190
+ insertSession.run(session.id, session.directory, session.timeCreated);
191
+ }
192
+ const insertMessage = db.prepare("INSERT INTO message (id, session_id, data, time_created, time_updated) VALUES (?, ?, ?, ?, ?)");
193
+ for (const message of data.messages ?? []){
194
+ insertMessage.run(message.id, message.sessionId, JSON.stringify({
195
+ role: message.role,
196
+ time: message.completed === undefined ? {} : {
197
+ completed: message.completed
198
+ }
199
+ }), message.timeCreated, message.timeUpdated);
200
+ }
201
+ const insertPart = db.prepare("INSERT INTO part (message_id, session_id, data, time_created) VALUES (?, ?, ?, ?)");
202
+ for (const part of data.parts ?? []){
203
+ insertPart.run(part.messageId, part.sessionId, JSON.stringify({
204
+ type: part.type,
205
+ text: part.text,
206
+ reasoning: part.reasoning,
207
+ tool: part.tool
208
+ }), part.timeCreated);
209
+ }
210
+ db.close();
211
+ }
212
+
213
+ //# sourceMappingURL=OpenCodeAdapter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/__tests__/providers/opencode/OpenCodeAdapter.test.ts"],"sourcesContent":["import Database from \"better-sqlite3\";\nimport * 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 { OpenCodeAdapter } from \"../../../providers/opencode/OpenCodeAdapter.js\";\n\ndescribe(\"OpenCodeAdapter\", () => {\n let adapter: OpenCodeAdapter;\n let tmpDir: string;\n let dbPath: string;\n\n beforeEach(() => {\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), \"opencode-adapter-test-\"));\n dbPath = path.join(tmpDir, \"opencode.db\");\n adapter = new OpenCodeAdapter(dbPath);\n });\n\n afterEach(() => {\n adapter.close();\n fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n it(\"detects opencode executables\", () => {\n expect(adapter.canHandle({ pid: 1, command: \"opencode\", cwd: \"/repo\" })).toBe(true);\n expect(adapter.canHandle({ pid: 2, command: \"/usr/bin/opencode.exe\", cwd: \"/repo\" })).toBe(\n true,\n );\n expect(adapter.canHandle({ pid: 3, command: \"node /repo/opencode-plugin.js\" })).toBe(false);\n });\n\n it(\"returns process-only agents when the database is unavailable\", async () => {\n const agents = await adapter.detectAgents({\n processes: [{ pid: 100, command: \"opencode\", cwd: \"/repo\", tty: \"ttys001\" }],\n });\n\n expect(agents).toEqual([\n expect.objectContaining({\n type: \"opencode\",\n status: AgentStatus.RUNNING,\n pid: 100,\n projectPath: \"/repo\",\n sessionId: \"pid-100\",\n summary: \"OpenCode process running\",\n }),\n ]);\n });\n\n it(\"maps matching OpenCode sessions to agents\", async () => {\n const now = Date.now();\n writeDatabase(dbPath, {\n sessions: [{ id: \"sess-001\", directory: \"/repo\", timeCreated: now - 60_000 }],\n messages: [\n {\n id: \"msg-user\",\n sessionId: \"sess-001\",\n role: \"user\",\n timeCreated: now - 50_000,\n timeUpdated: now - 50_000,\n },\n {\n id: \"msg-assistant\",\n sessionId: \"sess-001\",\n role: \"assistant\",\n timeCreated: now - 30_000,\n timeUpdated: now - 30_000,\n completed: now - 20_000,\n },\n ],\n parts: [\n {\n messageId: \"msg-user\",\n sessionId: \"sess-001\",\n type: \"text\",\n text: \"Refactor this module\",\n timeCreated: now - 50_000,\n },\n ],\n });\n\n const process: ProcessInfo = { pid: 200, command: \"opencode\", cwd: \"/repo\", tty: \"ttys002\" };\n const [agent] = await adapter.detectAgents({ processes: [process] });\n\n expect(agent).toMatchObject({\n type: \"opencode\",\n status: AgentStatus.WAITING,\n pid: 200,\n projectPath: \"/repo\",\n sessionId: \"sess-001\",\n summary: \"Refactor this module\",\n sessionFilePath: `${dbPath}::sess-001`,\n });\n });\n\n it(\"returns conversation messages for encoded OpenCode session refs\", () => {\n writeDatabase(dbPath, {\n sessions: [{ id: \"sess-abc\", directory: \"/repo\", timeCreated: Date.now() }],\n messages: [\n {\n id: \"msg-user\",\n sessionId: \"sess-abc\",\n role: \"user\",\n timeCreated: 1000,\n timeUpdated: 1000,\n },\n ],\n parts: [\n {\n messageId: \"msg-user\",\n sessionId: \"sess-abc\",\n type: \"text\",\n text: \"Hello agent\",\n timeCreated: 1000,\n },\n ],\n });\n\n expect(adapter.getConversation(`${dbPath}::sess-abc`)).toEqual([\n { role: \"user\", content: \"Hello agent\" },\n ]);\n expect(adapter.getConversation(\"/not-an-opencode-ref\")).toEqual([]);\n });\n\n it(\"finds an exact session ID with a targeted database query\", async () => {\n const now = Date.now();\n writeDatabase(dbPath, {\n sessions: [\n { id: \"wanted\", directory: \"/repo\", timeCreated: now },\n { id: \"other\", directory: \"/other\", timeCreated: now - 1_000 },\n ],\n });\n\n await expect(adapter.findSessionsById(\"wanted\")).resolves.toEqual([\n expect.objectContaining({\n type: \"opencode\",\n sessionId: \"wanted\",\n sessionFilePath: `${dbPath}::wanted`,\n }),\n ]);\n });\n});\n\nfunction writeDatabase(\n filePath: string,\n data: {\n sessions: Array<{ id: string; directory: string; timeCreated: number }>;\n messages?: Array<{\n id: string;\n sessionId: string;\n role: string;\n timeCreated: number;\n timeUpdated: number;\n completed?: number;\n }>;\n parts?: Array<{\n messageId: string;\n sessionId: string;\n type: string;\n text?: string;\n reasoning?: string;\n tool?: string;\n timeCreated: number;\n }>;\n },\n): void {\n const db = new Database(filePath);\n db.exec(`\n CREATE TABLE session (id TEXT, directory TEXT, time_created INTEGER);\n CREATE TABLE message (id TEXT, session_id TEXT, data TEXT, time_created INTEGER, time_updated INTEGER);\n CREATE TABLE part (message_id TEXT, session_id TEXT, data TEXT, time_created INTEGER);\n `);\n\n const insertSession = db.prepare(\n \"INSERT INTO session (id, directory, time_created) VALUES (?, ?, ?)\",\n );\n for (const session of data.sessions) {\n insertSession.run(session.id, session.directory, session.timeCreated);\n }\n\n const insertMessage = db.prepare(\n \"INSERT INTO message (id, session_id, data, time_created, time_updated) VALUES (?, ?, ?, ?, ?)\",\n );\n for (const message of data.messages ?? []) {\n insertMessage.run(\n message.id,\n message.sessionId,\n JSON.stringify({\n role: message.role,\n time: message.completed === undefined ? {} : { completed: message.completed },\n }),\n message.timeCreated,\n message.timeUpdated,\n );\n }\n\n const insertPart = db.prepare(\n \"INSERT INTO part (message_id, session_id, data, time_created) VALUES (?, ?, ?, ?)\",\n );\n for (const part of data.parts ?? []) {\n insertPart.run(\n part.messageId,\n part.sessionId,\n JSON.stringify({\n type: part.type,\n text: part.text,\n reasoning: part.reasoning,\n tool: part.tool,\n }),\n part.timeCreated,\n );\n }\n\n db.close();\n}\n"],"names":["Database","fs","os","path","afterEach","beforeEach","describe","expect","it","AgentStatus","OpenCodeAdapter","adapter","tmpDir","dbPath","mkdtempSync","join","tmpdir","close","rmSync","recursive","force","canHandle","pid","command","cwd","toBe","agents","detectAgents","processes","tty","toEqual","objectContaining","type","status","RUNNING","projectPath","sessionId","summary","now","Date","writeDatabase","sessions","id","directory","timeCreated","messages","role","timeUpdated","completed","parts","messageId","text","process","agent","toMatchObject","WAITING","sessionFilePath","getConversation","content","findSessionsById","resolves","filePath","data","db","exec","insertSession","prepare","session","run","insertMessage","message","JSON","stringify","time","undefined","insertPart","part","reasoning","tool"],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { AgentStatus } from "../../../adapters/AgentAdapter.js";
3
+ import { OpenCodeAgentMapper } from "../../../providers/opencode/OpenCodeAgentMapper.js";
4
+ describe("OpenCodeAgentMapper", ()=>{
5
+ const mapper = new OpenCodeAgentMapper("/tmp/opencode.db");
6
+ it("maps completed assistant sessions as waiting", ()=>{
7
+ const now = Date.now();
8
+ const agent = mapper.mapSessionToAgent({
9
+ sessionId: "sess-1",
10
+ directory: "/repo",
11
+ timeCreated: now - 60_000
12
+ }, {
13
+ lastRole: "assistant",
14
+ lastTimeUpdated: now - 30_000,
15
+ lastAssistantCompleted: true,
16
+ lastAssistantErrored: false,
17
+ summary: "Ship the feature"
18
+ }, {
19
+ pid: 123,
20
+ command: "opencode",
21
+ cwd: "/repo"
22
+ });
23
+ expect(agent).toMatchObject({
24
+ type: "opencode",
25
+ status: AgentStatus.WAITING,
26
+ summary: "Ship the feature",
27
+ pid: 123,
28
+ projectPath: "/repo",
29
+ sessionId: "sess-1",
30
+ sessionFilePath: "/tmp/opencode.db::sess-1"
31
+ });
32
+ });
33
+ it("keeps incomplete assistant sessions running even when they are quiet briefly", ()=>{
34
+ const now = Date.now();
35
+ const agent = mapper.mapSessionToAgent({
36
+ sessionId: "sess-2",
37
+ directory: "/repo",
38
+ timeCreated: now - 120_000
39
+ }, {
40
+ lastRole: "assistant",
41
+ lastTimeUpdated: now - 120_000,
42
+ lastAssistantCompleted: false,
43
+ lastAssistantErrored: false,
44
+ summary: ""
45
+ }, {
46
+ pid: 124,
47
+ command: "opencode",
48
+ cwd: "/repo"
49
+ });
50
+ expect(agent.status).toBe(AgentStatus.RUNNING);
51
+ expect(agent.summary).toBe("OpenCode session active");
52
+ });
53
+ it("marks stale sessions idle", ()=>{
54
+ const staleTime = Date.now() - 10 * 60 * 1000;
55
+ const agent = mapper.mapSessionToAgent({
56
+ sessionId: "sess-3",
57
+ directory: "/old",
58
+ timeCreated: staleTime
59
+ }, {
60
+ lastRole: "assistant",
61
+ lastTimeUpdated: staleTime,
62
+ lastAssistantCompleted: true,
63
+ lastAssistantErrored: false,
64
+ summary: ""
65
+ }, {
66
+ pid: 125,
67
+ command: "opencode",
68
+ cwd: "/old"
69
+ });
70
+ expect(agent.status).toBe(AgentStatus.IDLE);
71
+ });
72
+ it("maps unmatched processes as running process-only agents", ()=>{
73
+ const agent = mapper.mapProcessOnlyAgent({
74
+ pid: 500,
75
+ command: "opencode",
76
+ cwd: "/repo"
77
+ });
78
+ expect(agent).toMatchObject({
79
+ type: "opencode",
80
+ status: AgentStatus.RUNNING,
81
+ summary: "OpenCode process running",
82
+ pid: 500,
83
+ projectPath: "/repo",
84
+ sessionId: "pid-500"
85
+ });
86
+ });
87
+ });
88
+
89
+ //# sourceMappingURL=OpenCodeAgentMapper.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/__tests__/providers/opencode/OpenCodeAgentMapper.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { AgentStatus } from \"../../../adapters/AgentAdapter.js\";\nimport { OpenCodeAgentMapper } from \"../../../providers/opencode/OpenCodeAgentMapper.js\";\n\ndescribe(\"OpenCodeAgentMapper\", () => {\n const mapper = new OpenCodeAgentMapper(\"/tmp/opencode.db\");\n\n it(\"maps completed assistant sessions as waiting\", () => {\n const now = Date.now();\n\n const agent = mapper.mapSessionToAgent(\n { sessionId: \"sess-1\", directory: \"/repo\", timeCreated: now - 60_000 },\n {\n lastRole: \"assistant\",\n lastTimeUpdated: now - 30_000,\n lastAssistantCompleted: true,\n lastAssistantErrored: false,\n summary: \"Ship the feature\",\n },\n { pid: 123, command: \"opencode\", cwd: \"/repo\" },\n );\n\n expect(agent).toMatchObject({\n type: \"opencode\",\n status: AgentStatus.WAITING,\n summary: \"Ship the feature\",\n pid: 123,\n projectPath: \"/repo\",\n sessionId: \"sess-1\",\n sessionFilePath: \"/tmp/opencode.db::sess-1\",\n });\n });\n\n it(\"keeps incomplete assistant sessions running even when they are quiet briefly\", () => {\n const now = Date.now();\n\n const agent = mapper.mapSessionToAgent(\n { sessionId: \"sess-2\", directory: \"/repo\", timeCreated: now - 120_000 },\n {\n lastRole: \"assistant\",\n lastTimeUpdated: now - 120_000,\n lastAssistantCompleted: false,\n lastAssistantErrored: false,\n summary: \"\",\n },\n { pid: 124, command: \"opencode\", cwd: \"/repo\" },\n );\n\n expect(agent.status).toBe(AgentStatus.RUNNING);\n expect(agent.summary).toBe(\"OpenCode session active\");\n });\n\n it(\"marks stale sessions idle\", () => {\n const staleTime = Date.now() - 10 * 60 * 1000;\n\n const agent = mapper.mapSessionToAgent(\n { sessionId: \"sess-3\", directory: \"/old\", timeCreated: staleTime },\n {\n lastRole: \"assistant\",\n lastTimeUpdated: staleTime,\n lastAssistantCompleted: true,\n lastAssistantErrored: false,\n summary: \"\",\n },\n { pid: 125, command: \"opencode\", cwd: \"/old\" },\n );\n\n expect(agent.status).toBe(AgentStatus.IDLE);\n });\n\n it(\"maps unmatched processes as running process-only agents\", () => {\n const agent = mapper.mapProcessOnlyAgent({ pid: 500, command: \"opencode\", cwd: \"/repo\" });\n\n expect(agent).toMatchObject({\n type: \"opencode\",\n status: AgentStatus.RUNNING,\n summary: \"OpenCode process running\",\n pid: 500,\n projectPath: \"/repo\",\n sessionId: \"pid-500\",\n });\n });\n});\n"],"names":["describe","expect","it","AgentStatus","OpenCodeAgentMapper","mapper","now","Date","agent","mapSessionToAgent","sessionId","directory","timeCreated","lastRole","lastTimeUpdated","lastAssistantCompleted","lastAssistantErrored","summary","pid","command","cwd","toMatchObject","type","status","WAITING","projectPath","sessionFilePath","toBe","RUNNING","staleTime","IDLE","mapProcessOnlyAgent"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAS;AAC9C,SAASC,WAAW,QAAQ,oCAAoC;AAChE,SAASC,mBAAmB,QAAQ,qDAAqD;AAEzFJ,SAAS,uBAAuB;IAC9B,MAAMK,SAAS,IAAID,oBAAoB;IAEvCF,GAAG,gDAAgD;QACjD,MAAMI,MAAMC,KAAKD,GAAG;QAEpB,MAAME,QAAQH,OAAOI,iBAAiB,CACpC;YAAEC,WAAW;YAAUC,WAAW;YAASC,aAAaN,MAAM;QAAO,GACrE;YACEO,UAAU;YACVC,iBAAiBR,MAAM;YACvBS,wBAAwB;YACxBC,sBAAsB;YACtBC,SAAS;QACX,GACA;YAAEC,KAAK;YAAKC,SAAS;YAAYC,KAAK;QAAQ;QAGhDnB,OAAOO,OAAOa,aAAa,CAAC;YAC1BC,MAAM;YACNC,QAAQpB,YAAYqB,OAAO;YAC3BP,SAAS;YACTC,KAAK;YACLO,aAAa;YACbf,WAAW;YACXgB,iBAAiB;QACnB;IACF;IAEAxB,GAAG,gFAAgF;QACjF,MAAMI,MAAMC,KAAKD,GAAG;QAEpB,MAAME,QAAQH,OAAOI,iBAAiB,CACpC;YAAEC,WAAW;YAAUC,WAAW;YAASC,aAAaN,MAAM;QAAQ,GACtE;YACEO,UAAU;YACVC,iBAAiBR,MAAM;YACvBS,wBAAwB;YACxBC,sBAAsB;YACtBC,SAAS;QACX,GACA;YAAEC,KAAK;YAAKC,SAAS;YAAYC,KAAK;QAAQ;QAGhDnB,OAAOO,MAAMe,MAAM,EAAEI,IAAI,CAACxB,YAAYyB,OAAO;QAC7C3B,OAAOO,MAAMS,OAAO,EAAEU,IAAI,CAAC;IAC7B;IAEAzB,GAAG,6BAA6B;QAC9B,MAAM2B,YAAYtB,KAAKD,GAAG,KAAK,KAAK,KAAK;QAEzC,MAAME,QAAQH,OAAOI,iBAAiB,CACpC;YAAEC,WAAW;YAAUC,WAAW;YAAQC,aAAaiB;QAAU,GACjE;YACEhB,UAAU;YACVC,iBAAiBe;YACjBd,wBAAwB;YACxBC,sBAAsB;YACtBC,SAAS;QACX,GACA;YAAEC,KAAK;YAAKC,SAAS;YAAYC,KAAK;QAAO;QAG/CnB,OAAOO,MAAMe,MAAM,EAAEI,IAAI,CAACxB,YAAY2B,IAAI;IAC5C;IAEA5B,GAAG,2DAA2D;QAC5D,MAAMM,QAAQH,OAAO0B,mBAAmB,CAAC;YAAEb,KAAK;YAAKC,SAAS;YAAYC,KAAK;QAAQ;QAEvFnB,OAAOO,OAAOa,aAAa,CAAC;YAC1BC,MAAM;YACNC,QAAQpB,YAAYyB,OAAO;YAC3BX,SAAS;YACTC,KAAK;YACLO,aAAa;YACbf,WAAW;QACb;IACF;AACF"}
@@ -0,0 +1,94 @@
1
+ import Database from "better-sqlite3";
2
+ import * as fs from "fs";
3
+ import * as os from "os";
4
+ import * as path from "path";
5
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
+ import { OpenCodeSessionLocator } from "../../../providers/opencode/OpenCodeSessionLocator.js";
7
+ import { OpenCodeSessionParser } from "../../../providers/opencode/OpenCodeSessionParser.js";
8
+ describe("OpenCodeSessionLocator", ()=>{
9
+ let tmpDir;
10
+ let dbPath;
11
+ let locator;
12
+ beforeEach(()=>{
13
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-locator-test-"));
14
+ dbPath = path.join(tmpDir, "opencode.db");
15
+ locator = new OpenCodeSessionLocator(dbPath, new OpenCodeSessionParser());
16
+ });
17
+ afterEach(()=>{
18
+ locator.close();
19
+ fs.rmSync(tmpDir, {
20
+ recursive: true,
21
+ force: true
22
+ });
23
+ });
24
+ it("returns null when the database file does not exist", ()=>{
25
+ expect(locator.openDb()).toBeNull();
26
+ expect(locator.listSessions()).toEqual([]);
27
+ });
28
+ it("finds the newest session for a directory", ()=>{
29
+ writeDatabase(dbPath, [
30
+ {
31
+ id: "old",
32
+ directory: "/repo",
33
+ timeCreated: 1000
34
+ },
35
+ {
36
+ id: "new",
37
+ directory: "/repo",
38
+ timeCreated: 2000
39
+ },
40
+ {
41
+ id: "other",
42
+ directory: "/other",
43
+ timeCreated: 3000
44
+ }
45
+ ]);
46
+ const db = locator.openDb();
47
+ expect(db).not.toBeNull();
48
+ expect(locator.findSessionForDirectory(db, "/repo")).toEqual({
49
+ sessionId: "new",
50
+ directory: "/repo",
51
+ timeCreated: 2000
52
+ });
53
+ });
54
+ it("lists sessions and filters by cwd", ()=>{
55
+ writeDatabase(dbPath, [
56
+ {
57
+ id: "sess-a",
58
+ directory: "/repo-a",
59
+ timeCreated: 1000
60
+ },
61
+ {
62
+ id: "sess-b",
63
+ directory: "/repo-b",
64
+ timeCreated: 2000
65
+ }
66
+ ]);
67
+ const sessions = locator.listSessions({
68
+ cwd: "/repo-a"
69
+ });
70
+ expect(sessions).toEqual([
71
+ expect.objectContaining({
72
+ type: "opencode",
73
+ sessionId: "sess-a",
74
+ cwd: "/repo-a",
75
+ sessionFilePath: `${dbPath}::sess-a`
76
+ })
77
+ ]);
78
+ });
79
+ });
80
+ function writeDatabase(filePath, sessions) {
81
+ const db = new Database(filePath);
82
+ db.exec(`
83
+ CREATE TABLE session (id TEXT, directory TEXT, time_created INTEGER);
84
+ CREATE TABLE message (id TEXT, session_id TEXT, data TEXT, time_created INTEGER, time_updated INTEGER);
85
+ CREATE TABLE part (message_id TEXT, session_id TEXT, data TEXT, time_created INTEGER);
86
+ `);
87
+ const insertSession = db.prepare("INSERT INTO session (id, directory, time_created) VALUES (?, ?, ?)");
88
+ for (const session of sessions){
89
+ insertSession.run(session.id, session.directory, session.timeCreated);
90
+ }
91
+ db.close();
92
+ }
93
+
94
+ //# sourceMappingURL=OpenCodeSessionLocator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/__tests__/providers/opencode/OpenCodeSessionLocator.test.ts"],"sourcesContent":["import Database from \"better-sqlite3\";\nimport * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\nimport { afterEach, beforeEach, describe, expect, it } from \"vitest\";\nimport { OpenCodeSessionLocator } from \"../../../providers/opencode/OpenCodeSessionLocator.js\";\nimport { OpenCodeSessionParser } from \"../../../providers/opencode/OpenCodeSessionParser.js\";\n\ndescribe(\"OpenCodeSessionLocator\", () => {\n let tmpDir: string;\n let dbPath: string;\n let locator: OpenCodeSessionLocator;\n\n beforeEach(() => {\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), \"opencode-locator-test-\"));\n dbPath = path.join(tmpDir, \"opencode.db\");\n locator = new OpenCodeSessionLocator(dbPath, new OpenCodeSessionParser());\n });\n\n afterEach(() => {\n locator.close();\n fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n it(\"returns null when the database file does not exist\", () => {\n expect(locator.openDb()).toBeNull();\n expect(locator.listSessions()).toEqual([]);\n });\n\n it(\"finds the newest session for a directory\", () => {\n writeDatabase(dbPath, [\n { id: \"old\", directory: \"/repo\", timeCreated: 1000 },\n { id: \"new\", directory: \"/repo\", timeCreated: 2000 },\n { id: \"other\", directory: \"/other\", timeCreated: 3000 },\n ]);\n\n const db = locator.openDb();\n expect(db).not.toBeNull();\n expect(locator.findSessionForDirectory(db!, \"/repo\")).toEqual({\n sessionId: \"new\",\n directory: \"/repo\",\n timeCreated: 2000,\n });\n });\n\n it(\"lists sessions and filters by cwd\", () => {\n writeDatabase(dbPath, [\n { id: \"sess-a\", directory: \"/repo-a\", timeCreated: 1000 },\n { id: \"sess-b\", directory: \"/repo-b\", timeCreated: 2000 },\n ]);\n\n const sessions = locator.listSessions({ cwd: \"/repo-a\" });\n\n expect(sessions).toEqual([\n expect.objectContaining({\n type: \"opencode\",\n sessionId: \"sess-a\",\n cwd: \"/repo-a\",\n sessionFilePath: `${dbPath}::sess-a`,\n }),\n ]);\n });\n});\n\nfunction writeDatabase(\n filePath: string,\n sessions: Array<{ id: string; directory: string; timeCreated: number }>,\n): void {\n const db = new Database(filePath);\n db.exec(`\n CREATE TABLE session (id TEXT, directory TEXT, time_created INTEGER);\n CREATE TABLE message (id TEXT, session_id TEXT, data TEXT, time_created INTEGER, time_updated INTEGER);\n CREATE TABLE part (message_id TEXT, session_id TEXT, data TEXT, time_created INTEGER);\n `);\n\n const insertSession = db.prepare(\n \"INSERT INTO session (id, directory, time_created) VALUES (?, ?, ?)\",\n );\n for (const session of sessions) {\n insertSession.run(session.id, session.directory, session.timeCreated);\n }\n\n db.close();\n}\n"],"names":["Database","fs","os","path","afterEach","beforeEach","describe","expect","it","OpenCodeSessionLocator","OpenCodeSessionParser","tmpDir","dbPath","locator","mkdtempSync","join","tmpdir","close","rmSync","recursive","force","openDb","toBeNull","listSessions","toEqual","writeDatabase","id","directory","timeCreated","db","not","findSessionForDirectory","sessionId","sessions","cwd","objectContaining","type","sessionFilePath","filePath","exec","insertSession","prepare","session","run"],"mappings":"AAAA,OAAOA,cAAc,iBAAiB;AACtC,YAAYC,QAAQ,KAAK;AACzB,YAAYC,QAAQ,KAAK;AACzB,YAAYC,UAAU,OAAO;AAC7B,SAASC,SAAS,EAAEC,UAAU,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAS;AACrE,SAASC,sBAAsB,QAAQ,wDAAwD;AAC/F,SAASC,qBAAqB,QAAQ,uDAAuD;AAE7FJ,SAAS,0BAA0B;IACjC,IAAIK;IACJ,IAAIC;IACJ,IAAIC;IAEJR,WAAW;QACTM,SAASV,GAAGa,WAAW,CAACX,KAAKY,IAAI,CAACb,GAAGc,MAAM,IAAI;QAC/CJ,SAAST,KAAKY,IAAI,CAACJ,QAAQ;QAC3BE,UAAU,IAAIJ,uBAAuBG,QAAQ,IAAIF;IACnD;IAEAN,UAAU;QACRS,QAAQI,KAAK;QACbhB,GAAGiB,MAAM,CAACP,QAAQ;YAAEQ,WAAW;YAAMC,OAAO;QAAK;IACnD;IAEAZ,GAAG,sDAAsD;QACvDD,OAAOM,QAAQQ,MAAM,IAAIC,QAAQ;QACjCf,OAAOM,QAAQU,YAAY,IAAIC,OAAO,CAAC,EAAE;IAC3C;IAEAhB,GAAG,4CAA4C;QAC7CiB,cAAcb,QAAQ;YACpB;gBAAEc,IAAI;gBAAOC,WAAW;gBAASC,aAAa;YAAK;YACnD;gBAAEF,IAAI;gBAAOC,WAAW;gBAASC,aAAa;YAAK;YACnD;gBAAEF,IAAI;gBAASC,WAAW;gBAAUC,aAAa;YAAK;SACvD;QAED,MAAMC,KAAKhB,QAAQQ,MAAM;QACzBd,OAAOsB,IAAIC,GAAG,CAACR,QAAQ;QACvBf,OAAOM,QAAQkB,uBAAuB,CAACF,IAAK,UAAUL,OAAO,CAAC;YAC5DQ,WAAW;YACXL,WAAW;YACXC,aAAa;QACf;IACF;IAEApB,GAAG,qCAAqC;QACtCiB,cAAcb,QAAQ;YACpB;gBAAEc,IAAI;gBAAUC,WAAW;gBAAWC,aAAa;YAAK;YACxD;gBAAEF,IAAI;gBAAUC,WAAW;gBAAWC,aAAa;YAAK;SACzD;QAED,MAAMK,WAAWpB,QAAQU,YAAY,CAAC;YAAEW,KAAK;QAAU;QAEvD3B,OAAO0B,UAAUT,OAAO,CAAC;YACvBjB,OAAO4B,gBAAgB,CAAC;gBACtBC,MAAM;gBACNJ,WAAW;gBACXE,KAAK;gBACLG,iBAAiB,GAAGzB,OAAO,QAAQ,CAAC;YACtC;SACD;IACH;AACF;AAEA,SAASa,cACPa,QAAgB,EAChBL,QAAuE;IAEvE,MAAMJ,KAAK,IAAI7B,SAASsC;IACxBT,GAAGU,IAAI,CAAC,CAAC;;;;EAIT,CAAC;IAED,MAAMC,gBAAgBX,GAAGY,OAAO,CAC9B;IAEF,KAAK,MAAMC,WAAWT,SAAU;QAC9BO,cAAcG,GAAG,CAACD,QAAQhB,EAAE,EAAEgB,QAAQf,SAAS,EAAEe,QAAQd,WAAW;IACtE;IAEAC,GAAGZ,KAAK;AACV"}