@agentmeshhq/agent 0.1.17 → 0.2.1

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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -0
  3. package/dist/__tests__/orphan-process.test.d.ts +11 -0
  4. package/dist/__tests__/orphan-process.test.js +286 -0
  5. package/dist/__tests__/orphan-process.test.js.map +1 -0
  6. package/dist/__tests__/runner.test.js +16 -0
  7. package/dist/__tests__/runner.test.js.map +1 -1
  8. package/dist/__tests__/watchdog.test.js +138 -12
  9. package/dist/__tests__/watchdog.test.js.map +1 -1
  10. package/dist/cli/index.js +2 -1
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/start.d.ts +2 -1
  13. package/dist/cli/start.js +6 -3
  14. package/dist/cli/start.js.map +1 -1
  15. package/dist/cli/status.js +11 -0
  16. package/dist/cli/status.js.map +1 -1
  17. package/dist/cli/stop.js +7 -2
  18. package/dist/cli/stop.js.map +1 -1
  19. package/dist/config/schema.d.ts +4 -2
  20. package/dist/core/daemon/assignment-message.d.ts +12 -0
  21. package/dist/core/daemon/assignment-message.js +36 -0
  22. package/dist/core/daemon/assignment-message.js.map +1 -0
  23. package/dist/core/daemon/bootstrap.d.ts +35 -0
  24. package/dist/core/daemon/bootstrap.js +52 -0
  25. package/dist/core/daemon/bootstrap.js.map +1 -0
  26. package/dist/core/daemon/crash-log.d.ts +16 -0
  27. package/dist/core/daemon/crash-log.js +24 -0
  28. package/dist/core/daemon/crash-log.js.map +1 -0
  29. package/dist/core/daemon/health-policy.d.ts +21 -0
  30. package/dist/core/daemon/health-policy.js +32 -0
  31. package/dist/core/daemon/health-policy.js.map +1 -0
  32. package/dist/core/daemon/sandbox-config.d.ts +9 -0
  33. package/dist/core/daemon/sandbox-config.js +17 -0
  34. package/dist/core/daemon/sandbox-config.js.map +1 -0
  35. package/dist/core/daemon/state.d.ts +33 -0
  36. package/dist/core/daemon/state.js +77 -0
  37. package/dist/core/daemon/state.js.map +1 -0
  38. package/dist/core/daemon/tmux-session.d.ts +17 -0
  39. package/dist/core/daemon/tmux-session.js +34 -0
  40. package/dist/core/daemon/tmux-session.js.map +1 -0
  41. package/dist/core/daemon/workspace.d.ts +10 -0
  42. package/dist/core/daemon/workspace.js +51 -0
  43. package/dist/core/daemon/workspace.js.map +1 -0
  44. package/dist/core/daemon.d.ts +4 -7
  45. package/dist/core/daemon.js +143 -259
  46. package/dist/core/daemon.js.map +1 -1
  47. package/dist/core/injector.js +6 -0
  48. package/dist/core/injector.js.map +1 -1
  49. package/dist/core/registry.js +1 -1
  50. package/dist/core/registry.js.map +1 -1
  51. package/dist/core/runner/build.d.ts +9 -0
  52. package/dist/core/runner/build.js +53 -0
  53. package/dist/core/runner/build.js.map +1 -0
  54. package/dist/core/runner/detect.d.ts +5 -0
  55. package/dist/core/runner/detect.js +14 -0
  56. package/dist/core/runner/detect.js.map +1 -0
  57. package/dist/core/runner/index.d.ts +5 -0
  58. package/dist/core/runner/index.js +5 -0
  59. package/dist/core/runner/index.js.map +1 -0
  60. package/dist/core/runner/model.d.ts +5 -0
  61. package/dist/core/runner/model.js +7 -0
  62. package/dist/core/runner/model.js.map +1 -0
  63. package/dist/core/runner/opencode-models.d.ts +15 -0
  64. package/dist/core/runner/opencode-models.js +70 -0
  65. package/dist/core/runner/opencode-models.js.map +1 -0
  66. package/dist/core/runner/types.d.ts +19 -0
  67. package/dist/core/runner/types.js +8 -0
  68. package/dist/core/runner/types.js.map +1 -0
  69. package/dist/core/runner.d.ts +5 -47
  70. package/dist/core/runner.js +5 -167
  71. package/dist/core/runner.js.map +1 -1
  72. package/dist/core/tmux-runtime.d.ts +13 -0
  73. package/dist/core/tmux-runtime.js +72 -0
  74. package/dist/core/tmux-runtime.js.map +1 -0
  75. package/dist/core/tmux.d.ts +7 -1
  76. package/dist/core/tmux.js +75 -45
  77. package/dist/core/tmux.js.map +1 -1
  78. package/dist/core/watchdog.d.ts +18 -1
  79. package/dist/core/watchdog.js +78 -29
  80. package/dist/core/watchdog.js.map +1 -1
  81. package/package.json +30 -11
  82. package/dist/cli/inbox.d.ts +0 -5
  83. package/dist/cli/inbox.js +0 -123
  84. package/dist/cli/inbox.js.map +0 -1
  85. package/dist/cli/issue.d.ts +0 -42
  86. package/dist/cli/issue.js +0 -297
  87. package/dist/cli/issue.js.map +0 -1
  88. package/dist/cli/ready.d.ts +0 -5
  89. package/dist/cli/ready.js +0 -131
  90. package/dist/cli/ready.js.map +0 -1
  91. package/dist/cli/sync.d.ts +0 -8
  92. package/dist/cli/sync.js +0 -154
  93. package/dist/cli/sync.js.map +0 -1
  94. package/dist/core/issue-cache.d.ts +0 -44
  95. package/dist/core/issue-cache.js +0 -75
  96. package/dist/core/issue-cache.js.map +0 -1
  97. package/src/__tests__/context.test.ts +0 -464
  98. package/src/__tests__/injector.test.ts +0 -29
  99. package/src/__tests__/jwt.test.ts +0 -112
  100. package/src/__tests__/loader.test.ts +0 -239
  101. package/src/__tests__/runner.test.ts +0 -104
  102. package/src/__tests__/sandbox.test.ts +0 -435
  103. package/src/__tests__/watchdog.test.ts +0 -368
  104. package/src/cli/attach.ts +0 -22
  105. package/src/cli/build.ts +0 -145
  106. package/src/cli/config.ts +0 -148
  107. package/src/cli/context.ts +0 -231
  108. package/src/cli/deploy.ts +0 -155
  109. package/src/cli/index.ts +0 -375
  110. package/src/cli/init.ts +0 -75
  111. package/src/cli/list.ts +0 -70
  112. package/src/cli/local.ts +0 -183
  113. package/src/cli/logs.ts +0 -64
  114. package/src/cli/migrate.ts +0 -212
  115. package/src/cli/nudge.ts +0 -81
  116. package/src/cli/restart.ts +0 -59
  117. package/src/cli/slack.ts +0 -70
  118. package/src/cli/start.ts +0 -115
  119. package/src/cli/status.ts +0 -91
  120. package/src/cli/stop.ts +0 -48
  121. package/src/cli/test.ts +0 -143
  122. package/src/cli/token.ts +0 -188
  123. package/src/cli/whoami.ts +0 -142
  124. package/src/config/loader.ts +0 -121
  125. package/src/config/schema.ts +0 -68
  126. package/src/context/handoff.ts +0 -122
  127. package/src/context/index.ts +0 -8
  128. package/src/context/schema.ts +0 -111
  129. package/src/context/storage.ts +0 -197
  130. package/src/core/daemon.ts +0 -1308
  131. package/src/core/heartbeat.ts +0 -129
  132. package/src/core/injector.ts +0 -292
  133. package/src/core/registry.ts +0 -159
  134. package/src/core/runner.ts +0 -225
  135. package/src/core/sandbox.ts +0 -547
  136. package/src/core/session-id.ts +0 -111
  137. package/src/core/tmux.ts +0 -405
  138. package/src/core/watchdog.ts +0 -238
  139. package/src/core/websocket.ts +0 -94
  140. package/src/index.ts +0 -10
  141. package/src/utils/jwt.ts +0 -87
  142. package/tsconfig.json +0 -8
  143. package/vitest.config.ts +0 -12
