@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.
- package/dist/AgentManager.d.ts +2 -0
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +25 -0
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +74 -0
- package/dist/__tests__/AgentManager.test.js.map +1 -1
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +28 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/CodexAdapter.test.js +7 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
- package/dist/__tests__/capacity/codex.test.js +2 -1
- package/dist/__tests__/capacity/codex.test.js.map +1 -1
- package/dist/__tests__/capacity/index.test.js +6 -3
- package/dist/__tests__/capacity/index.test.js.map +1 -1
- package/dist/__tests__/capacity/zai.test.js +213 -0
- package/dist/__tests__/capacity/zai.test.js.map +1 -0
- package/dist/__tests__/providers/copilot/CopilotAdapter.test.js +18 -0
- package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -1
- package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js +36 -0
- package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -1
- package/dist/__tests__/providers/grok/GrokAgentMapper.test.js +56 -0
- package/dist/__tests__/providers/grok/GrokAgentMapper.test.js.map +1 -0
- package/dist/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.js +19 -6
- package/dist/__tests__/providers/grok/GrokCliAdapter.test.js.map +1 -0
- package/dist/__tests__/providers/grok/GrokSessionLocator.test.js +133 -0
- package/dist/__tests__/providers/grok/GrokSessionLocator.test.js.map +1 -0
- package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js +213 -0
- package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/providers/opencode/OpenCodeAgentMapper.test.js +89 -0
- package/dist/__tests__/providers/opencode/OpenCodeAgentMapper.test.js.map +1 -0
- package/dist/__tests__/providers/opencode/OpenCodeSessionLocator.test.js +94 -0
- package/dist/__tests__/providers/opencode/OpenCodeSessionLocator.test.js.map +1 -0
- package/dist/__tests__/providers/opencode/OpenCodeSessionParser.test.js +147 -0
- package/dist/__tests__/providers/opencode/OpenCodeSessionParser.test.js.map +1 -0
- package/dist/__tests__/providers/pi/PiAdapter.test.js +30 -0
- package/dist/__tests__/providers/pi/PiAdapter.test.js.map +1 -1
- package/dist/__tests__/utils/session.test.js +18 -1
- package/dist/__tests__/utils/session.test.js.map +1 -1
- package/dist/adapters/AgentAdapter.d.ts +8 -0
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/index.d.ts +2 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +2 -2
- package/dist/adapters/index.js.map +1 -1
- package/dist/capacity/codex.d.ts +1 -0
- package/dist/capacity/codex.d.ts.map +1 -1
- package/dist/capacity/codex.js +9 -4
- package/dist/capacity/codex.js.map +1 -1
- package/dist/capacity/index.d.ts +8 -0
- package/dist/capacity/index.d.ts.map +1 -1
- package/dist/capacity/index.js +10 -9
- package/dist/capacity/index.js.map +1 -1
- package/dist/capacity/types.d.ts +8 -0
- package/dist/capacity/types.d.ts.map +1 -1
- package/dist/capacity/types.js.map +1 -1
- package/dist/capacity/zai.d.ts +14 -0
- package/dist/capacity/zai.d.ts.map +1 -0
- package/dist/capacity/zai.js +203 -0
- package/dist/capacity/zai.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/providers/claude/ClaudeCodeAdapter.d.ts +2 -0
- package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -1
- package/dist/providers/claude/ClaudeCodeAdapter.js +22 -19
- package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -1
- package/dist/providers/claude/ClaudeSessionLocator.d.ts +4 -0
- package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -1
- package/dist/providers/claude/ClaudeSessionLocator.js +18 -1
- package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -1
- package/dist/providers/codex/CodexAdapter.d.ts +1 -0
- package/dist/providers/codex/CodexAdapter.d.ts.map +1 -1
- package/dist/providers/codex/CodexAdapter.js +8 -0
- package/dist/providers/codex/CodexAdapter.js.map +1 -1
- package/dist/providers/copilot/CopilotAdapter.d.ts +2 -0
- package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -1
- package/dist/providers/copilot/CopilotAdapter.js +24 -9
- package/dist/providers/copilot/CopilotAdapter.js.map +1 -1
- package/dist/providers/copilot/CopilotSessionLocator.d.ts +1 -0
- package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -1
- package/dist/providers/copilot/CopilotSessionLocator.js +9 -1
- package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -1
- package/dist/providers/gemini/GeminiCliAdapter.d.ts +1 -0
- package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/providers/gemini/GeminiCliAdapter.js +9 -0
- package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -1
- package/dist/providers/grok/GrokAgentMapper.d.ts +14 -0
- package/dist/providers/grok/GrokAgentMapper.d.ts.map +1 -0
- package/dist/providers/grok/GrokAgentMapper.js +36 -0
- package/dist/providers/grok/GrokAgentMapper.js.map +1 -0
- package/dist/providers/grok/GrokCliAdapter.d.ts +36 -0
- package/dist/providers/grok/GrokCliAdapter.d.ts.map +1 -0
- package/dist/providers/grok/GrokCliAdapter.js +98 -0
- package/dist/providers/grok/GrokCliAdapter.js.map +1 -0
- package/dist/providers/grok/GrokSessionLocator.d.ts +62 -0
- package/dist/providers/grok/GrokSessionLocator.d.ts.map +1 -0
- package/dist/providers/grok/GrokSessionLocator.js +140 -0
- package/dist/providers/grok/GrokSessionLocator.js.map +1 -0
- package/dist/providers/grok/GrokSessionParser.d.ts +54 -0
- package/dist/providers/grok/GrokSessionParser.d.ts.map +1 -0
- package/dist/providers/grok/GrokSessionParser.js +122 -0
- package/dist/providers/grok/GrokSessionParser.js.map +1 -0
- package/dist/{adapters → providers/opencode}/OpenCodeAdapter.d.ts +6 -12
- package/dist/providers/opencode/OpenCodeAdapter.d.ts.map +1 -0
- package/dist/providers/opencode/OpenCodeAdapter.js +89 -0
- package/dist/providers/opencode/OpenCodeAdapter.js.map +1 -0
- package/dist/providers/opencode/OpenCodeAgentMapper.d.ts +11 -0
- package/dist/providers/opencode/OpenCodeAgentMapper.d.ts.map +1 -0
- package/dist/providers/opencode/OpenCodeAgentMapper.js +50 -0
- package/dist/providers/opencode/OpenCodeAgentMapper.js.map +1 -0
- package/dist/providers/opencode/OpenCodeSessionLocator.d.ts +23 -0
- package/dist/providers/opencode/OpenCodeSessionLocator.d.ts.map +1 -0
- package/dist/providers/opencode/OpenCodeSessionLocator.js +118 -0
- package/dist/providers/opencode/OpenCodeSessionLocator.js.map +1 -0
- package/dist/providers/opencode/OpenCodeSessionParser.d.ts +18 -0
- package/dist/providers/opencode/OpenCodeSessionParser.d.ts.map +1 -0
- package/dist/providers/opencode/OpenCodeSessionParser.js +104 -0
- package/dist/providers/opencode/OpenCodeSessionParser.js.map +1 -0
- package/dist/providers/pi/PiAdapter.d.ts +1 -0
- package/dist/providers/pi/PiAdapter.d.ts.map +1 -1
- package/dist/providers/pi/PiAdapter.js +10 -0
- package/dist/providers/pi/PiAdapter.js.map +1 -1
- package/dist/providers/pi/PiSessionLocator.d.ts +1 -0
- package/dist/providers/pi/PiSessionLocator.d.ts.map +1 -1
- package/dist/providers/pi/PiSessionLocator.js +8 -1
- package/dist/providers/pi/PiSessionLocator.js.map +1 -1
- package/dist/utils/session.d.ts +2 -0
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +3 -0
- package/dist/utils/session.js.map +1 -1
- package/package.json +1 -1
- package/src/AgentManager.ts +34 -0
- package/src/__tests__/AgentManager.test.ts +60 -0
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +23 -0
- package/src/__tests__/adapters/CodexAdapter.test.ts +7 -0
- package/src/__tests__/capacity/codex.test.ts +94 -26
- package/src/__tests__/capacity/fixtures/zai-auth.json +6 -0
- package/src/__tests__/capacity/fixtures/zai-quota-invalid.json +7 -0
- package/src/__tests__/capacity/fixtures/zai-quota.json +38 -0
- package/src/__tests__/capacity/index.test.ts +10 -3
- package/src/__tests__/capacity/zai.test.ts +186 -0
- package/src/__tests__/providers/copilot/CopilotAdapter.test.ts +12 -0
- package/src/__tests__/providers/gemini/GeminiCliAdapter.test.ts +21 -0
- package/src/__tests__/providers/grok/GrokAgentMapper.test.ts +57 -0
- package/src/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.ts +18 -9
- package/src/__tests__/providers/grok/GrokSessionLocator.test.ts +106 -0
- package/src/__tests__/providers/opencode/OpenCodeAdapter.test.ts +215 -0
- package/src/__tests__/providers/opencode/OpenCodeAgentMapper.test.ts +83 -0
- package/src/__tests__/providers/opencode/OpenCodeSessionLocator.test.ts +84 -0
- package/src/__tests__/providers/opencode/OpenCodeSessionParser.test.ts +148 -0
- package/src/__tests__/providers/pi/PiAdapter.test.ts +15 -0
- package/src/__tests__/utils/session.test.ts +11 -1
- package/src/adapters/AgentAdapter.ts +9 -0
- package/src/adapters/index.ts +2 -2
- package/src/capacity/codex.ts +96 -28
- package/src/capacity/index.ts +29 -11
- package/src/capacity/types.ts +8 -0
- package/src/capacity/zai.ts +237 -0
- package/src/index.ts +9 -4
- package/src/providers/claude/ClaudeCodeAdapter.ts +28 -23
- package/src/providers/claude/ClaudeSessionLocator.ts +20 -0
- package/src/providers/codex/CodexAdapter.ts +8 -0
- package/src/providers/copilot/CopilotAdapter.ts +26 -10
- package/src/providers/copilot/CopilotSessionLocator.ts +7 -1
- package/src/providers/gemini/GeminiCliAdapter.ts +8 -0
- package/src/providers/grok/GrokAgentMapper.ts +41 -0
- package/src/providers/grok/GrokCliAdapter.ts +125 -0
- package/src/providers/grok/GrokSessionLocator.ts +179 -0
- package/src/providers/grok/GrokSessionParser.ts +166 -0
- package/src/providers/opencode/OpenCodeAdapter.ts +116 -0
- package/src/providers/opencode/OpenCodeAgentMapper.ts +61 -0
- package/src/providers/opencode/OpenCodeSessionLocator.ts +151 -0
- package/src/providers/opencode/OpenCodeSessionParser.ts +145 -0
- package/src/providers/pi/PiAdapter.ts +12 -0
- package/src/providers/pi/PiSessionLocator.ts +16 -1
- package/src/utils/session.ts +11 -0
- package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +0 -1
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js +0 -636
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +0 -1
- package/dist/adapters/GrokCliAdapter.d.ts +0 -80
- package/dist/adapters/GrokCliAdapter.d.ts.map +0 -1
- package/dist/adapters/GrokCliAdapter.js +0 -310
- package/dist/adapters/GrokCliAdapter.js.map +0 -1
- package/dist/adapters/OpenCodeAdapter.d.ts.map +0 -1
- package/dist/adapters/OpenCodeAdapter.js +0 -289
- package/dist/adapters/OpenCodeAdapter.js.map +0 -1
- package/src/__tests__/adapters/OpenCodeAdapter.test.ts +0 -544
- package/src/adapters/GrokCliAdapter.ts +0 -408
- 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"}
|