@@ -1,112 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- decodeToken,
4
- getAgentIdFromToken,
5
- getTokenExpiry,
6
- getTokenTimeRemaining,
7
- isTokenExpired,
8
- isTokenExpiringSoon,
9
- } from "../utils/jwt.js";
10
-
11
- // Sample JWT tokens for testing
12
- // These are test tokens with known payloads
13
-
14
- // Token payload: { sub: "test-agent-123", actorType: "agent", workspaceScopes: ["test"], iat: 1700000000, exp: 1700604800 }
15
- const VALID_TOKEN =
16
- "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0LWFnZW50LTEyMyIsImFjdG9yVHlwZSI6ImFnZW50Iiwid29ya3NwYWNlU2NvcGVzIjpbInRlc3QiXSwiaWF0IjoxNzAwMDAwMDAwLCJleHAiOjE3MDA2MDQ4MDB9.fake_signature";
17
-
18
- // Expired token (exp: 1600000000 - Sep 2020)
19
- const EXPIRED_TOKEN =
20
- "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJleHBpcmVkLWFnZW50IiwiYWN0b3JUeXBlIjoiYWdlbnQiLCJ3b3Jrc3BhY2VTY29wZXMiOlsidGVzdCJdLCJpYXQiOjE1MDAwMDAwMDAsImV4cCI6MTYwMDAwMDAwMH0.fake_signature";
21
-
22
- // Future token (exp: 2700000000 - ~2055)
23
- const FUTURE_TOKEN =
24
- "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmdXR1cmUtYWdlbnQiLCJhY3RvclR5cGUiOiJhZ2VudCIsIndvcmtzcGFjZVNjb3BlcyI6WyJ0ZXN0Il0sImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoyNzAwMDAwMDAwfQ.fake_signature";
25
-
26
- describe("JWT Utils", () => {
27
- describe("decodeToken", () => {
28
- it("should decode a valid JWT token", () => {
29
- const payload = decodeToken(VALID_TOKEN);
30
- expect(payload).not.toBeNull();
31
- expect(payload?.sub).toBe("test-agent-123");
32
- expect(payload?.actorType).toBe("agent");
33
- expect(payload?.workspaceScopes).toEqual(["test"]);
34
- });
35
-
36
- it("should return null for invalid token format", () => {
37
- expect(decodeToken("invalid")).toBeNull();
38
- expect(decodeToken("")).toBeNull();
39
- expect(decodeToken("a.b")).toBeNull();
40
- });
41
-
42
- it("should return null for malformed base64", () => {
43
- expect(decodeToken("a.!!!invalid!!!.c")).toBeNull();
44
- });
45
- });
46
-
47
- describe("getTokenExpiry", () => {
48
- it("should return expiry date for valid token", () => {
49
- const expiry = getTokenExpiry(VALID_TOKEN);
50
- expect(expiry).toBeInstanceOf(Date);
51
- expect(expiry?.getTime()).toBe(1700604800 * 1000);
52
- });
53
-
54
- it("should return null for invalid token", () => {
55
- expect(getTokenExpiry("invalid")).toBeNull();
56
- });
57
- });
58
-
59
- describe("isTokenExpired", () => {
60
- it("should return true for expired token", () => {
61
- expect(isTokenExpired(EXPIRED_TOKEN)).toBe(true);
62
- });
63
-
64
- it("should return false for future token", () => {
65
- expect(isTokenExpired(FUTURE_TOKEN)).toBe(false);
66
- });
67
-
68
- it("should return true for invalid token", () => {
69
- expect(isTokenExpired("invalid")).toBe(true);
70
- });
71
- });
72
-
73
- describe("isTokenExpiringSoon", () => {
74
- it("should return true for expired token", () => {
75
- expect(isTokenExpiringSoon(EXPIRED_TOKEN, 1000)).toBe(true);
76
- });
77
-
78
- it("should return false for future token with short threshold", () => {
79
- // Future token expires in ~2055, so any reasonable threshold should return false
80
- expect(isTokenExpiringSoon(FUTURE_TOKEN, 24 * 60 * 60 * 1000)).toBe(false);
81
- });
82
-
83
- it("should return true for invalid token", () => {
84
- expect(isTokenExpiringSoon("invalid", 1000)).toBe(true);
85
- });
86
- });
87
-
88
- describe("getAgentIdFromToken", () => {
89
- it("should return agent ID from valid token", () => {
90
- expect(getAgentIdFromToken(VALID_TOKEN)).toBe("test-agent-123");
91
- });
92
-
93
- it("should return null for invalid token", () => {
94
- expect(getAgentIdFromToken("invalid")).toBeNull();
95
- });
96
- });
97
-
98
- describe("getTokenTimeRemaining", () => {
99
- it("should return 0 for expired token", () => {
100
- expect(getTokenTimeRemaining(EXPIRED_TOKEN)).toBe(0);
101
- });
102
-
103
- it("should return positive value for future token", () => {
104
- const remaining = getTokenTimeRemaining(FUTURE_TOKEN);
105
- expect(remaining).toBeGreaterThan(0);
106
- });
107
-
108
- it("should return 0 for invalid token", () => {
109
- expect(getTokenTimeRemaining("invalid")).toBe(0);
110
- });
111
- });
112
- });
@@ -1,239 +0,0 @@
1
- import * as fs from "node:fs";
2
- import { beforeEach, describe, expect, it, vi } from "vitest";
3
- import {
4
- addAgentToState,
5
- getAgentState,
6
- loadConfig,
7
- loadState,
8
- removeAgentFromState,
9
- resetAgentRestartCount,
10
- updateAgentInState,
11
- } from "../config/loader.js";
12
- import type { AgentState, Config, State } from "../config/schema.js";
13
-
14
- // Mock the file system
15
- vi.mock("node:fs");
16
-
17
- describe("Config Loader", () => {
18
- const mockConfig: Config = {
19
- hubUrl: "https://test.agentmesh.dev",
20
- apiKey: "test-api-key",
21
- workspace: "test-workspace",
22
- defaults: {
23
- command: "opencode",
24
- model: "claude-sonnet-4",
25
- },
26
- agents: [],
27
- };
28
-
29
- const mockState: State = {
30
- agents: [
31
- {
32
- name: "test-agent",
33
- agentId: "agent-123",
34
- pid: 12345,
35
- tmuxSession: "agentmesh-test-agent",
36
- startedAt: "2024-01-01T00:00:00Z",
37
- token: "test-token",
38
- },
39
- ],
40
- };
41
-
42
- beforeEach(() => {
43
- vi.resetAllMocks();
44
- });
45
-
46
- describe("loadConfig", () => {
47
- it("should return null if config file does not exist", () => {
48
- vi.mocked(fs.existsSync).mockReturnValue(false);
49
- expect(loadConfig()).toBeNull();
50
- });
51
-
52
- it("should load and parse config file", () => {
53
- vi.mocked(fs.existsSync).mockReturnValue(true);
54
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockConfig));
55
-
56
- const config = loadConfig();
57
- expect(config).toEqual(mockConfig);
58
- });
59
-
60
- it("should return null on parse error", () => {
61
- vi.mocked(fs.existsSync).mockReturnValue(true);
62
- vi.mocked(fs.readFileSync).mockReturnValue("invalid json");
63
-
64
- expect(loadConfig()).toBeNull();
65
- });
66
- });
67
-
68
- describe("loadState", () => {
69
- it("should return empty state if file does not exist", () => {
70
- vi.mocked(fs.existsSync).mockReturnValue(false);
71
- expect(loadState()).toEqual({ agents: [] });
72
- });
73
-
74
- it("should load and parse state file", () => {
75
- vi.mocked(fs.existsSync).mockReturnValue(true);
76
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockState));
77
-
78
- const state = loadState();
79
- expect(state).toEqual(mockState);
80
- });
81
- });
82
-
83
- describe("addAgentToState", () => {
84
- it("should add new agent to state", () => {
85
- vi.mocked(fs.existsSync).mockReturnValue(true);
86
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify({ agents: [] }));
87
- vi.mocked(fs.writeFileSync).mockImplementation(() => {});
88
-
89
- const newAgent: AgentState = {
90
- name: "new-agent",
91
- agentId: "agent-456",
92
- pid: 67890,
93
- tmuxSession: "agentmesh-new-agent",
94
- startedAt: "2024-01-02T00:00:00Z",
95
- token: "new-token",
96
- };
97
-
98
- addAgentToState(newAgent);
99
-
100
- expect(fs.writeFileSync).toHaveBeenCalled();
101
- const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1] as string;
102
- const parsedState = JSON.parse(writtenContent);
103
- expect(parsedState.agents).toHaveLength(1);
104
- expect(parsedState.agents[0].name).toBe("new-agent");
105
- });
106
-
107
- it("should replace existing agent with same name", () => {
108
- vi.mocked(fs.existsSync).mockReturnValue(true);
109
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockState));
110
- vi.mocked(fs.writeFileSync).mockImplementation(() => {});
111
-
112
- const updatedAgent: AgentState = {
113
- name: "test-agent",
114
- agentId: "agent-789",
115
- pid: 11111,
116
- tmuxSession: "agentmesh-test-agent",
117
- startedAt: "2024-01-03T00:00:00Z",
118
- token: "updated-token",
119
- };
120
-
121
- addAgentToState(updatedAgent);
122
-
123
- const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1] as string;
124
- const parsedState = JSON.parse(writtenContent);
125
- expect(parsedState.agents).toHaveLength(1);
126
- expect(parsedState.agents[0].agentId).toBe("agent-789");
127
- });
128
- });
129
-
130
- describe("removeAgentFromState", () => {
131
- it("should remove agent from state", () => {
132
- vi.mocked(fs.existsSync).mockReturnValue(true);
133
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockState));
134
- vi.mocked(fs.writeFileSync).mockImplementation(() => {});
135
-
136
- removeAgentFromState("test-agent");
137
-
138
- const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1] as string;
139
- const parsedState = JSON.parse(writtenContent);
140
- expect(parsedState.agents).toHaveLength(0);
141
- });
142
- });
143
-
144
- describe("getAgentState", () => {
145
- it("should return agent by name", () => {
146
- vi.mocked(fs.existsSync).mockReturnValue(true);
147
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockState));
148
-
149
- const agent = getAgentState("test-agent");
150
- expect(agent).toBeDefined();
151
- expect(agent?.agentId).toBe("agent-123");
152
- });
153
-
154
- it("should return undefined for non-existent agent", () => {
155
- vi.mocked(fs.existsSync).mockReturnValue(true);
156
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockState));
157
-
158
- const agent = getAgentState("non-existent");
159
- expect(agent).toBeUndefined();
160
- });
161
- });
162
-
163
- describe("updateAgentInState", () => {
164
- it("should update agent fields", () => {
165
- vi.mocked(fs.existsSync).mockReturnValue(true);
166
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockState));
167
- vi.mocked(fs.writeFileSync).mockImplementation(() => {});
168
-
169
- updateAgentInState("test-agent", { token: "new-token-123" });
170
-
171
- const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1] as string;
172
- const parsedState = JSON.parse(writtenContent);
173
- expect(parsedState.agents[0].token).toBe("new-token-123");
174
- // Other fields should remain unchanged
175
- expect(parsedState.agents[0].agentId).toBe("agent-123");
176
- });
177
-
178
- it("should do nothing for non-existent agent", () => {
179
- vi.mocked(fs.existsSync).mockReturnValue(true);
180
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockState));
181
- vi.mocked(fs.writeFileSync).mockImplementation(() => {});
182
-
183
- updateAgentInState("non-existent", { token: "new-token" });
184
-
185
- // writeFileSync should not be called since agent doesn't exist
186
- // Actually it will be called but the state will be unchanged
187
- });
188
-
189
- it("should update restart count and status fields", () => {
190
- vi.mocked(fs.existsSync).mockReturnValue(true);
191
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockState));
192
- vi.mocked(fs.writeFileSync).mockImplementation(() => {});
193
-
194
- updateAgentInState("test-agent", {
195
- restartCount: 2,
196
- status: "stuck",
197
- stuckSince: "2024-01-01T01:00:00Z",
198
- });
199
-
200
- const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1] as string;
201
- const parsedState = JSON.parse(writtenContent);
202
- expect(parsedState.agents[0].restartCount).toBe(2);
203
- expect(parsedState.agents[0].status).toBe("stuck");
204
- expect(parsedState.agents[0].stuckSince).toBe("2024-01-01T01:00:00Z");
205
- });
206
- });
207
-
208
- describe("resetAgentRestartCount", () => {
209
- it("should reset restart count and clear stuck status", () => {
210
- const stuckState: State = {
211
- agents: [
212
- {
213
- name: "test-agent",
214
- agentId: "agent-123",
215
- pid: 12345,
216
- tmuxSession: "agentmesh-test-agent",
217
- startedAt: "2024-01-01T00:00:00Z",
218
- token: "test-token",
219
- restartCount: 3,
220
- status: "stuck",
221
- stuckSince: "2024-01-01T01:00:00Z",
222
- },
223
- ],
224
- };
225
-
226
- vi.mocked(fs.existsSync).mockReturnValue(true);
227
- vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(stuckState));
228
- vi.mocked(fs.writeFileSync).mockImplementation(() => {});
229
-
230
- resetAgentRestartCount("test-agent");
231
-
232
- const writtenContent = vi.mocked(fs.writeFileSync).mock.calls[0][1] as string;
233
- const parsedState = JSON.parse(writtenContent);
234
- expect(parsedState.agents[0].restartCount).toBe(0);
235
- expect(parsedState.agents[0].status).toBe("running");
236
- expect(parsedState.agents[0].stuckSince).toBeUndefined();
237
- });
238
- });
239
- });
@@ -1,104 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- buildRunnerConfig,
4
- detectRunner,
5
- getRunnerDisplayName,
6
- resolveModel,
7
- } from "../core/runner.js";
8
-
9
- describe("Runner Module", () => {
10
- describe("detectRunner", () => {
11
- it("should detect opencode runner", () => {
12
- expect(detectRunner("opencode")).toBe("opencode");
13
- expect(detectRunner("opencode --some-flag")).toBe("opencode");
14
- expect(detectRunner("OPENCODE")).toBe("opencode");
15
- });
16
-
17
- it("should detect claude runner", () => {
18
- expect(detectRunner("claude")).toBe("claude");
19
- expect(detectRunner("claude --some-flag")).toBe("claude");
20
- expect(detectRunner("CLAUDE")).toBe("claude");
21
- });
22
-
23
- it("should detect custom runner for unknown commands", () => {
24
- expect(detectRunner("aider")).toBe("custom");
25
- expect(detectRunner("cursor")).toBe("custom");
26
- expect(detectRunner("my-custom-tool")).toBe("custom");
27
- });
28
- });
29
-
30
- describe("resolveModel", () => {
31
- it("should prioritize CLI model over all others", () => {
32
- const result = resolveModel({
33
- cliModel: "cli-model",
34
- agentModel: "agent-model",
35
- defaultModel: "default-model",
36
- command: "opencode",
37
- });
38
- expect(result).toBe("cli-model");
39
- });
40
-
41
- it("should use agent model when CLI not provided", () => {
42
- const result = resolveModel({
43
- agentModel: "agent-model",
44
- defaultModel: "default-model",
45
- command: "opencode",
46
- });
47
- expect(result).toBe("agent-model");
48
- });
49
-
50
- it("should fall back to default model", () => {
51
- const result = resolveModel({
52
- defaultModel: "default-model",
53
- command: "opencode",
54
- });
55
- expect(result).toBe("default-model");
56
- });
57
- });
58
-
59
- describe("buildRunnerConfig", () => {
60
- it("should build config for opencode with OPENCODE_MODEL env", () => {
61
- const config = buildRunnerConfig({
62
- cliModel: "claude-sonnet-4",
63
- defaultModel: "claude-sonnet-4",
64
- command: "opencode",
65
- });
66
-
67
- expect(config.type).toBe("opencode");
68
- expect(config.model).toBe("claude-sonnet-4");
69
- expect(config.env.OPENCODE_MODEL).toBeDefined();
70
- });
71
-
72
- it("should build config for claude with CLAUDE_MODEL env", () => {
73
- const config = buildRunnerConfig({
74
- cliModel: "claude-sonnet-4",
75
- defaultModel: "claude-sonnet-4",
76
- command: "claude",
77
- });
78
-
79
- expect(config.type).toBe("claude");
80
- expect(config.model).toBe("claude-sonnet-4");
81
- expect(config.env.CLAUDE_MODEL).toBe("claude-sonnet-4");
82
- });
83
-
84
- it("should build config for custom runner without env vars", () => {
85
- const config = buildRunnerConfig({
86
- cliModel: "gpt-4",
87
- defaultModel: "claude-sonnet-4",
88
- command: "aider",
89
- });
90
-
91
- expect(config.type).toBe("custom");
92
- expect(config.model).toBe("gpt-4");
93
- expect(Object.keys(config.env)).toHaveLength(0);
94
- });
95
- });
96
-
97
- describe("getRunnerDisplayName", () => {
98
- it("should return correct display names", () => {
99
- expect(getRunnerDisplayName("opencode")).toBe("OpenCode");
100
- expect(getRunnerDisplayName("claude")).toBe("Claude CLI");
101
- expect(getRunnerDisplayName("custom")).toBe("Custom");
102
- });
103
- });
104
- });